Compare commits

...

301 Commits

Author SHA1 Message Date
Simon Vieille 64258a2d8c Merge branch 'develop' 2024-03-25 15:27:12 +01:00
Simon Vieille 70329ceeda
fix template of CrudController (maker) 2024-03-25 15:27:06 +01:00
Simon Vieille 1adb1ebe2b Merge branch 'develop' 2024-03-25 13:04:17 +01:00
Simon Vieille f57cc8e4d6
fix hidden save button in file manager 2024-03-25 13:04:16 +01:00
Simon Vieille b21967028e Merge branch 'develop' 2024-03-25 13:03:01 +01:00
Simon Vieille 5b22851674
fix hidden save button in file manager 2024-03-25 13:02:20 +01:00
Simon Vieille 4082bb171a
fix use of IsGranted is CrudController 2024-02-04 16:48:44 +01:00
Simon Vieille dc19617fb1 Merge branch 'develop' 2024-02-04 16:33:34 +01:00
Simon Vieille dbd2036fb0
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:33:30 +01:00
Simon Vieille 8e2566abc8
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:33:22 +01:00
Simon Vieille 66e1a9c87f
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:31:21 +01:00
Simon Vieille 6709c0a303
update changelog 2024-02-04 16:17:49 +01:00
Simon Vieille 614ae40901 add IsGranted in all methods of the CrudController maker template 2024-02-04 16:16:22 +01:00
Simon Vieille 430bff9433
fix default crud sort 2024-02-02 20:04:19 +01:00
Simon Vieille 6441da8a27
fix default crud sort 2024-02-02 20:03:57 +01:00
Simon Vieille 801e3317e7
update changelog 2024-02-02 20:02:00 +01:00
Simon Vieille f7604d2a45
fix default crud sort 2024-02-02 20:01:23 +01:00
Simon Vieille 8a632a1b14
update Murph version constant 2024-02-01 18:46:33 +01:00
Simon Vieille 4701090134
update changelog 2024-01-27 15:49:58 +01:00
Simon Vieille c40c7e3362
fix type casting in slugifier 2024-01-27 15:49:39 +01:00
Simon Vieille 8edbf0cc08
add default default on node's code when slugify 2023-11-27 10:32:38 +01:00
Simon Vieille 175321bc2d
fix issue on file manager when a file is selected in the file in the file picker 2023-11-16 23:06:00 +01:00
Simon Vieille 5d6531d197
fix sidebar scroll algo 2023-11-14 23:21:24 +01:00
Simon Vieille 90603f62e0
add side bar scroll animation 2023-11-14 23:08:09 +01:00
Simon Vieille 053f4aa5b8
add auto-scroll on current sidebar item 2023-11-14 23:03:43 +01:00
Simon Vieille 79754d45c1
add auto-scroll on current sidebar item
remove jquery from sidebar module
2023-11-14 22:51:40 +01:00
Simon Vieille c98ea50f30
fix undefined pager on index 2023-11-13 16:06:06 +01:00
Simon Vieille 0f1bc761b2
add no-wrap around the thead sort link 2023-11-10 19:42:45 +01:00
Simon Vieille 50dbb07314
update changelog 2023-11-10 19:35:48 +01:00
Simon Vieille 72e783f865
fix render of the URL when the window is small 2023-11-10 19:35:39 +01:00
Simon Vieille ee28c9abb7
copy the pager in the bottom of the index table
remove the with class of the action column
2023-11-10 19:28:20 +01:00
Simon Vieille 2bd6836a7f
fix issue on file manager when a file is selected in the file in the file picker 2023-11-09 17:19:09 +01:00
Simon Vieille 6f961ba79b
release v1.23.0 2023-11-01 16:31:33 +01:00
Simon Vieille e095fc4197
update changelog 2023-10-27 17:03:08 +02:00
Simon Vieille 6736f94eea
set searchFields option on jschoice manager 2023-10-27 17:02:15 +02:00
Simon Vieille 93a1e7811d
change colors on js-choices element 2023-10-25 20:15:21 +02:00
Simon Vieille a0027c0b69 Merge branch 'feature/theming' into develop 2023-10-25 19:59:32 +02:00
Simon Vieille 498c71081d
add red * on required label 2023-10-25 19:59:28 +02:00
Simon Vieille 8713b401f9
rollback modal changes 2023-10-25 19:36:10 +02:00
Simon Vieille 1463f43298
update changelog 2023-10-25 19:22:56 +02:00
Simon Vieille b89e036c49 change border colors of inputs when focused 2023-10-25 19:22:44 +02:00
Simon Vieille 1d0b657c83
add sass classes to mange with of elements
fix the aspect of the actions's column in the crud

add background in the modal header

change vavigation pills colors
2023-10-25 19:12:02 +02:00
Simon Vieille 0cadf28738
update changelog 2023-10-20 09:46:57 +02:00
Simon Vieille ede8d4fdcb
remove unsed twig in mail notifier 2023-10-20 09:45:58 +02:00
Simon Vieille 5c3f2ab1e7
refactor services using constructor property promotions 2023-10-20 09:44:18 +02:00
Simon Vieille c1eb277a6a
add 'Length' constraint in forms 2023-10-19 20:51:23 +02:00
Simon Vieille d3f27d97ad
apply php linter 2023-10-12 16:15:07 +02:00
Simon Vieille 5e392d469a
update changelog 2023-10-12 16:04:49 +02:00
Simon Vieille 67f79083ef
change params given to the callback of a global batch action (page removed, add selected items) 2023-10-12 16:04:43 +02:00
Simon Vieille b9b07c1409
fix issue with CrudConfiguration::setGlobalBatchAction 2023-10-12 16:04:03 +02:00
Simon Vieille 521ed5ce64 Merge branch 'feature/batch' into develop 2023-10-12 15:28:17 +02:00
Simon Vieille dda43ef3cc
change CrudController::doBatch to manage a global batch action 2023-10-12 15:28:14 +02:00
Simon Vieille c65cc26be8
batch form is not submitted with XHR when it's a global action 2023-10-12 15:27:36 +02:00
Simon Vieille 2f884df602
add CrudConfiguration::setGlobalBatchAction method 2023-10-12 15:25:52 +02:00
Simon Vieille 8979fc5beb
fix test in RepositoryQuery::addForcedFilterHandler 2023-10-10 10:18:25 +02:00
Simon Vieille bd663838f6
allow to define templates show before and after a murph collection item 2023-10-06 12:46:51 +02:00
Simon Vieille 177b23365b
update changelog 2023-10-04 13:55:08 +02:00
Simon Vieille 645ae700d4
remove parameter $option on CrudConfiguration::setForm
fix CrudController make template
2023-10-04 13:54:37 +02:00
Simon Vieille 7614c24012
fix regression on crud sorting 2023-10-04 13:47:01 +02:00
Simon Vieille 1f2edf183b
release v1.22.0 2023-09-28 18:14:49 +02:00
Simon Vieille 28a4f63640
fix compatibility of TranslatableEntityManager with the entity manager 2023-09-28 10:32:49 +02:00
Simon Vieille 7fceefa6d3
add flush option in the entity manager on create, update, remove, and persist methods 2023-09-28 10:31:00 +02:00
Simon Vieille a08c62229d
update changelog 2023-09-27 15:17:54 +02:00
Simon Vieille f97f1dfedf
add crud sorting in the session 2023-09-27 15:17:35 +02:00
Simon Vieille 45b3f6bb80
update changelog 2023-09-27 10:22:29 +02:00
Simon Vieille 21ee41ff29
add count method on repository query
add addForcedFilterHandler method
2023-09-27 10:21:49 +02:00
Simon Vieille 8d5de79192
add 'inline_form_validation' option to validate inline forms with custom algo 2023-09-26 16:12:23 +02:00
Simon Vieille 212afe2775
fix href_attr allowed types 2023-09-15 15:27:56 +02:00
Simon Vieille 16dd0d5744
fix session name in inline form controller 2023-09-14 16:57:39 +02:00
Simon Vieille 9957523c59
add context variable into admin controller's views 2023-09-09 13:48:03 +02:00
Simon Vieille 688f66e94e
update default class on toggle display 2023-08-31 16:20:07 +02:00
Simon Vieille c9b997e75d add new options in BooleanField: `toggle|checkbox_class_when_true` and `toggle|checkbox_class_when_false` 2023-08-31 16:12:57 +02:00
Simon Vieille 374db9117f
release v1.21.1 2023-08-17 20:24:12 +02:00
Simon Vieille 4048152a8e
update changelog 2023-08-17 20:16:04 +02:00
Simon Vieille 4385e7a525
add form error handle in settings actions 2023-08-17 20:15:02 +02:00
Simon Vieille 5d3999f766
update translations 2023-08-17 08:59:06 +02:00
Simon Vieille 03c0d6cfd2
fix modal hiding when a file is successfuly uploaded in the file manager on tinymce 2023-08-16 10:08:02 +02:00
Simon Vieille 290a4750bc Revert "remove sensio/framework-extra-bundle"
This reverts commit 7d647d3bb4.
2023-08-14 14:32:34 +02:00
Simon Vieille 7d647d3bb4
remove sensio/framework-extra-bundle 2023-08-14 14:29:11 +02:00
Simon Vieille d637c44e5c
replace the navigation badge with a fontawesome square 2023-08-14 12:47:33 +02:00
Simon Vieille 19d26e6bd0
fix modal hiding when a file is successfuly uploaded in the file manager 2023-08-14 11:39:53 +02:00
Simon Vieille 109584d933
update changelog 2023-08-13 22:38:42 +02:00
Simon Vieille c3209c68dd
fix modal hiding when a file is successfuly uploaded in the file manager 2023-08-13 22:38:10 +02:00
Simon Vieille 4a5b67dd93
update changelog 2023-08-13 22:12:44 +02:00
Simon Vieille 4573a8d31e
fix tinymce reload when modal is closed and reopened 2023-08-13 22:11:44 +02:00
Simon Vieille 405909a4e7
remove last request data after filling the form 2023-08-11 19:28:09 +02:00
Simon Vieille 2b3e2027c9
update changelog 2023-08-11 19:20:43 +02:00
Simon Vieille 76b25f3bca
add form error handle in inline edit action
refill the form using last request
2023-08-11 19:18:11 +02:00
Simon Vieille ca25210d1c
release v1.21.0 2023-08-11 09:47:46 +02:00
Simon Vieille 0ab960ed0a
allow to use array syntax in string builder filter 2023-08-08 14:18:10 +02:00
Simon Vieille 8e0a7f178b
fix block in BooleanField template 2023-08-08 12:15:01 +02:00
Simon Vieille 200dd0b8d6
add the option 'display' on BooleanField
add displays toggle and checkbox for BooleanField
2023-08-08 12:13:28 +02:00
Simon Vieille bd4338bb2d
fix getColor return type 2023-08-07 19:28:51 +02:00
Simon Vieille 64b524b04e
update changelog 2023-08-07 19:20:44 +02:00
Simon Vieille 6a5c5d899f
add color property in Navigation
add badge with navigation color in admin views
2023-08-07 19:20:14 +02:00
Simon Vieille b38fe0fe00
update changelog 2023-08-07 18:39:33 +02:00
Simon Vieille d21ab30ebe
add default_value option in crud fields 2023-08-07 18:31:22 +02:00
Simon Vieille a96a6377d5
add associated nodes in page form 2023-08-07 18:24:31 +02:00
Simon Vieille 946a421900
add default value for $route param in doDelete method 2023-08-04 11:08:56 +02:00
Simon Vieille f7eb5b0b49
fix routes in the global settings controller 2023-07-28 10:43:21 +02:00
Simon Vieille 0dadc670d9
update changelog 2023-07-27 18:15:33 +02:00
Simon Vieille 4a0b13e6e3
update version 2023-07-27 18:15:09 +02:00
Simon Vieille 1936b366df
remove logo in login pages if empty 2023-07-27 18:14:17 +02:00
Simon Vieille 4541bbfb8a
update changelog 2023-07-26 11:31:19 +02:00
Simon Vieille f3674ad4e0 Merge branch 'master' into develop 2023-07-26 11:29:07 +02:00
Simon Vieille ddf1fecc90
fix collection widget: allow_add/allow_delete and prototype 2023-07-26 11:28:55 +02:00
Simon Vieille 0c61cb9355
update changelog 2023-07-26 11:28:03 +02:00
Simon Vieille fc1a1c617e
update changelog 2023-07-20 09:20:18 +02:00
Simon Vieille bea2d1cc9f
add default templates when a crud is generated 2023-07-19 22:01:25 +02:00
Simon Vieille 4bf6b048c3
add missing configuration property
remove user admin controller routes
2023-07-19 21:43:16 +02:00
Simon Vieille 81194a1d67
add boolean 'is_disabled' in the menu item template options 2023-07-19 21:42:28 +02:00
Simon Vieille 10221591c2 core.site.name and core.site.logo are not longer required 2023-07-19 20:42:35 +02:00
Simon Vieille 231742eb0d
fix responsive of account edit template 2023-07-19 20:28:37 +02:00
Simon Vieille f1d956ee5c
add context variable in each controllers to simplify overrides 2023-07-19 20:20:55 +02:00
Simon Vieille 5de35c3408
add context variable in each controllers to simplify overrides 2023-07-19 20:20:45 +02:00
Simon Vieille f144760085
add boolean field for CRUD 2023-07-19 20:19:16 +02:00
Simon Vieille e2c9ecb941
add option to remove itetables values and/or specifics keys in the twig toArray function 2023-07-19 19:36:32 +02:00
Simon Vieille f9a20716a0
add twig block to override defaults actions in crud index template 2023-07-19 14:18:50 +02:00
Simon Vieille b107f077de
fix redirect listener: use boolean instead of integer 2023-07-04 12:49:56 +02:00
Simon Vieille 2729b38fd8
add variable for the sidebar size 2023-06-07 10:57:15 +02:00
Simon Vieille baeecc0f7f
fix translations 2023-06-05 16:52:58 +02:00
Simon Vieille 8667188675
add filename generator setter in FileUploadHandler 2023-05-27 15:48:58 +02:00
Simon Vieille 895d5065ca
add setter to define all fields in a defined context 2023-05-27 15:08:16 +02:00
Simon Vieille c688cc0552
add trans filter in inline form modal title 2023-05-27 15:02:17 +02:00
Simon Vieille c0340ec5a9 fix maker CrudController template: remove bad pasted code 2023-05-27 14:55:21 +02:00
Simon Vieille 650ea3ede0
add form options in the crud filter action 2023-05-27 11:43:45 +02:00
Simon Vieille 497ee2f027
add form options in the crud filter action 2023-05-27 11:34:13 +02:00
Simon Vieille 8b13d37e71
update file details view on the file manager 2023-05-19 20:30:35 +02:00
Simon Vieille 3219ba47aa
add block class name for the choice type in the page maker 2023-05-19 19:55:02 +02:00
Simon Vieille 7ca8a4aa04
fix filemanager date ordering
add order by type
2023-04-17 13:36:09 +02:00
Simon Vieille 7ba417a03e
enable double click on cruds
add inline form on user display names
2023-04-16 23:19:34 +02:00
Simon Vieille 27cbfd1110
release v1.19.0 2023-04-15 10:48:51 +02:00
Simon Vieille 1069a4ad46
fix(crud): fix setter of double click 2023-04-14 10:57:53 +02:00
Simon Vieille a134a41926
feat(filemanager): rename date name 2023-04-14 07:11:30 +02:00
Simon Vieille b915b8b3ed
feat(crud): add the option "inline_form" in fields
feat(crud): allows to enable and disable double click listener on a table row

feat(crud): update de template of the controller
2023-04-14 07:09:57 +02:00
Simon Vieille 42ab4d85f0
show size and last modified date of files in file manager 2023-03-05 21:33:34 +01:00
Simon Vieille 902ac81e4b
allow to remove meta datas of a page when enditing 2023-03-05 20:39:52 +01:00
Simon Vieille e74469a687
update the view 'show' of a navigation 2023-03-02 21:12:18 +01:00
Simon Vieille 219526e3a2
allow webp image in filemanager 2023-03-02 19:10:08 +01:00
Simon Vieille 1cea077598
fix translation 2023-03-02 19:06:50 +01:00
Simon Vieille de6ca18c0a
release v1.18.0 2023-01-13 18:20:51 +01:00
Simon Vieille 3127018a73
fix(crud): remove default page value in abstract crud controller 2023-01-09 08:10:34 +01:00
Simon Vieille 9e0eda4338
fix(crud): allow POST in delete actions 2023-01-09 08:07:40 +01:00
Simon Vieille 03afb6fb4b Merge branch 'feature/updates' into develop 2023-01-08 20:54:16 +01:00
Simon Vieille e402c49835
fix(admin): test site_logo before using it 2023-01-08 20:50:00 +01:00
Simon Vieille 0a7550940d
feat(dep): add symfony/runtime 5.4 2023-01-08 19:59:54 +01:00
Simon Vieille 5626a2522a
feat(dep): remove symfony/runtime 2023-01-08 19:40:34 +01:00
Simon Vieille 8ebb54c9a9
feat(dep): change version of symfony/runtime 2023-01-08 19:40:00 +01:00
Simon Vieille 98c2d70367
feat(dep): add symfony/flex 2023-01-08 19:32:02 +01:00
Simon Vieille e623e9fb46
feat(dep): add symfony/runtime 2023-01-08 19:30:15 +01:00
Simon Vieille 6b222dd4f7 style(css): replace 4 spaces by 2 spaces 2023-01-08 18:09:03 +01:00
Simon Vieille bc1f8e7cf9 fix(ui): update z-index of choices__list--dropdown 2023-01-08 18:07:54 +01:00
Simon Vieille 3f4aeda307
update murph version 2022-12-03 12:27:05 +01:00
Simon Vieille a13d3c9f3c
update changelog 2022-12-03 12:24:19 +01:00
Simon Vieille d80d02bc07
replace annotation with attributes on Timestampable 2022-11-24 19:02:37 +01:00
Simon Vieille 4491c12684
fix the constructor of UrlGenerator 2022-11-19 21:08:28 +01:00
Simon Vieille e54019d4d9
add a constructor in UrlGenerator 2022-11-19 21:07:33 +01:00
Simon Vieille 34747236ef
define UrlGenerator as Attribute 2022-11-19 21:04:58 +01:00
Simon Vieille 173ed9aa83
update sitemap to use attributes instead of annotations 2022-11-19 20:32:17 +01:00
Simon Vieille 154e6d3e0b
release v1.17.0 2022-11-19 19:55:50 +01:00
Simon Vieille 25a47ebfb1
release v1.17.0 2022-11-19 19:53:28 +01:00
Simon Vieille 9454ceb539
replace annotation with attributes 2022-11-19 19:45:12 +01:00
Simon Vieille 7dc463d14a
replace annotation with attributes 2022-11-19 19:35:28 +01:00
Simon Vieille e130a42a57
update changelog 2022-11-19 19:32:40 +01:00
Simon Vieille 6f5cda1bbc
fix tinymce modal z-index in tox 2022-11-19 19:13:20 +01:00
Simon Vieille dd5a2f55de
fix murph version number 2022-09-19 11:44:43 +02:00
Simon Vieille 6074d4cde6
release v1.16.0 2022-09-06 12:06:33 +02:00
Simon Vieille cf3bc02ed7
add cleanup of html string extracted from grapesjs content 2022-06-12 23:24:41 +02:00
Simon Vieille 6e0a4107c7
refactoring of AbListener 2022-05-21 00:38:41 +02:00
Simon Vieille a16f4f998e
refactoring of AbListener 2022-05-21 00:28:48 +02:00
Simon Vieille 3540f42c2d
refactoring of AbListener 2022-05-21 00:15:37 +02:00
Simon Vieille d436f38668
refactoring of ab classes 2022-05-20 18:39:42 +02:00
Simon Vieille 280f7f01b1
refactoring of ab classes 2022-05-20 18:39:37 +02:00
Simon Vieille baf43e21f9
update changelog 2022-05-20 14:27:28 +02:00
Simon Vieille feb627478b
update ab test label and translations 2022-05-20 14:05:45 +02:00
Simon Vieille 3d6f6bb7b3
add abtest feature 2022-05-20 13:50:04 +02:00
Simon Vieille e91a395c34
update page form display on mobile 2022-05-18 15:43:13 +02:00
Simon Vieille 061a63d6a2
update redirect index view 2022-05-17 21:05:39 +02:00
Simon Vieille 41c441105d
update file manager display on mobile 2022-05-17 10:50:07 +02:00
Simon Vieille 65d3aab76e
update crud-header display on mobile 2022-05-17 09:39:49 +02:00
Simon Vieille 0f5571b33e
update crud-header display on mobile 2022-05-17 09:25:49 +02:00
Simon Vieille af98f7bfea
update crud-header display on mobile 2022-05-17 09:22:22 +02:00
Simon Vieille 79817a4e92
hide button label on mobile (crud) 2022-05-17 08:44:28 +02:00
Simon Vieille c1e7e3ddf0
replace is_image with file_type in form types 2022-05-17 00:06:24 +02:00
Simon Vieille cf33f4b5a3
fix form view on mobile device 2022-05-16 21:15:54 +02:00
Simon Vieille 4abb262e5c
update changelog 2022-05-16 14:48:16 +02:00
Simon Vieille 17974843d9
fix file block type 2022-05-16 14:47:35 +02:00
Simon Vieille a715f7e474
add class in modal 2022-05-14 10:52:00 +02:00
Simon Vieille 7eaf77cb9b
remove route base in dashboard controller 2022-05-14 10:51:36 +02:00
Simon Vieille 3e1b674985
update changelog 2022-05-13 22:27:38 +02:00
Simon Vieille 70a01a2be3
remove dashboard action from the core 2022-05-13 22:27:21 +02:00
Simon Vieille fda4ed5175
update base of mail template 2022-05-10 15:48:47 +02:00
Simon Vieille 815f578478
add modal-static class that keep the modal alive 2022-05-10 11:38:56 +02:00
Simon Vieille 53280aeccd
fix sitemap generator: inject current domain when it's possible 2022-05-10 09:42:09 +02:00
Simon Vieille 8e06dab2b3
update changelog 2022-05-09 14:50:23 +02:00
Simon Vieille a8def20853
add default field to show in crud configuration 2022-05-09 14:49:58 +02:00
Simon Vieille bd7a2544e8
update changelog 2022-05-09 14:32:51 +02:00
Simon Vieille 99337b341f
change default template to show an entity using entity_to_array 2022-05-09 14:31:17 +02:00
Simon Vieille ff599a7101
add entity_to_array twig function 2022-05-09 14:30:48 +02:00
Simon Vieille 6bf65ccd2b fix regression with editorjs: content not loaded 2022-05-08 22:26:13 +02:00
Simon Vieille 170e74b5b7
update page edition template 2022-05-08 19:34:53 +02:00
Simon Vieille c140f402e4
form's labels now are bold 2022-05-08 18:55:45 +02:00
Simon Vieille 1d5e0addec Merge branch 'feature/theme' into develop 2022-05-08 18:50:55 +02:00
Simon Vieille a6250f8f3a
form's labels now are bold 2022-05-08 18:50:51 +02:00
Simon Vieille 67a5890573
add background when a form is show 2022-05-08 18:38:55 +02:00
Simon Vieille e428ae5674
update changelog 2022-05-08 16:50:48 +02:00
Simon Vieille 0184693743
hide the backoffice site name when small resolution 2022-05-08 16:50:45 +02:00
Simon Vieille bb7acbd6de Merge branch 'feature/sitemap' into develop 2022-05-08 16:44:28 +02:00
Simon Vieille 06805b0293
PSR 2 compliance 2022-05-08 16:44:24 +02:00
Simon Vieille edc9c25d70
fix sitemap: navigation with several domains 2022-05-08 16:43:47 +02:00
Simon Vieille e3f6793ce6
update base of mail template 2022-05-07 17:02:30 +02:00
Simon Vieille 82d7d98d65
update base of mail template 2022-05-07 16:59:13 +02:00
Simon Vieille f9f8185280
update mail/account/resetting_request.html.twig 2022-05-07 16:48:00 +02:00
Simon Vieille c39fee15ed
update base of mail template 2022-05-07 16:47:47 +02:00
Simon Vieille df106c70c9
change version 2022-05-07 16:24:11 +02:00
Simon Vieille 1b6b3ab0c6
fix the mail notifier 2022-05-07 16:24:04 +02:00
Simon Vieille f77c4691b9
apply linter 2022-05-05 16:44:05 +02:00
Simon Vieille 75e3cf41f6
disable enter key submission on grapesjs editor 2022-05-05 16:22:27 +02:00
Simon Vieille 7fa7d10b01
update changelog 2022-05-05 14:40:58 +02:00
Simon Vieille bb7dfc2f25
add grapesjs-component-code-editor and grapesjs-parser-postcss 2022-05-05 14:39:26 +02:00
Simon Vieille a2e7466e12
add grapesjs-plugin-header 2022-05-01 18:23:04 +02:00
Simon Vieille 2249374547
update changelog 2022-04-30 14:40:44 +02:00
Simon Vieille 443ccabd0b
CrudConfiguration::setAction can receive a callable instead of a boolean in 'enabled' param 2022-04-30 14:31:37 +02:00
Simon Vieille 82f4f63fe4
release v1.14.1 2022-04-30 12:32:21 +02:00
Simon Vieille ec885a1f77
update page maker 2022-04-25 21:41:54 +02:00
Simon Vieille b58c370e07
update page maker 2022-04-25 21:34:38 +02:00
Simon Vieille a0d37e54c3
improve sidebar in mobile view 2022-04-25 18:05:29 +02:00
Simon Vieille dd53e60eff
change method name 2022-04-25 16:32:05 +02:00
Simon Vieille 734a51891d
fix editorjs error when the textarea is empty 2022-04-25 14:43:33 +02:00
Simon Vieille f3d5b0380d
update changelog 2022-04-25 09:20:21 +02:00
Simon Vieille 581b8b7162
add allowed chars in RouteParameterSlugify and CodeSlugify 2022-04-25 09:20:17 +02:00
Simon Vieille 9d7f140923
fix creation of new element when a menu is edited 2022-04-25 08:58:07 +02:00
Simon Vieille 8869175340 Merge branch 'feature/policies' into develop 2022-04-22 15:28:29 +02:00
Simon Vieille d74405237e
fix security policy 2022-04-22 15:27:08 +02:00
Simon Vieille 66c78c2c8e
add security policy 2022-04-22 15:24:39 +02:00
Simon Vieille 01d1ebd8d1
change murph version 2022-04-20 14:47:07 +02:00
Simon Vieille 7b3363d5a1
update changelog 2022-04-20 13:53:51 +02:00
Simon Vieille bff1728067
replace flag-icon-css with flag-icons 2022-04-20 13:53:21 +02:00
Simon Vieille 7811084c50
update changelog 2022-04-19 15:21:03 +02:00
Simon Vieille 12a63601fc
add page template when a page is made with the maker 2022-04-19 15:19:51 +02:00
Simon Vieille 194c43e19b
update changelog 2022-04-19 15:05:01 +02:00
Simon Vieille 7249c13a95
add editor types in page maker 2022-04-19 15:04:09 +02:00
Simon Vieille c5db18e05e
add tinymce block type 2022-04-19 11:23:38 +02:00
Simon Vieille a12efc5e23
update changelog 2022-04-18 19:23:06 +02:00
Simon Vieille 90629a199c
add grapesjs modes 2022-04-18 19:22:09 +02:00
Simon Vieille 5ec7624019
update version 2022-04-17 18:36:32 +02:00
Simon Vieille 4dc6166b07
update changelog 2022-04-17 18:29:39 +02:00
Simon Vieille 17c68e53c2
update attributes in murph_collection_widget template 2022-04-15 15:44:50 +02:00
Simon Vieille 6bfbc1beca
remove dump 2022-04-15 15:36:43 +02:00
Simon Vieille c916d68412
update attributes in murph_collection_widget template 2022-04-15 15:35:23 +02:00
Simon Vieille f4b8705efc
add widget_attributes in murph_collection_widget template 2022-04-15 15:14:23 +02:00
Simon Vieille 2c001ac3e8
update changelog 2022-04-14 22:43:29 +02:00
Simon Vieille 05d3949236
add EditorJsTextareaBlockType 2022-04-14 22:42:26 +02:00
Simon Vieille c7f9fc94b0
remove the tag <body> from grapesjs html output (in twig filter) 2022-04-13 12:41:57 +02:00
Simon Vieille 3399b255b1
remove comments 2022-04-13 12:41:14 +02:00
Simon Vieille 24b41bf5c7
update changelog 2022-04-12 23:49:53 +02:00
Simon Vieille f48041eacd Merge branch 'feature/grapejs' into develop 2022-04-12 22:42:18 +02:00
Simon Vieille c6bafeda19
add grapesjs editor 2022-04-12 22:41:27 +02:00
Simon Vieille 7052d8ccc1
add button to show and hide metas (admin) 2022-04-12 22:26:22 +02:00
Simon Vieille aa7a280ce0
add button to show and hide metas (admin) 2022-04-12 22:26:07 +02:00
Simon Vieille ff1c4204fb update editorjs quote block template 2022-04-01 17:25:42 +02:00
Simon Vieille a447aa6da8 fix editorjs inline tools bold and italic 2022-03-30 14:19:41 +02:00
Simon Vieille 3198850d80 fix editorjs paragraph view with raw filter 2022-03-30 11:08:58 +02:00
Simon Vieille 8c118e9ff2 add editorjs hyperlink block 2022-03-30 11:08:27 +02:00
Simon Vieille 3ea8b20123 change murph version 2022-03-26 16:09:02 +01:00
Simon Vieille 110073fea5 update changelog and upgrade doc 2022-03-26 16:08:34 +01:00
Simon Vieille 0db44d9043 update changelog and upgrade doc 2022-03-26 16:08:10 +01:00
Simon Vieille dfbe32f916 add fusonic/opengraph dependency 2022-03-26 13:56:08 +01:00
Simon Vieille 354f3399bf update changelog 2022-03-26 13:53:51 +01:00
Simon Vieille 181f1b6c8c update editorjs image block view and add link endpoint 2022-03-26 13:53:32 +01:00
Simon Vieille 4e6125c66d add CrudConfiguration::getViewData in complement of CrudConfiguration::getViewDatas 2022-03-25 12:28:30 +01:00
Simon Vieille 995d76c711 remove useless 'use' 2022-03-25 11:49:38 +01:00
Simon Vieille 89efccd90f update changelog 2022-03-24 16:21:54 +01:00
Simon Vieille 51fef83889 add page maker 2022-03-24 16:20:42 +01:00
Simon Vieille d9b144732b update changelog 2022-03-23 13:39:54 +01:00
Simon Vieille de6b885bb3 fix issue with empty user-agent in AnalyticListener 2022-03-23 13:39:29 +01:00
Simon Vieille d8a15a6d73 update version and changelog 2022-03-22 15:10:33 +01:00
Simon Vieille af68d3d269 add specific form types for Tinymce and EditorJS 2022-03-22 14:58:23 +01:00
Simon Vieille 2ad1d86b76 Merge branch 'feature/editorjs' into develop 2022-03-22 11:06:19 +01:00
Simon Vieille 9d72d2c0c0 add editorjs editor 2022-03-22 11:00:58 +01:00
Simon Vieille d396eafde6 rename editor.js with tinymce.js 2022-03-22 11:00:18 +01:00
Simon Vieille 371aa1bf03 remove double click attribute in settings 2022-03-18 17:18:49 +01:00
Simon Vieille 717136ea43 update changelog 2022-03-18 17:01:39 +01:00
Simon Vieille b686fd5285 add blur when several modals are opened 2022-03-18 16:56:30 +01:00
Simon Vieille 2655088ee6 update changelog 2022-03-18 14:52:41 +01:00
Simon Vieille ca54447f7e update file-manager with data-modal-create attribute 2022-03-18 14:51:33 +01:00
Simon Vieille d4533e780f add data-modal-create to force modal to be open in a new container 2022-03-18 14:51:16 +01:00
Simon Vieille 1ba3f9d721 update murph version 2022-03-17 10:58:01 +01:00
Simon Vieille 569832aee9 update upgrade doc 2022-03-17 10:52:44 +01:00
Simon Vieille 4c3aff4fa7 update changelog 2022-03-17 10:51:17 +01:00
Simon Vieille 37654704e0 update changelog 2022-03-16 12:49:29 +01:00
Simon Vieille 2c5ba11740 add url and path generator using code 2022-03-16 12:47:08 +01:00
Simon Vieille 8b834f567f use english in note sitemap parameters form 2022-03-16 08:44:26 +01:00
Simon Vieille ea0121b4f7 update node entity constraints 2022-03-14 15:44:53 +01:00
Simon Vieille 7970359071 update changelog 2022-03-14 10:40:42 +01:00
Simon Vieille b9f8a5cad5 update version 2022-03-14 10:39:00 +01:00
Simon Vieille aaacc4a23d fix issue with murph version constant and autoloader 2022-03-14 10:38:13 +01:00
Simon Vieille 6401b46de8 update changelog 2022-03-14 10:10:14 +01:00
Simon Vieille 3979feb80c remove AdminController constructor 2022-03-14 10:02:42 +01:00
Simon Vieille a713a4d263 add murph version in autoload file 2022-03-14 10:02:25 +01:00
Simon Vieille 32397751be update composer configuration 2022-03-13 20:44:05 +01:00
213 changed files with 4731 additions and 2006 deletions

View File

@ -1,8 +1,209 @@
## [Unreleased]
### Added
### Fixed
* fix default crud sort
* fix hidden save button in file manager
* fix template of CrudController (maker)
## [v1.24.1] - 2024-02-01
### Fixed
* update Murph version constant
## [v1.24.0] - 2024-01-27
### Added
* add CSS class `no-wrap`
* copy the pager of the CRUD at the bottom of the list
### Fixed
* fix an issue with the file manager when editing an item opened in a modal
* fix type casting in slugifier
## [v1.23.0] - 2023-11-01
### Added
* allow to define templates show before and after a murph collection item
* add global batch actions
* add constraint `Length` in forms
* add sass classes to mange with of elements
* set searchFields option on jschoice manager (search on labels)
### Changed
* refactor services using constructor property promotions
* remove twig in the mail notifier service
* change pills colors
* change border colors of inputs when focused
* change colors on js-choices element
### Fixed
* fix regression on crud sorting
* fix test in RepositoryQuery::addForcedFilterHandler
* remove parameter $option on CrudConfiguration::setForm and fix CrudController make template
* fix the aspect of the actions's column in the crud
## [v1.22.0] - 2023-09-28
### Added
* add new options in BooleanField: `toggle|checkbox_class_when_true` and `toggle|checkbox_class_when_false`
* add `count` method in repository query
* add `addForcedFilterHandler` method in repository query
* add `inline_form_validation` option to validate inline forms with custom algo
* add crud sorting parameters in the session
* add flush option in the entity manager on create, update, remove, and persist methods
## [1.21.1] - 2023-08-17
### Added
* add form error handle in inline edit action and refill the form using the previous request content
* add form error handle in ssettings actions and refill the form using the previous request content
### Fixed
* fix tinymce reload when modal is closed and reopened
* fix modal hiding when a file is successfuly uploaded in the file manager
## [1.21.0] - 2023-08-11
### Added
* allow to use array syntax in string builder filter
* add color property in Navigation
* add badge with navigation color in admin views
* add `default_value` option in crud fields
* add `display` option in BooleanField
* add associated nodes in page form
### Fixed
* fix routes in the global settings controller
## [1.20.0] - 2023-07-27
### Added
* enable double click on cruds
* add block class name for the choice type in the page maker
* update file details view on the file manager
* add form options in the crud filter action
* add trans filter in inline form modal title
* add setter to define all fields in a defined context
* add filename generator setter in FileUploadHandler
* add variable for the sidebar size
* add twig block to override defaults actions in crud index template
* add option to remove iterable values and/or specifics keys in the twig toArray function
* add boolean field for CRUD
* add context variable in each controllers to simplify overrides
* core.site.name and core.site.logo are not longer required
* add default templates when a crud is generated
* add boolean 'is_disabled' in the menu item template options
### Fixed
* fix filemanager date ordering
* fix maker CrudController template: remove bad pasted code
* fix redirect listener: use boolean instead of integer
* fix responsive of account edit template
* fix collection widget: allow_add/allow_delete and prototype
### Changed
* user admin routes are defined in core, custom controller is not required
## [1.19.0] - 2023-04-15
### Added
* feat(page): forms for metas, opengraph and extra informations can be removed
* feat(navigation): user interface is improved
* feat(file): webp is allowed and shown in form widgets and in file manager details
* feat(file): the file manager now show the size and the modification date of a file
* feat(crud): add option `action` in field to add a link to the view page or to the edition page
* feat(crud): add option `inline_form` in field to configure to edit the data
* feat(crud): add `setDoubleClick` in the crud configuration
## [1.18.0] - 2023-01-13
### Added
* feat(dep): add symfony/runtime
* feat(dep): add symfony/flex
### Fixed
* fix(crud): allow POST in delete actions
* fix(crud): remove default page value in abstract crud controller
* fix(admin): test site_logo before using it
* fix(ui): update z-index of choices__list--dropdown
## [1.17.1] - 2022-12-03
### Fixed
* add mising attribute on timestampable (doctrine)
## [1.17.0] - 2022-11-19
### Fixed
* fix tinymce modal z-index in tox
### Changed
* replace annotation with attributes
## [1.16.0] - 2022-09-06
### Added
* add A/B testing feature
* add cleanup of html string extracted from grapesjs content
### Fixed
* fix file block type
### Changed
* remove dashboard action from the core
## [1.15.0] - 2022-05-09
### Added
* CrudConfiguration::setAction can receive a callable instead of a boolean in 'enabled' param
* add grapesjs-component-code-editor and grapesjs-parser-postcss
* hide the backoffice site name when small resolution
* add entity_to_array twig function
* add default field to show in crud configuration
### Fixed
* fix the mail notifier
* fix sitemap: navigation with several domains
* fix regression with editorjs: content not loaded
### Changed
* change default template to show an entity using `entity_to_array`
## [1.14.1] - 2022-04-30
### Added
* add allowed chars in RouteParameterSlugify and CodeSlugify
* improve sidebar in mobile view
### Fixed
* fix creation of new element when a menu is edited
* fix editorjs error when the textarea is empty
## [1.14.0] - 2022-04-20
### Added
* add grapesjs modes
* add tinymce block type
* add editor types in page maker
* add the page template when the page is generated with the maker
### Changed
* replace flag-icon-css with flag-icons
## [1.13.0] - 2022-04-17
### Added
* add editorjs hyperlink block
* add button to show and hide metas (admin)
* add grapesjs editor
* add editorjs type
### Fixed
* fix editorjs inline tools (bold and italic)
### Changed
* update editorjs quote block template
## [1.12.0] - 2022-03-26
### Added
* add page maker command (`make:page`)
* add CrudConfiguration::getViewData in complement of CrudConfiguration::getViewDatas
* add editorjs link block endpoint
### Fixed
* fix issue with empty user-agent in AnalyticListener
### Changed
* update editorjs image block view
## [1.11.0] - 2022-03-22
### Added
* add data-modal-create attribute to force modal to be open in a new container
* add blur when several modals are opened
* add specific form types for Tinymce and EditorJS
### Changed
* update file-manager with data-modal-create attribute
## [1.10.0] - 2022-03-17
### Added
* add url and path generators using code (twig)
### Changed
* update node entity constraints
## [1.9.2] - 2022-03-14
### Fixed
* fix issue with murph version constant and autoloader
## [1.9.1] - 2022-03-14
### Added
* add murph version in autoload file
### Changed
* remove AdminController constructor
## [1.9.0] - 2022-03-13
### Added

12
SECURITY.md Normal file
View File

@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ---------- | --------- |
| >= 2.0 | :x: |
| >= 1.0 | :white_check_mark: |
## Reporting a Vulnerability
If you discover a security vulnerability within Murph, send an email to security [at] murph-project.org.

View File

@ -1,5 +1,23 @@
## General process
Upgrade dependencies:
* `composer update`
* `yarn upgrade`
Build:
* `make build`
## [Unreleased]
## Upgrade to v1.10.0
### Commands
```
make doctrine-migration
```
## Upgrade to v1.8.0
### Commands

View File

@ -1,7 +1,7 @@
{
"name": "murph/murph-core",
"description": "A powerful CMS framework",
"type": "project",
"type": "library",
"license": "MIT",
"minimum-stability": "stable",
"prefer-stable": true,
@ -16,6 +16,7 @@
"doctrine/doctrine-bundle": "^2.5",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.11",
"fusonic/opengraph": "^2.2",
"friendsofsymfony/jsrouting-bundle": "^2.8",
"jaybizzle/crawler-detect": "^1.2",
"knplabs/doctrine-behaviors": "^2.6",
@ -36,7 +37,7 @@
"symfony/event-dispatcher": "5.4.*",
"symfony/expression-language": "5.4.*",
"symfony/finder": "5.4.*",
"symfony/flex": "^1.3.1",
"symfony/flex": "^2.2",
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
@ -59,7 +60,8 @@
"symfony/webpack-encore-bundle": "^1.11",
"symfony/yaml": "5.4.*",
"twig/extra-bundle": "^2.12|^3.3",
"twig/twig": "^2.12|^3.3"
"twig/twig": "^2.12|^3.3",
"symfony/runtime": "^5.4"
},
"autoload": {
"psr-4": {

View File

@ -0,0 +1,30 @@
<?php
namespace App\Core\Ab;
/**
* class AbContainer.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbContainer implements AbContainerInterface
{
protected array $tests = [];
public function add(AbTestInterface $test): self
{
$this->tests[$test->getName()] = $test;
return $this;
}
public function has(string $name): bool
{
return isset($this->tests[$name]);
}
public function get(string $name): AbTestInterface
{
return $this->tests[$name];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Core\Ab;
/**
* interface AbContainerInterface.
*
* @author Simon Vieille <simon@deblan.fr>
*/
interface AbContainerInterface
{
public function add(AbTestInterface $test): self;
public function has(string $name): bool;
public function get(string $name): AbTestInterface;
}

123
src/core/Ab/AbTest.php Normal file
View File

@ -0,0 +1,123 @@
<?php
namespace App\Core\Ab;
/**
* class AbTest.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbTest implements AbTestInterface
{
protected $results;
protected array $variations = [];
protected array $probabilities = [];
protected int $duration = 3600 * 24;
public function __construct(protected string $name)
{
}
public function getName(): string
{
return $this->name;
}
public function getResult()
{
return $this->result;
}
public function setResult(string $result): self
{
$this->result = $result;
return $this;
}
public function isValidVariation($variation): bool
{
return array_key_exists($variation, $this->variations);
}
public function addVariation(string $name, $value, int $probability = null): self
{
$this->variations[$name] = $value;
$this->probabilities[$name] = $probability;
return $this;
}
public function getVariation($variation)
{
return $this->variations[$variation];
}
public function getResultValue()
{
return $this->getVariation($this->getResult());
}
public function setDuration(int $duration): self
{
$this->duration = $duration;
return $this;
}
public function getDuration(): int
{
return $this->duration;
}
public function run(): self
{
$this->result = $this->chance();
return $this;
}
protected function chance(): string
{
$sum = 0;
$empty = 0;
foreach ($this->probabilities as $name => $value) {
$sum += $value;
if (empty($value)) {
++$empty;
}
}
if ($sum > 100) {
throw new \LogicException('Test Error: Total variation probabilities is bigger than 100%');
}
if ($sum < 100) {
foreach ($this->probabilities as $name => $value) {
if (empty($value)) {
$this->probabilities[$name] = (100 - $sum) / $empty;
}
}
}
krsort($this->probabilities);
$number = mt_rand(0, (int) array_sum($this->probabilities) * 10);
$starter = 0;
$return = '';
foreach ($this->probabilities as $key => $val) {
$starter += $val * 10;
if ($number <= $starter) {
$return = $key;
break;
}
}
return $return;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Core\Ab;
/**
* interface AbTestInterface.
*
* @author Simon Vieille <simon@deblan.fr>
*/
interface AbTestInterface
{
public function getName(): string;
public function getResult();
public function setResult(string $result): self;
public function isValidVariation($variation): bool;
public function addVariation(string $name, $value, int $probability = null): self;
public function getVariation($variation);
public function getResultValue();
public function setDuration(int $duration): self;
public function getDuration(): int;
public function run(): self;
}

View File

@ -13,18 +13,16 @@ use App\Core\Repository\Analytic\ViewRepositoryQuery;
*/
class DateRangeAnalytic
{
protected ViewRepositoryQuery $viewQuery;
protected RefererRepositoryQuery $refererQuery;
protected ?Node $node;
protected ?\DateTime $from;
protected ?\DateTime $to;
protected bool $reload = true;
protected array $cache = [];
public function __construct(ViewRepositoryQuery $viewQuery, RefererRepositoryQuery $refererQuery)
{
$this->viewQuery = $viewQuery;
$this->refererQuery = $refererQuery;
public function __construct(
protected ViewRepositoryQuery $viewQuery,
protected RefererRepositoryQuery $refererQuery
) {
}
public function getViews(): array
@ -83,7 +81,7 @@ class DateRangeAnalytic
$datas[$index]['mobileViews'] += $entity->getMobileViews();
}
uasort($datas, function($a, $b) {
uasort($datas, function ($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}
@ -130,7 +128,7 @@ class DateRangeAnalytic
$datas[$index]['uris'][$path] += $entity->getViews();
}
uasort($datas, function($a, $b) {
uasort($datas, function ($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}

View File

@ -2,19 +2,18 @@
namespace App\Core\Annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* class UrlGenerator.
*
* @author Simon Vieille <simon@deblan.fr>
* @Annotation
*/
#[\Attribute]
class UrlGenerator
{
public string $service;
public string $method;
public array $options = [];
public function __construct(
public string $service,
public string $method,
public array $options = []
) {
}
}

View File

@ -23,20 +23,12 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private CsrfTokenManagerInterface $csrfTokenManager;
private UserPasswordEncoderInterface $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
public function __construct(
private EntityManagerInterface $entityManager,
private UrlGeneratorInterface $urlGenerator,
private CsrfTokenManagerInterface $csrfTokenManager,
private UserPasswordEncoderInterface $passwordEncoder
) {
}
public function supports(Request $request)

View File

@ -12,8 +12,8 @@
namespace App\Core\Bundle;
use App\Core\DependencyInjection\CoreExtension;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class CoreBundle extends Bundle
{

View File

@ -5,13 +5,13 @@ namespace App\Core\Cache;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* class SymfonyCacheManager.
@ -20,15 +20,11 @@ use Symfony\Component\HttpClient\Exception\TransportException;
*/
class SymfonyCacheManager
{
protected KernelInterface $kernel;
protected HttpClientInterface $httpClient;
protected UrlGeneratorInterface $urlGenerator;
public function __construct(KernelInterface $kernel, HttpClientInterface $httpClient, UrlGeneratorInterface $urlGenerator)
{
$this->kernel = $kernel;
$this->httpClient = $httpClient;
$this->urlGenerator = $urlGenerator;
public function __construct(
protected KernelInterface $kernel,
protected HttpClientInterface $httpClient,
protected UrlGeneratorInterface $urlGenerator
) {
}
public function cleanRouting()

View File

@ -18,19 +18,12 @@ class UserCreateCommand extends Command
{
protected static $defaultName = 'murph:user:create';
protected static $defaultDescription = 'Creates a user';
protected UserFactory $userFactory;
protected EntityManager $entityManager;
protected TokenGeneratorInterface $tokenGenerator;
public function __construct(
UserFactory $userFactory,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator
protected UserFactory $userFactory,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator
) {
$this->userFactory = $userFactory;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
parent::__construct();
}

View File

@ -14,14 +14,10 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
use ZxcvbnPhp\Zxcvbn;
/**
* @Route("/admin/account")
*/
#[Route(path: '/admin/account')]
class AccountAdminController extends AdminController
{
/**
* @Route("/", name="admin_account")
*/
#[Route(path: '/', name: 'admin_account')]
public function account(Request $request, TotpAuthenticatorInterface $totpAuthenticatorService): Response
{
$account = $this->getUser();
@ -31,9 +27,7 @@ class AccountAdminController extends AdminController
]);
}
/**
* @Route("/2fa", name="admin_account_2fa")
*/
#[Route(path: '/2fa', name: 'admin_account_2fa')]
public function twoFactorAuthentication(
Request $request,
GoogleAuthenticatorInterface $totpAuthenticatorService,
@ -93,9 +87,7 @@ class AccountAdminController extends AdminController
]);
}
/**
* @Route("/password", name="admin_account_password", methods={"POST"})
*/
#[Route(path: '/password', name: 'admin_account_password', methods: ['POST'])]
public function password(
Request $request,
UserRepository $repository,

View File

@ -2,23 +2,14 @@
namespace App\Core\Controller\Admin;
use App\Core\Murph;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
abstract class AdminController extends AbstractController
{
protected array $coreParameters;
public function __construct(ParameterBagInterface $parameters)
{
$this->coreParameters = $parameters->get('core');
}
/**
* @Route("/_ping", name="_ping")
*/
#[Route(path: '/_ping', name: '_ping')]
public function ping()
{
return $this->json(true);
@ -29,10 +20,17 @@ abstract class AdminController extends AbstractController
*/
protected function render(string $view, array $parameters = [], Response $response = null): Response
{
$parameters['section'] = $this->getSection();
$parameters['site_name'] = $this->coreParameters['site']['name'];
$parameters['site_logo'] = $this->coreParameters['site']['logo'];
$parameters['murph_version'] = defined('MURPH_VERSION') ? MURPH_VERSION : null;
$params = $this->getParameter('core')['site'];
$parameters = array_merge(
$parameters,
[
'section' => $this->getSection(),
'site_name' => $params['name'],
'site_logo' => $params['logo'],
'murph_version' => Murph::version(),
]
);
return parent::render($view, $parameters, $response);
}

View File

@ -26,7 +26,7 @@ abstract class CrudController extends AdminController
abstract protected function getConfiguration(): CrudConfiguration;
protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response
protected function doIndex(int $page, RepositoryQuery $query, Request $request, Session $session, string $context = 'index'): Response
{
$configuration = $this->getConfiguration();
@ -35,13 +35,14 @@ abstract class CrudController extends AdminController
$pager = $query
->usefilters($this->filters)
->paginate($page, $configuration->getmaxperpage('index'))
->paginate($page, $configuration->getMaxPerPage($context))
;
return $this->render($this->getConfiguration()->getView('index'), [
return $this->render($this->getConfiguration()->getView($context), [
'configuration' => $configuration,
'pager' => $pager,
'sort' => $this->sort,
'context' => $context,
'filters' => [
'show' => null !== $configuration->getForm('filter'),
'isEmpty' => empty($this->filters),
@ -49,13 +50,13 @@ abstract class CrudController extends AdminController
]);
}
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null): Response
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null, string $context = 'new'): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions('new'));
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions($context));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -76,30 +77,32 @@ abstract class CrudController extends AdminController
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView('new'), [
return $this->render($configuration->getView($context), [
'form' => $form->createView(),
'configuration' => $configuration,
'context' => $context,
'entity' => $entity,
]);
}
protected function doShow(EntityInterface $entity): Response
protected function doShow(EntityInterface $entity, string $context = 'show'): Response
{
$configuration = $this->getConfiguration();
return $this->render($configuration->getView('show'), [
return $this->render($configuration->getView($context), [
'entity' => $entity,
'context' => $context,
'configuration' => $configuration,
]);
}
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null, string $context = 'edit'): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions('edit'));
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions($context));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -112,22 +115,109 @@ abstract class CrudController extends AdminController
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
return $this->redirectToRoute($configuration->getPageRoute($context), array_merge(
['entity' => $entity->getId()],
$configuration->getPageRouteParams('edit')
$configuration->getPageRouteParams($context)
));
}
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView('edit'), [
return $this->render($configuration->getView($context), [
'form' => $form->createView(),
'context' => $context,
'configuration' => $configuration,
'entity' => $entity,
]);
}
protected function doSort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
protected function doInlineEdit(string $context, string $label, EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$builder = $this->createFormBuilder($entity);
$callback = $configuration->getFields($context)[$label]['options']['inline_form'] ?? null;
$validationCallback = $configuration->getFields($context)[$label]['options']['inline_form_validation'] ?? null;
if (null === $callback) {
throw $this->createNotFoundException();
}
call_user_func_array($callback, [$builder, $entity]);
$redirectTo = $request->query->get('redirectTo');
$form = $builder->getForm();
$session = $request->getSession();
$lastRequestId = sprintf(
'inline_request_%s_%s_%s_%s',
get_class($entity),
$entity->getId(),
$context,
$label
);
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
if (null !== $validationCallback) {
call_user_func_array($validationCallback, [$entity, $form, $request]);
}
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if (null !== $validationCallback) {
call_user_func_array($validationCallback, [$entity, $form, $request]);
}
if ($form->isValid()) {
if (null !== $beforeUpdate) {
call_user_func_array($beforeUpdate, [$entity, $form, $request]);
}
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirect($redirectTo);
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render($configuration->getView('inline_edit'), [
'form' => $form->createView(),
'configuration' => $configuration,
'entity' => $entity,
'context' => $context,
'label' => $label,
'redirectTo' => $redirectTo,
]);
}
protected function doSort(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$context = $request->query->get('context', 'index');
@ -165,7 +255,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doBatch(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
protected function doBatch(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$datas = $request->request->get('batch', []);
@ -194,16 +284,39 @@ abstract class CrudController extends AdminController
$query->useFilters($this->filters);
if ('selection' === $target) {
$isSelection = true;
$pager = $query->paginate($page, $configuration->getMaxPerPage($context));
} else {
$isSelection = false;
$pager = $query->find();
$useSelection = 'selection' === $target;
if ($batchAction['isGlobal']) {
$selection = null;
if ($useSelection) {
$queryClone = clone $query;
$pager = $queryClone->paginate($page, $configuration->getMaxPerPage($context));
$selection = [];
foreach ($pager as $key => $entity) {
if (isset($items[$key + 1])) {
$selection[] = $entity;
}
}
}
$result = $callback($query, $entityManager, $selection);
if ($result instanceof Response) {
return $result;
}
return $this->redirect($request->query->get('redirectTo'));
}
$pager = $useSelection
? $query->paginate($page, $configuration->getMaxPerPage($context))
: $query->find()
;
foreach ($pager as $key => $entity) {
if (($isSelection && isset($items[$key + 1])) || !$isSelection) {
if (($useSelection && isset($items[$key + 1])) || !$useSelection) {
$callback($entity, $entityManager);
}
}
@ -213,7 +326,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null): Response
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null, string $route = 'index'): Response
{
$configuration = $this->getConfiguration();
@ -227,10 +340,10 @@ abstract class CrudController extends AdminController
$this->addFlash('success', 'The data has been removed.');
}
return $this->redirectToRoute($configuration->getPageRoute('index'));
return $this->redirectToRoute($configuration->getPageRoute($route));
}
protected function doFilter(Session $session): Response
protected function doFilter(Session $session, string $context = 'filter'): Response
{
$configuration = $this->getConfiguration();
$type = $configuration->getForm('filter');
@ -239,11 +352,12 @@ abstract class CrudController extends AdminController
throw $this->createNotFoundException();
}
$form = $this->createForm($type);
$form = $this->createForm($type, null, $configuration->getFormOptions('filter'));
$form->submit($session->get($form->getName(), []));
return $this->render($configuration->getView('filter'), [
return $this->render($configuration->getView($context), [
'form' => $form->createView(),
'context' => $context,
'configuration' => $configuration,
]);
}
@ -257,7 +371,7 @@ abstract class CrudController extends AdminController
return;
}
$form = $this->createForm($type);
$form = $this->createForm($type, null, $configuration->getFormOptions('filter'));
if ($request->query->has($form->getName())) {
$filters = $request->query->get($form->getName());
@ -304,9 +418,27 @@ abstract class CrudController extends AdminController
}
$defaultSort = $configuration->getDefaultSort($context);
$session = $request->getSession();
$name = $request->query->get('_sort', $defaultSort['label'] ?? null);
$direction = strtolower($request->query->get('_sort_direction', $defaultSort['direction'] ?? 'asc'));
$sessionId = sprintf('%s_%s_sort', $context, get_called_class());
$sessionSortName = sprintf('%s_label', $sessionId);
$sessionSortDirection = sprintf('%s_direction', $sessionId);
$name = $request->query->get('_sort', $session->get($sessionSortName)) ?? $defaultSort['label'] ?? null;
$direction = strtolower(
$request->query->get(
'_sort_direction',
$session->get($sessionSortDirection)
) ?? $defaultSort['direction'] ?? 'asc'
);
$session->set($sessionSortName, $name);
$session->set($sessionSortDirection, $direction);
if (empty($name)) {
return;
}
if (!in_array($direction, ['asc', 'desc'])) {
$direction = 'asc';

View File

@ -8,14 +8,10 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/analytic")
*/
#[Route(path: '/admin/analytic')]
class AnalyticController extends AbstractController
{
/**
* @Route("/stats/{node}/{range}", name="admin_analytic_stats")
*/
#[Route(path: '/stats/{node}/{range}', name: 'admin_analytic_stats')]
public function stats(Node $node, DateRangeAnalytic $analytic, string $range = '7days'): Response
{
if (!in_array($range, ['7days', '30days', '90days', '1year'])) {

View File

@ -25,9 +25,7 @@ class AuthController extends AbstractController
$this->coreParameters = $parameters->get('core');
}
/**
* @Route("/login", name="auth_login")
*/
#[Route(path: '/login', name: 'auth_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
@ -45,9 +43,7 @@ class AuthController extends AbstractController
]);
}
/**
* @Route("/resetting/request", name="auth_resetting_request")
*/
#[Route(path: '/resetting/request', name: 'auth_resetting_request')]
public function requestResetting(Request $request, UserRepository $repository, EventDispatcherInterface $eventDispatcher): Response
{
if ($this->getUser()) {
@ -85,9 +81,7 @@ class AuthController extends AbstractController
]);
}
/**
* @Route("/resetting/update/{token}", name="auth_resetting_update")
*/
#[Route(path: '/resetting/update/{token}', name: 'auth_resetting_update')]
public function requestUpdate(
string $token,
Request $request,
@ -145,9 +139,7 @@ class AuthController extends AbstractController
]);
}
/**
* @Route("/logout", name="auth_logout")
*/
#[Route(path: '/logout', name: 'auth_logout')]
public function logout()
{
throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');

View File

@ -3,23 +3,9 @@
namespace App\Core\Controller\Dashboard;
use App\Core\Controller\Admin\AdminController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin")
*/
class DashboardAdminController extends AdminController
{
/**
* @Route("/", name="admin_dashboard_index")
*/
public function index(): Response
{
return $this->render('@Core/dashboard/index.html.twig', [
]);
}
protected function getSection(): string
{
return 'dashboard';

View File

@ -0,0 +1,48 @@
<?php
namespace App\Core\Controller\Editor;
use Fusonic\OpenGraph\Consumer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Route(path: '/admin/editor/editorjs')]
class EditorJsController extends AbstractController
{
#[Route(path: '/fetch_url', name: 'admin_editor_editorjs_fetch_url', options: ['expose' => true])]
public function fetchUrl(Request $request, HttpClientInterface $client): JsonResponse
{
$url = filter_var($request->query->get('url'), FILTER_VALIDATE_URL);
$datas = [];
if (!$url) {
$data['success'] = 0;
} else {
try {
$consumer = new Consumer();
$response = $client->request('GET', $url);
$openGraph = $consumer->loadHtml($response->getContent());
$data = [
'success' => 1,
'link' => $openGraph->url,
'meta' => [
'title' => $openGraph->title,
'description' => $openGraph->description,
],
];
if (isset($openGraph->images[0])) {
$data['meta']['image']['url'] = $openGraph->images[0]->url;
}
} catch (\Exception $e) {
$data['success'] = 0;
}
}
return $this->json($data);
}
}

View File

@ -15,22 +15,16 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Route("/admin/file_manager")
*/
#[Route(path: '/admin/file_manager')]
class FileManagerAdminController extends AdminController
{
/**
* @Route("/", name="admin_file_manager_index")
*/
#[Route(path: '/', name: 'admin_file_manager_index')]
public function index(): Response
{
return $this->render('@Core/file_manager/index.html.twig');
}
/**
* @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
*/
#[Route(path: '/api/directory', name: 'admin_file_manager_api_directory', options: ['expose' => true])]
public function directory(FsFileManager $manager, Request $request): Response
{
$options = [
@ -43,9 +37,7 @@ class FileManagerAdminController extends AdminController
return $this->json($files);
}
/**
* @Route("/info/{tab}/{context}/{ajax}", name="admin_file_manager_info", options={"expose"=true})
*/
#[Route(path: '/info/{tab}/{context}/{ajax}', name: 'admin_file_manager_info', options: ['expose' => true])]
public function info(
FsFileManager $manager,
Request $request,
@ -115,9 +107,7 @@ class FileManagerAdminController extends AdminController
]);
}
/**
* @Route("/directory/new/{ajax}", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
*/
#[Route(path: '/directory/new/{ajax}', name: 'admin_file_manager_directory_new', options: ['expose' => true], methods: ['GET', 'POST'])]
public function directoryNew(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -184,9 +174,7 @@ class FileManagerAdminController extends AdminController
]);
}
/**
* @Route("/directory/rename/{ajax}", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
*/
#[Route(path: '/directory/rename/{ajax}', name: 'admin_file_manager_directory_rename', methods: ['GET', 'POST'])]
public function directoryRename(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -255,9 +243,7 @@ class FileManagerAdminController extends AdminController
]);
}
/**
* @Route("/file/rename/{ajax}", name="admin_file_manager_file_rename", methods={"GET", "POST"})
*/
#[Route(path: '/file/rename/{ajax}', name: 'admin_file_manager_file_rename', methods: ['GET', 'POST'])]
public function fileRename(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -327,9 +313,7 @@ class FileManagerAdminController extends AdminController
]);
}
/**
* @Route("/upload/{ajax}", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
*/
#[Route(path: '/upload/{ajax}', name: 'admin_file_manager_upload', options: ['expose' => true], methods: ['GET', 'POST'])]
public function upload(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -406,9 +390,7 @@ class FileManagerAdminController extends AdminController
]);
}
/**
* @Route("/delete", name="admin_file_manager_delete", methods={"DELETE"})
*/
#[Route(path: '/delete', name: 'admin_file_manager_delete', methods: ['DELETE', 'POST'])]
public function delete(FsFileManager $manager, Request $request): Response
{
$path = $request->request->get('file');

View File

@ -19,65 +19,49 @@ use Symfony\Component\Routing\Annotation\Route;
class RedirectAdminController extends CrudController
{
/**
* @Route("/admin/redirect/{page}", name="admin_redirect_index", methods={"GET"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/redirect/{page}', name: 'admin_redirect_index', methods: ['GET'], requirements: ['page' => '\d+'])]
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/redirect/new", name="admin_redirect_new", methods={"GET", "POST"})
*/
#[Route(path: '/admin/redirect/new', name: 'admin_redirect_new', methods: ['GET', 'POST'])]
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create(), $entityManager, $request);
}
/**
* @Route("/admin/redirect/show/{entity}", name="admin_redirect_show", methods={"GET"})
*/
#[Route(path: '/admin/redirect/show/{entity}', name: 'admin_redirect_show', methods: ['GET'])]
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/redirect/filter", name="admin_redirect_filter", methods={"GET"})
*/
#[Route(path: '/admin/redirect/filter', name: 'admin_redirect_filter', methods: ['GET'])]
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/redirect/edit/{entity}", name="admin_redirect_edit", methods={"GET", "POST"})
*/
#[Route(path: '/admin/redirect/edit/{entity}', name: 'admin_redirect_edit', methods: ['GET', 'POST'])]
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/redirect/sort/{page}", name="admin_redirect_sort", methods={"POST"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/redirect/sort/{page}', name: 'admin_redirect_sort', methods: ['POST'], requirements: ['page' => '\d+'])]
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/redirect/batch/{page}", name="admin_redirect_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/redirect/batch/{page}', name: 'admin_redirect_batch', methods: ['POST'], requirements: ['page' => '\d+'])]
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doBatch($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/redirect/delete/{entity}", name="admin_redirect_delete", methods={"DELETE"})
*/
#[Route(path: '/admin/redirect/delete/{entity}', name: 'admin_redirect_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
@ -118,11 +102,11 @@ class RedirectAdminController extends CrudController
'attr' => ['class' => 'col-6'],
])
->setField('index', 'Enabled', Field\ButtonField::class, [
'property_builder' => function(EntityInterface $entity) {
'property_builder' => function (EntityInterface $entity) {
return $entity->getIsEnabled() ? 'Yes' : 'No';
},
'attr' => ['class' => 'col-1'],
'button_attr_builder' => function(EntityInterface $entity) {
'button_attr_builder' => function (EntityInterface $entity) {
return ['class' => 'btn btn-sm btn-'.($entity->getIsEnabled() ? 'success' : 'primary')];
},
])

View File

@ -11,14 +11,10 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/navigation_setting")
*/
#[Route(path: '/admin/navigation_setting')]
class NavigationSettingAdminController extends AdminController
{
/**
* @Route("/edit/{entity}", name="admin_navigation_setting_edit")
*/
#[Route(path: '/edit/{entity}', name: 'admin_navigation_setting_edit')]
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -35,11 +31,28 @@ class NavigationSettingAdminController extends AdminController
$eventDispatcher->dispatch($event, NavigationSettingEvent::FORM_INIT_EVENT);
$form = $builder->getForm();
$redirectTo = $request->query->get('redirectTo');
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entityManager->update($entity);
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
@ -48,19 +61,25 @@ class NavigationSettingAdminController extends AdminController
]);
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [
'form' => $form->createView(),
'entity' => $entity,
'options' => $event->getData()['options'],
'redirectTo' => $redirectTo,
]);
}
/**
* @Route("/delete/{entity}", name="admin_navigation_setting_delete", methods={"DELETE"})
*/
#[Route(path: '/delete/{entity}', name: 'admin_navigation_setting_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View File

@ -12,14 +12,10 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/setting")
*/
#[Route(path: '/admin/setting')]
class SettingAdminController extends AdminController
{
/**
* @Route("/{page}", name="admin_setting_index", requirements={"page": "\d+"})
*/
#[Route(path: '/{page}', name: 'admin_setting_index', requirements: ['page' => '\d+'])]
public function index(
RepositoryQuery $query,
EventDispatcherInterface $eventDispatcher,
@ -38,9 +34,7 @@ class SettingAdminController extends AdminController
]);
}
/**
* @Route("/edit/{entity}", name="admin_setting_edit")
*/
#[Route(path: '/edit/{entity}', name: 'admin_setting_edit')]
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -57,30 +51,53 @@ class SettingAdminController extends AdminController
$eventDispatcher->dispatch($event, SettingEvent::FORM_INIT_EVENT);
$form = $builder->getForm();
$redirectTo = $request->query->get('redirectTo');
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entityManager->update($entity);
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute('admin_setting_index');
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/setting_admin/edit.html.twig', [
'form' => $form->createView(),
'entity' => $entity,
'options' => $event->getData()['options'],
'redirectTo' => $redirectTo,
]);
}
/**
* @Route("/delete/{entity}", name="admin_setting_delete", methods={"DELETE"})
*/
#[Route(path: '/delete/{entity}', name: 'admin_setting_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View File

@ -12,14 +12,10 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/site/menu")
*/
#[Route(path: '/admin/site/menu')]
class MenuAdminController extends AdminController
{
/**
* @Route("/new/{navigation}", name="admin_site_menu_new", methods={"POST"})
*/
#[Route(path: '/new/{navigation}', name: 'admin_site_menu_new', methods: ['POST'])]
public function new(Navigation $navigation, EntityFactory $factory, EntityManager $entityManager, Request $request): Response
{
$entity = $factory->create($navigation);
@ -39,9 +35,7 @@ class MenuAdminController extends AdminController
]);
}
/**
* @Route("/edit/{entity}", name="admin_site_menu_edit", methods={"POST"})
*/
#[Route(path: '/edit/{entity}', name: 'admin_site_menu_edit', methods: ['POST'])]
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
$form = $this->createForm(EntityType::class, $entity);
@ -59,9 +53,7 @@ class MenuAdminController extends AdminController
]);
}
/**
* @Route("/delete/{entity}", name="admin_site_menu_delete", methods={"DELETE"})
*/
#[Route(path: '/delete/{entity}', name: 'admin_site_menu_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View File

@ -20,25 +20,19 @@ use Symfony\Component\Routing\Annotation\Route;
class NavigationAdminController extends CrudController
{
/**
* @Route("/admin/site/navigation/{page}", name="admin_site_navigation_index", methods={"GET"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/site/navigation/{page}', name: 'admin_site_navigation_index', methods: ['GET'], requirements: ['page' => '\d+'])]
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/site/navigation/new", name="admin_site_navigation_new", methods={"GET", "POST"})
*/
#[Route(path: '/admin/site/navigation/new', name: 'admin_site_navigation_new', methods: ['GET', 'POST'])]
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create(), $entityManager, $request);
}
/**
* @Route("/admin/site/navigation/show/{entity}", name="admin_site_navigation_show", methods={"GET"})
*/
#[Route(path: '/admin/site/navigation/show/{entity}', name: 'admin_site_navigation_show', methods: ['GET'])]
public function show(
Entity $entity,
EventDispatcherInterface $eventDispatcher,
@ -60,33 +54,25 @@ class NavigationAdminController extends CrudController
return $this->doShow($entity);
}
/**
* @Route("/admin/site/navigation/filter", name="admin_site_navigation_filter", methods={"GET"})
*/
#[Route(path: '/admin/site/navigation/filter', name: 'admin_site_navigation_filter', methods: ['GET'])]
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/site/navigation/edit/{entity}", name="admin_site_navigation_edit", methods={"GET", "POST"})
*/
#[Route(path: '/admin/site/navigation/edit/{entity}', name: 'admin_site_navigation_edit', methods: ['GET', 'POST'])]
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/site/navigation/sort/{page}", name="admin_site_navigation_sort", methods={"POST"}, requirements={"page":"\d+"})
*/
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1, ): Response
#[Route(path: '/admin/site/navigation/sort/{page}', name: 'admin_site_navigation_sort', methods: ['POST'], requirements: ['page' => '\d+'])]
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
/**
* @Route("/admin/site/navigation/delete/{entity}", name="admin_site_navigation_delete", methods={"DELETE"})
*/
#[Route(path: '/admin/site/navigation/delete/{entity}', name: 'admin_site_navigation_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
@ -118,9 +104,11 @@ class NavigationAdminController extends CrudController
->setView('form', '@Core/site/navigation_admin/_form.html.twig')
->setIsSortableCollection('index', true)
->setDoubleClick('index', true)
->setField('index', 'Label', Field\TextField::class, [
'property' => 'label',
'view' => '@Core/site/navigation_admin/field/label.html.twig',
'attr' => ['class' => 'miw-200'],
])
->setField('index', 'Domain', Field\ButtonField::class, [

View File

@ -2,7 +2,6 @@
namespace App\Core\Controller\Site;
use App\Core\Controller\Admin\AdminController;
use App\Core\Entity\Site\Node;
use App\Core\Entity\Site\Node as Entity;
use App\Core\Entity\Site\Page\Page;
@ -14,24 +13,20 @@ use App\Core\Form\Site\NodeType as EntityType;
use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\NodeRepository;
use App\Core\Site\ControllerLocator;
use App\Core\Site\RoleLocator;
use App\Core\Site\PageLocator;
use App\Core\Site\RoleLocator;
use App\Core\Sitemap\SitemapBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* @Route("/admin/site/node")
*/
#[Route(path: '/admin/site/node')]
class NodeAdminController extends AbstractController
{
/**
* @Route("/new/{node}", name="admin_site_node_new")
*/
#[Route(path: '/new/{node}', name: 'admin_site_node_new')]
public function new(
Node $node,
EntityFactory $factory,
@ -103,9 +98,7 @@ class NodeAdminController extends AbstractController
]);
}
/**
* @Route("/edit/{entity}/{tab}", name="admin_site_node_edit")
*/
#[Route(path: '/edit/{entity}/{tab}', name: 'admin_site_node_edit')]
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -151,7 +144,7 @@ class NodeAdminController extends AbstractController
$page = $entity->getPage();
if ($page !== null) {
if (null !== $page) {
$pageConfiguration = $pageLocator->getPages()[get_class($page)] ?? null;
} else {
$pageConfiguration = null;
@ -166,9 +159,7 @@ class NodeAdminController extends AbstractController
]);
}
/**
* @Route("/urls/{entity}", name="admin_site_node_urls")
*/
#[Route(path: '/urls/{entity}', name: 'admin_site_node_urls')]
public function urls(Entity $entity, SitemapBuilder $builder): Response
{
return $this->render('@Core/site/node_admin/urls.html.twig', [
@ -177,9 +168,7 @@ class NodeAdminController extends AbstractController
]);
}
/**
* @Route("/move/{entity}", name="admin_site_node_move")
*/
#[Route(path: '/move/{entity}', name: 'admin_site_node_move')]
public function move(
Entity $entity,
EntityManager $entityManager,
@ -233,9 +222,7 @@ class NodeAdminController extends AbstractController
]);
}
/**
* @Route("/toggle/visibility/{entity}", name="admin_site_node_toggle_visibility", methods={"POST"})
*/
#[Route(path: '/toggle/visibility/{entity}', name: 'admin_site_node_toggle_visibility', methods: ['POST'])]
public function toggleVisibility(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('toggle_visibility'.$entity->getId(), $request->request->get('_token'))) {
@ -251,9 +238,7 @@ class NodeAdminController extends AbstractController
]).sprintf('#node-%d', $entity->getId()));
}
/**
* @Route("/delete/{entity}", name="admin_site_node_delete", methods={"DELETE"})
*/
#[Route(path: '/delete/{entity}', name: 'admin_site_node_delete', methods: ['DELETE', 'POST'])]
public function delete(
Entity $entity,
NodeRepository $nodeRepository,

View File

@ -5,49 +5,41 @@ namespace App\Core\Controller\Site;
use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Page\Page as Entity;
use App\Core\Event\Page\PageEditEvent;
use App\Core\Form\Site\Page\Filter\PageFilterType as FilterType;
use App\Core\Form\Site\Page\PageType as Type;
use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\Page\PageRepositoryQuery as RepositoryQuery;
use App\Core\Site\PageLocator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
use App\Core\Event\Page\PageEditEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use App\Core\Entity\EntityInterface;
class PageAdminController extends CrudController
{
/**
* @Route("/admin/site/page/{page}", name="admin_site_page_index", methods={"GET"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/site/page/{page}', name: 'admin_site_page_index', methods: ['GET'], requirements: ['page' => '\d+'])]
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/site/page/show/{entity}", name="admin_site_page_show", methods={"GET"})
*/
#[Route(path: '/admin/site/page/show/{entity}', name: 'admin_site_page_show', methods: ['GET'])]
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/site/page/filter", name="admin_site_page_filter", methods={"GET"})
*/
#[Route(path: '/admin/site/page/filter', name: 'admin_site_page_filter', methods: ['GET'])]
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/site/page/edit/{entity}", name="admin_site_page_edit", methods={"GET", "POST"})
*/
#[Route(path: '/admin/site/page/edit/{entity}', name: 'admin_site_page_edit', methods: ['GET', 'POST'])]
public function edit(
int $entity,
EntityManager $entityManager,
@ -69,17 +61,13 @@ class PageAdminController extends CrudController
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/site/page/delete/{entity}", name="admin_site_page_delete", methods={"DELETE"})
*/
#[Route(path: '/admin/site/page/delete/{entity}', name: 'admin_site_page_delete', methods: ['DELETE', 'POST'])]
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
/**
* @Route("/admin/site/page/batch/{page}", name="admin_site_page_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
#[Route(path: '/admin/site/page/batch/{page}', name: 'admin_site_page_batch', methods: ['POST'], requirements: ['page' => '\d+'])]
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doBatch($page, $query, $entityManager, $request, $session);
@ -101,11 +89,14 @@ class PageAdminController extends CrudController
->setForm('edit', Type::class, [])
->setForm('filter', FilterType::class)
->setView('form', '@Core/site/page_admin/_form.html.twig')
->setView('edit', '@Core/site/page_admin/edit.html.twig')
->setAction('index', 'new', false)
->setAction('index', 'show', false)
->setAction('edit', 'show', false)
->setDoubleClick('index', true)
->setField('index', 'Name', Field\TextField::class, [
'property' => 'name',
'sort' => ['name', '.name'],
@ -123,7 +114,7 @@ class PageAdminController extends CrudController
}],
'attr' => ['class' => 'col-6'],
])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity);
})
;

View File

@ -11,22 +11,21 @@ use Symfony\Component\Routing\Annotation\Route;
class SitemapController extends AbstractController
{
/**
* @Route("/sitemap.xml", name="sitemap")
*/
public function sitemap(Request $request, NavigationRepositoryQuery $navigationRepositoryQuery, SitemapBuilder $builder): Response
#[Route(path: '/sitemap.xml', name: 'sitemap')]
public function sitemap(Request $request, NavigationRepositoryQuery $query, SitemapBuilder $builder): Response
{
$navigations = $navigationRepositoryQuery
->whereDomain($request->getHost())
->find()
;
$navigations = $query->create()->find();
$items = [];
foreach ($navigations as $navigation) {
if (!$navigation->matchDomain($request->getHost())) {
continue;
}
$items = array_merge(
$items,
$builder->build($navigation)
$builder->build($navigation, $request->getHost())
);
}

View File

@ -11,14 +11,10 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/site/tree")
*/
#[Route(path: '/admin/site/tree')]
class TreeAdminController extends AdminController
{
/**
* @Route("/", name="admin_site_tree_index")
*/
#[Route(path: '/', name: 'admin_site_tree_index')]
public function index(NavigationRepositoryQuery $navigationQuery, Session $session): Response
{
$navigation = null;
@ -48,9 +44,7 @@ class TreeAdminController extends AdminController
]);
}
/**
* @Route("/navigation/{navigation}", name="admin_site_tree_navigation")
*/
#[Route(path: '/navigation/{navigation}', name: 'admin_site_tree_navigation')]
public function navigation(
Navigation $navigation,
NavigationRepositoryQuery $navigationQuery,

View File

@ -13,14 +13,10 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/task")
*/
#[Route(path: '/admin/task')]
class TaskAdminController extends AdminController
{
/**
* @Route("/", name="admin_task_index")
*/
#[Route(path: '/', name: 'admin_task_index')]
public function index(EventDispatcherInterface $eventDispatcher): Response
{
$event = new TaskInitEvent();
@ -31,9 +27,7 @@ class TaskAdminController extends AdminController
]);
}
/**
* @Route("/run/{task}", name="admin_task_run", methods={"GET"})
*/
#[Route(path: '/run/{task}', name: 'admin_task_run', methods: ['GET'])]
public function run(
string $task,
Request $request,

View File

@ -8,69 +8,55 @@ use App\Core\Crud\Field;
use App\Core\Event\Account\PasswordRequestEvent;
use App\Core\Factory\UserFactory as Factory;
use App\Core\Manager\EntityManager;
use App\Core\Security\TokenGenerator;
use App\Entity\User as Entity;
use App\Form\UserType as Type;
use App\Repository\UserRepositoryQuery as RepositoryQuery;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
use App\Core\Security\TokenGenerator;
class UserAdminController extends CrudController
{
/**
* @Route("/admin/user/{page}", name="admin_user_index", methods={"GET"}, requirements={"page":"\d+"})
*/
protected ?CrudConfiguration $configuration = null;
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/user/new", name="admin_user_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request, TokenGenerator $tokenGenerator): Response
{
return $this->doNew($factory->create(null, $tokenGenerator->generateToken()), $entityManager, $request);
}
/**
* @Route("/admin/user/show/{entity}", name="admin_user_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/user/filter", name="admin_user_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/user/edit/{entity}", name="admin_user_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/user/delete/{entity}", name="admin_user_delete", methods={"DELETE"})
*/
public function inlineEdit(string $context, string $label, Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doInlineEdit($context, $label, $entity, $entityManager, $request);
}
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
/**
* @Route("/admin/user/resetting_request/{entity}", name="admin_user_resetting_request", methods={"POST"})
*/
public function requestResetting(Entity $entity, EventDispatcherInterface $eventDispatcher, Request $request): Response
{
if ($this->isCsrfTokenValid('resetting_request'.$entity->getId(), $request->request->get('_token'))) {
@ -95,6 +81,7 @@ class UserAdminController extends CrudController
->setPageRoute('index', 'admin_user_index')
->setPageRoute('new', 'admin_user_new')
->setPageRoute('edit', 'admin_user_edit')
->setPageRoute('inline_edit', 'admin_user_inline_edit')
->setPageRoute('show', 'admin_user_show')
->setPageRoute('delete', 'admin_user_delete')
->setPageRoute('filter', 'admin_user_filter')
@ -110,6 +97,7 @@ class UserAdminController extends CrudController
->setView('edit', '@Core/user/user_admin/edit.html.twig')
->setDefaultSort('index', 'username')
->setDoubleClick('index', true)
->setField('index', 'E-mail', Field\TextField::class, [
'property' => 'email',
@ -120,6 +108,9 @@ class UserAdminController extends CrudController
'property' => 'displayName',
'sort' => ['displayName', '.displayName'],
'attr' => ['class' => 'miw-200'],
'inline_form' => function (FormBuilderInterface $builder) {
$builder->add('displayName', null);
},
])
;
}

View File

@ -2,8 +2,6 @@
namespace App\Core\Crud;
use App\Core\Crud\Exception\CrudConfigurationException;
/**
* class CrudConfiguration.
*
@ -19,6 +17,7 @@ class CrudConfiguration
protected array $actionTitles = [];
protected array $forms = [];
protected array $formOptions = [];
protected array $inlineForms = [];
protected array $views = [];
protected array $viewDatas = [];
protected array $fields = [];
@ -41,7 +40,7 @@ class CrudConfiguration
return self::$self;
}
/* -- */
// --
public function setPageTitle(string $page, string $title): self
{
@ -55,7 +54,7 @@ class CrudConfiguration
return $this->pageTitles[$page] ?? $default;
}
/* -- */
// --
public function setPageRoute(string $page, string $route): self
{
@ -81,9 +80,9 @@ class CrudConfiguration
return $this->pageRouteParams[$page] ?? [];
}
/* -- */
// --
public function setForm(string $context, string $form, array $options = []): self
public function setForm(string $context, string $form): self
{
$this->forms[$context] = $form;
@ -107,9 +106,9 @@ class CrudConfiguration
return $this->formOptions[$context] ?? [];
}
/* -- */
// --
public function setAction(string $page, string $action, bool $enabled): self
public function setAction(string $page, string $action, bool|callable $enabled): self
{
if (!isset($this->actions[$page])) {
$this->actions[$page] = [];
@ -120,13 +119,40 @@ class CrudConfiguration
return $this;
}
public function getAction(string $page, string $action, bool $default = true)
public function getAction(string $page, string $action, bool $default = true, array $callableParamaters = [])
{
return $this->actions[$page][$action] ?? $default;
if (!isset($this->actions[$page][$action])) {
return $default;
}
if (is_bool($this->actions[$page][$action])) {
return $this->actions[$page][$action];
}
return call_user_func_array(
$this->actions[$page][$action],
$callableParamaters
);
}
public function setBatchAction(string $page, string $action, string $label, callable $callback): self
{
public function setGlobalBatchAction(
string $page,
string $action,
string $label,
callable $callback
): self {
$this->setBatchAction($page, $action, $label, $callback);
$this->batchActions[$page][$action]['isGlobal'] = true;
return $this;
}
public function setBatchAction(
string $page,
string $action,
string $label,
callable $callback
): self {
if (!isset($this->batchActions[$page])) {
$this->batchActions[$page] = [];
}
@ -134,6 +160,7 @@ class CrudConfiguration
$this->batchActions[$page][$action] = [
'label' => $label,
'callback' => $callback,
'isGlobal' => false,
];
return $this;
@ -154,7 +181,7 @@ class CrudConfiguration
return !empty($this->batchActions[$page]);
}
/* -- */
// --
public function setActionTitle(string $page, string $action, string $title): self
{
@ -172,7 +199,7 @@ class CrudConfiguration
return $this->actionTitles[$page][$action] ?? $default;
}
/* -- */
// --
public function setView(string $context, string $view): self
{
@ -215,7 +242,12 @@ class CrudConfiguration
return $this->viewDatas[$context] ?? [];
}
/* -- */
public function getViewData(string $context, string $name, $defaultValue = null)
{
return $this->viewDatas[$context][$name] ?? $defaultValue;
}
// --
public function setField(string $context, string $label, string $field, array $options): self
{
@ -236,9 +268,16 @@ class CrudConfiguration
return $this->fields[$context] ?? [];
}
/* -- */
public function setFields(string $context, array $fields): self
{
$this->fields[$context] = $fields;
public function setMaxPerPage(string $page, int $max)
return $this;
}
// --
public function setMaxPerPage(string $page, int $max): self
{
$this->maxPerPage[$page] = $max;
@ -250,7 +289,21 @@ class CrudConfiguration
return $this->maxPerPage[$page] ?? $default;
}
/* -- */
// --
public function setDoubleClick(string $page, bool $enabled): self
{
$this->doubleClick[$page] = $enabled;
return $this;
}
public function getDoubleClick(string $page): bool
{
return $this->doubleClick[$page] ?? false;
}
// --
public function setI18n(array $locales, string $defaultLocale): self
{
@ -275,7 +328,7 @@ class CrudConfiguration
return !empty($this->locales);
}
/* -- */
// --
public function setDefaultSort(string $context, string $label, string $direction = 'asc'): self
{

View File

@ -0,0 +1,32 @@
<?php
namespace App\Core\Crud\Field;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* class BooleanField.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class BooleanField extends Field
{
public function configureOptions(OptionsResolver $resolver): OptionsResolver
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'view' => '@Core/admin/crud/field/boolean.html.twig',
'display' => 'toggle',
'checkbox_class_when_true' => 'fa-check-square',
'checkbox_class_when_false' => 'fa-square',
'toggle_class_when_true' => 'bg-success',
'toggle_class_when_false' => 'bg-secondary',
'default_value' => false,
]);
$resolver->setAllowedTypes('display', 'string');
return $resolver;
}
}

View File

@ -28,24 +28,31 @@ abstract class Field
$resolver->setDefaults([
'property' => null,
'property_builder' => null,
'default_value' => null,
'view' => null,
'action' => null,
'raw' => false,
'sort' => null,
'href' => null,
'href_attr' => [],
'attr' => [],
'inline_form' => null,
'inline_form_validation' => null,
]);
$resolver->setRequired('view');
$resolver->setAllowedTypes('property', ['null', 'string']);
$resolver->setAllowedTypes('view', 'string');
$resolver->setAllowedTypes('action', ['null', 'string']);
$resolver->setAllowedTypes('attr', 'array');
$resolver->setAllowedTypes('href', ['null', 'string', 'callable']);
$resolver->setAllowedTypes('href_attr', 'array', 'callable');
$resolver->setAllowedTypes('inline_form', ['null', 'callable']);
$resolver->setAllowedTypes('inline_form_validation', ['null', 'callable']);
$resolver->setAllowedTypes('href_attr', ['array', 'callable']);
$resolver->setAllowedTypes('raw', 'boolean');
$resolver->setAllowedTypes('property_builder', ['null', 'callable']);
$resolver->setAllowedValues('sort', function($value) {
if ($value === null) {
$resolver->setAllowedValues('sort', function ($value) {
if (null === $value) {
return true;
}

View File

@ -15,6 +15,7 @@ class Configuration implements ConfigurationInterface
'image/jpeg',
'image/gif',
'image/svg+xml',
'image/webp',
'video/mp4',
'audio/mpeg3',
'audio/x-mpeg-3',
@ -45,93 +46,100 @@ class Configuration implements ConfigurationInterface
$treeBuilder->getRootNode()
->children()
->arrayNode('site')
->children()
->scalarNode('name')
->defaultValue('Murph')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('logo')
->defaultValue('build/images/core/logo.svg')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('controllers')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('action')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->arrayNode('security')
->children()
->arrayNode('roles')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('role')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('pages')
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('templates')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('file')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('file_manager')
->children()
->arrayNode('mimes')
->scalarPrototype()
->end()
->defaultValue($defaultMimetypes)
->end()
->scalarNode('path')
->defaultValue('%kernel.project_dir%/public/uploads')
->cannotBeEmpty()
->end()
->scalarNode('path_uri')
->defaultValue('/uploads')
->cannotBeEmpty()
->end()
->arrayNode('path_locked')
->scalarPrototype()
->end()
->defaultValue($defaultLocked)
->end()
->end()
->end()
->end();
->arrayNode('site')
->children()
->scalarNode('name')
->defaultValue('Murph')
->isRequired()
->end()
->scalarNode('logo')
->defaultValue('build/images/core/logo.svg')
->isRequired()
->end()
->arrayNode('controllers')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('action')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->arrayNode('security')
->children()
->arrayNode('roles')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('role')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('pages')
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('templates')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('file')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('editor_js')
->children()
->arrayNode('blocks')
->scalarPrototype()
->end()
->end()
->end()
->end()
->arrayNode('file_manager')
->children()
->arrayNode('mimes')
->scalarPrototype()
->end()
->defaultValue($defaultMimetypes)
->end()
->scalarNode('path')
->defaultValue('%kernel.project_dir%/public/uploads')
->cannotBeEmpty()
->end()
->scalarNode('path_uri')
->defaultValue('/uploads')
->cannotBeEmpty()
->end()
->arrayNode('path_locked')
->scalarPrototype()
->end()
->defaultValue($defaultLocked)
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}

View File

@ -6,28 +6,20 @@ use Doctrine\ORM\Mapping as ORM;
trait Timestampable
{
/**
* @ORM\Column(name="created_at", type="datetime")
*/
#[ORM\Column(name: 'created_at', type: 'datetime')]
protected $createdAt;
/**
* @ORM\Column(name="updated_at", type="datetime")
*/
#[ORM\Column(name: 'updated_at', type: 'datetime')]
protected $updatedAt;
/**
* @ORM\PrePersist
*/
#[ORM\PrePersist]
public function onPrePersist(): void
{
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
}
/**
* @ORM\PreUpdate
*/
#[ORM\PreUpdate]
public function onPreUpdate(): void
{
$this->updatedAt = new \DateTime();

View File

@ -2,43 +2,30 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_referer")
*/
#[ORM\Table(name: 'analytic_referer')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]
class Referer implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticReferers")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'analyticReferers')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $node;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $uri;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
#[ORM\Column(type: 'integer', options: ['default' => 0])]
protected $views = 0;
/**
* @ORM\Column(type="date")
*/
#[ORM\Column(type: 'date')]
protected $date;
public function getId(): ?int

View File

@ -2,53 +2,36 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_view")
*/
#[ORM\Table(name: 'analytic_view')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]
class View implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticViews")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'analyticViews')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $node;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $path;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
#[ORM\Column(type: 'integer', options: ['default' => 0])]
protected $views = 0;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
#[ORM\Column(type: 'integer', options: ['default' => 0])]
protected $desktopViews = 0;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
#[ORM\Column(type: 'integer', options: ['default' => 0])]
protected $mobileViews = 0;
/**
* @ORM\Column(type="date")
*/
#[ORM\Column(type: 'date')]
protected $date;
public function getId(): ?int

View File

@ -5,21 +5,15 @@ namespace App\Core\Entity;
use App\Repository\Entity\FileInformationRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=FileInformationRepository::class)
*/
#[ORM\Entity(repositoryClass: FileInformationRepository::class)]
class FileInformation implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="string", length=96, unique=true)
*/
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'NONE')]
#[ORM\Column(type: 'string', length: 96, unique: true)]
protected $id;
/**
* @ORM\Column(type="text", nullable=true)
*/
#[ORM\Column(type: 'text', nullable: true)]
protected $attributes;
public function getId(): ?string

View File

@ -6,42 +6,28 @@ use App\Core\Entity\Site\Navigation;
use App\Core\Repository\NavigationSettingRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=NavigationSettingRepository::class)
*/
#[ORM\Entity(repositoryClass: NavigationSettingRepository::class)]
class NavigationSetting implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $section;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $code;
/**
* @ORM\Column(type="text", nullable=true)
*/
#[ORM\Column(type: 'text', nullable: true)]
protected $value;
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="navigationSettings")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'navigationSettings')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $navigation;
public function getId(): ?int

View File

@ -5,71 +5,45 @@ namespace App\Core\Entity;
use App\Core\Repository\RedirectRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=RedirectRepository::class)
*/
#[ORM\Entity(repositoryClass: RedirectRepository::class)]
class Redirect implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=5)
*/
#[ORM\Column(type: 'string', length: 5)]
protected $scheme;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $domain;
/**
* @ORM\Column(type="string", length=6)
*/
#[ORM\Column(type: 'string', length: 6)]
protected $domainType;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $rule;
/**
* @ORM\Column(type="string", length=6)
*/
#[ORM\Column(type: 'string', length: 6)]
protected $ruleType;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $location;
/**
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $redirectCode;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $label;
/**
* @ORM\Column(type="integer", nullable=true)
*/
#[ORM\Column(type: 'integer', nullable: true)]
protected $sortOrder;
/**
* @ORM\Column(type="boolean")
*/
#[ORM\Column(type: 'boolean')]
protected $isEnabled;
/**
* @ORM\Column(type="boolean")
*/
#[ORM\Column(type: 'boolean')]
protected $reuseQueryString;
public function getId(): ?int

View File

@ -5,36 +5,24 @@ namespace App\Core\Entity;
use App\Core\Repository\SettingRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=SettingRepository::class)
*/
#[ORM\Entity(repositoryClass: SettingRepository::class)]
class Setting implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $section;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $code;
/**
* @ORM\Column(type="text", nullable=true)
*/
#[ORM\Column(type: 'text', nullable: true)]
protected $value;
public function getId(): ?int

View File

@ -9,46 +9,32 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=MenuRepository::class)
* @ORM\HasLifecycleCallbacks
*/
#[ORM\Entity(repositoryClass: MenuRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Menu implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $code;
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="menus")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'menus')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $navigation;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="menu", orphanRemoval=true, cascade={"remove", "persist"})
*/
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'menu', orphanRemoval: true, cascade: ['remove', 'persist'])]
protected $nodes;
/**
* @ORM\OneToOne(targetEntity=Node::class, cascade={"persist"})
* @ORM\JoinColumn(onDelete="CASCADE")
*/
#[ORM\OneToOne(targetEntity: Node::class, cascade: ['persist'])]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
protected $rootNode;
public function __construct()

View File

@ -10,64 +10,45 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=NavigationRepository::class)
* @ORM\HasLifecycleCallbacks
*/
#[ORM\Entity(repositoryClass: NavigationRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Navigation implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $code;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $domain;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
protected $forceDomain = false;
/**
* @ORM\Column(type="text", nullable=true)
*/
#[ORM\Column(type: 'text', nullable: true)]
protected $additionalDomains = '[]';
/**
* @ORM\OneToMany(targetEntity=Menu::class, mappedBy="navigation")
*/
#[ORM\OneToMany(targetEntity: Menu::class, mappedBy: 'navigation')]
protected $menus;
/**
* @ORM\Column(type="string", length=10)
*/
#[ORM\Column(type: 'string', length: 10)]
protected $locale = 'en';
/**
* @ORM\Column(type="integer", nullable=true)
*/
#[ORM\Column(type: 'string', length: 7, nullable: true)]
protected $color;
#[ORM\Column(type: 'integer', nullable: true)]
protected $sortOrder;
/**
* @ORM\OneToMany(targetEntity=NavigationSetting::class, mappedBy="navigation", orphanRemoval=true)
*/
#[ORM\OneToMany(targetEntity: NavigationSetting::class, mappedBy: 'navigation', orphanRemoval: true)]
protected $navigationSettings;
public function __construct()
@ -86,7 +67,7 @@ class Navigation implements EntityInterface
return $this->label;
}
public function setLabel(string $label): self
public function setLabel(?string $label): self
{
$this->label = $label;
@ -240,4 +221,35 @@ class Navigation implements EntityInterface
return $this;
}
public function matchDomain(string $domain): bool
{
if ($domain === $this->getDomain()) {
return true;
}
foreach ($this->getAdditionalDomains() as $additionalDomain) {
if ('domain' === $additionalDomain['type'] && $additionalDomain['domain'] === $domain) {
return true;
}
if ('regexp' === $additionalDomain['type'] && preg_match('#'.$additionalDomain['domain'].'#', $domain) > 0) {
return true;
}
}
return false;
}
public function setColor(string $color): self
{
$this->color = $color;
return $this;
}
public function getColor(): ?string
{
return $this->color;
}
}

View File

@ -16,156 +16,120 @@ use function Symfony\Component\String\u;
/**
* @Gedmo\Tree(type="nested")
* @ORM\HasLifecycleCallbacks
* @ORM\Entity(repositoryClass=NodeRepository::class)
*/
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: NodeRepository::class)]
class Node implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Menu::class, inversedBy="nodes", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Menu::class, inversedBy: 'nodes', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $menu;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $label;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $url;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
protected $disableUrl = false;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
protected $isVisible = false;
/**
* @Gedmo\TreeLeft
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeLeft;
/**
* @Gedmo\TreeLevel
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeLevel;
/**
* @Gedmo\TreeRight
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeRight;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Node")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: 'Node')]
#[ORM\JoinColumn(referencedColumnName: 'id', onDelete: 'CASCADE')]
protected $treeRoot;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Node", inversedBy="children")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: 'Node', inversedBy: 'children')]
#[ORM\JoinColumn(referencedColumnName: 'id', onDelete: 'CASCADE')]
protected $parent;
/**
* @ORM\OneToMany(targetEntity="Node", mappedBy="parent")
* @ORM\OrderBy({"treeLeft"="ASC"})
*/
#[ORM\OneToMany(targetEntity: 'Node', mappedBy: 'parent')]
#[ORM\OrderBy(['treeLeft' => 'ASC'])]
protected $children;
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="nodes", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
#[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'nodes', cascade: ['persist'])]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
protected $page;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $code;
/**
* @ORM\Column(type="array", nullable=true)
*/
#[ORM\Column(type: 'array', nullable: true)]
protected $parameters = [];
/**
* @ORM\Column(type="array", nullable=true)
*/
#[ORM\Column(type: 'array', nullable: true)]
protected $attributes = [];
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $controller;
/**
* @ORM\Column(type="array", nullable=true)
*/
#[ORM\Column(type: 'array', nullable: true)]
protected $sitemapParameters = [];
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="aliasNodes")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'aliasNodes')]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
protected $aliasNode;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="aliasNode")
*/
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'aliasNode')]
protected $aliasNodes;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $contentType;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
protected $enableAnalytics = false;
/**
* @ORM\OneToMany(targetEntity=View::class, mappedBy="node")
*/
#[ORM\OneToMany(targetEntity: View::class, mappedBy: 'node')]
protected $analyticViews;
/**
* @ORM\OneToMany(targetEntity=Referer::class, mappedBy="node")
*/
#[ORM\OneToMany(targetEntity: Referer::class, mappedBy: 'node')]
protected $analyticReferers;
/**
* @ORM\Column(type="array")
*/
#[ORM\Column(type: 'array', nullable: true)]
private $securityRoles = [];
/**
* @ORM\Column(type="string", length=3, options={"default"="or"})
*/
#[ORM\Column(type: 'string', length: 3, nullable: true)]
private $securityOperator = 'or';
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
private $hasAbTest = false;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $abTestCode;
public function __construct()
{
$this->children = new ArrayCollection();
@ -662,9 +626,9 @@ class Node implements EntityInterface
return $this;
}
public function getSecurityOperator(): ?string
public function getSecurityOperator(): string
{
return $this->securityOperator;
return $this->securityOperator ?? 'or';
}
public function setSecurityOperator(string $securityOperator): self
@ -673,4 +637,28 @@ class Node implements EntityInterface
return $this;
}
public function getHasAbTest(): ?bool
{
return $this->hasAbTest;
}
public function setHasAbTest(bool $hasAbTest): self
{
$this->hasAbTest = $hasAbTest;
return $this;
}
public function getAbTestCode(): ?string
{
return $this->abTestCode;
}
public function setAbTestCode(?string $abTestCode): self
{
$this->abTestCode = $abTestCode;
return $this;
}
}

View File

@ -6,37 +6,27 @@ use App\Core\Doctrine\Timestampable;
use App\Core\Repository\Site\Page\BlockRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=BlockRepository::class)
* @ORM\DiscriminatorColumn(name="class_key", type="string")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\HasLifecycleCallbacks
*/
#[ORM\Entity(repositoryClass: BlockRepository::class)]
#[ORM\DiscriminatorColumn(name: 'class_key', type: 'string')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\HasLifecycleCallbacks]
class Block
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $name;
/**
* @ORM\Column(type="text", nullable=true)
*/
#[ORM\Column(type: 'text', nullable: true)]
protected $value;
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="blocks")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'blocks')]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
protected $page;
public function getId(): ?int

View File

@ -4,9 +4,7 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
#[ORM\Entity]
class ChoiceBlock extends Block
{
public function getValue()

View File

@ -4,9 +4,7 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
#[ORM\Entity]
class CollectionBlock extends Block
{
public function getValue()

View File

@ -6,9 +6,7 @@ use App\Core\File\FileAttribute;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
/**
* @ORM\Entity
*/
#[ORM\Entity]
class FileBlock extends Block
{
public function getValue()

View File

@ -5,74 +5,51 @@ namespace App\Core\Entity\Site\Page;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use App\Core\File\FileAttribute;
use App\Core\Repository\Site\Page\PageRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\File\File;
use App\Core\File\FileAttribute;
/**
* @ORM\Entity(repositoryClass=PageRepository::class)
* @ORM\DiscriminatorColumn(name="class_key", type="string")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\HasLifecycleCallbacks
*/
#[ORM\Entity(repositoryClass: PageRepository::class)]
#[ORM\DiscriminatorColumn(name: 'class_key', type: 'string')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\HasLifecycleCallbacks]
class Page implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
#[ORM\Column(type: 'string', length: 255)]
protected $name;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $template;
/**
* @ORM\OneToMany(targetEntity=Block::class, mappedBy="page", cascade={"persist"})
*/
#[ORM\OneToMany(targetEntity: Block::class, mappedBy: 'page', cascade: ['persist'])]
protected $blocks;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $metaTitle;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $metaDescription;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $ogTitle;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $ogDescription;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
#[ORM\Column(type: 'string', length: 255, nullable: true)]
protected $ogImage;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="page")
*/
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'page')]
protected $nodes;
public function __construct()
@ -111,7 +88,7 @@ class Page implements EntityInterface
}
/**
* @return Collection|Block[]
* @return Block[]|Collection
*/
public function getBlocks(): Collection
{

View File

@ -4,9 +4,7 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
#[ORM\Entity]
class TextBlock extends Block
{
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Core\Event\Ab;
use App\Core\Ab\AbTest;
use Symfony\Contracts\EventDispatcher\Event;
/**
* class AbTestEvent.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbTestEvent extends Event
{
public const INIT_EVENT = 'ab_test.init';
public const RUN_EVENT = 'ab_test.run';
public function __construct(protected AbTest $test)
{
}
public function getTest(): AbTest
{
return $this->test;
}
}

View File

@ -12,16 +12,13 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class PasswordRequestEvent extends Event
{
const EVENT = 'account_event.password_request';
public const EVENT = 'account_event.password_request';
protected User $user;
public function __construct(User $user)
public function __construct(protected User $user)
{
$this->user = $user;
}
public function getUser(): USer
public function getUser(): User
{
return $this->user;
}

View File

@ -12,18 +12,15 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class EntityManagerEvent extends Event
{
const CREATE_EVENT = 'entity_manager_event.create';
const UPDATE_EVENT = 'entity_manager_event.update';
const DELETE_EVENT = 'entity_manager_event.delete';
const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
public const CREATE_EVENT = 'entity_manager_event.create';
public const UPDATE_EVENT = 'entity_manager_event.update';
public const DELETE_EVENT = 'entity_manager_event.delete';
public const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
public const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
public const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
protected EntityInterface $entity;
public function __construct(EntityInterface $entity)
public function __construct(protected EntityInterface $entity)
{
$this->entity = $entity;
}
public function getEntity(): EntityInterface

View File

@ -2,8 +2,8 @@
namespace App\Core\Event\Page;
use Symfony\Contracts\EventDispatcher\Event;
use App\Core\Entity\Site\Page\Page;
use Symfony\Contracts\EventDispatcher\Event;
/**
* class PageEditEvent.
@ -12,14 +12,12 @@ use App\Core\Entity\Site\Page\Page;
*/
class PageEditEvent extends Event
{
const FORM_INIT_EVENT = 'page_edit_event.form_init';
public const FORM_INIT_EVENT = 'page_edit_event.form_init';
protected Page $page;
protected array $pageBuilderOptions = [];
public function __construct(Page $page)
public function __construct(protected Page $page)
{
$this->page = $page;
}
public function getPage()

View File

@ -11,14 +11,11 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class NavigationSettingEvent extends Event
{
const INIT_EVENT = 'navigation_setting_event.init';
const FORM_INIT_EVENT = 'navigation_setting_event.form_init';
public const INIT_EVENT = 'navigation_setting_event.init';
public const FORM_INIT_EVENT = 'navigation_setting_event.form_init';
protected $data;
public function __construct($data = null)
public function __construct(protected $data = null)
{
$this->data = $data;
}
public function getData()

View File

@ -11,14 +11,11 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class SettingEvent extends Event
{
const INIT_EVENT = 'setting_event.init';
const FORM_INIT_EVENT = 'setting_event.form_init';
public const INIT_EVENT = 'setting_event.init';
public const FORM_INIT_EVENT = 'setting_event.form_init';
protected $data;
public function __construct($data = null)
public function __construct(protected $data = null)
{
$this->data = $data;
}
public function getData()

View File

@ -11,7 +11,7 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class TaskInitEvent extends Event
{
const INIT_EVENT = 'task_event.init';
public const INIT_EVENT = 'task_event.init';
protected array $tasks = [];

View File

@ -14,17 +14,13 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class TaskRunRequestedEvent extends Event
{
const RUN_REQUEST_EVENT = 'task_event.run_request';
public const RUN_REQUEST_EVENT = 'task_event.run_request';
protected string $task;
protected InputBag $parameters;
protected BufferedOutput $output;
public function __construct(string $task, InputBag $parameters, BufferedOutput $output)
{
$this->task = $task;
$this->parameters = $parameters;
$this->output = $output;
public function __construct(
protected string $task,
protected InputBag $parameters,
protected BufferedOutput $output
) {
}
public function getTask(): string

View File

@ -0,0 +1,102 @@
<?php
namespace App\Core\EventListener;
use App\Core\Ab\AbContainer;
use App\Core\Ab\AbTest;
use App\Core\Entity\Site\Node;
use App\Core\Event\Ab\AbTestEvent;
use App\Core\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
/**
* class AbListener.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbListener
{
protected ?Node $node;
public function __construct(
protected AbContainer $container,
protected EventDispatcherInterface $eventDispatcher,
protected SiteRequest $siteRequest
) {
}
public function onKernelRequest(RequestEvent $event)
{
$this->node = $this->siteRequest->getNode();
if (!$this->supports($event->getRequest())) {
return;
}
$request = $event->getRequest();
$cookieName = md5($this->getCookieName());
$cookieValue = $event->getRequest()->cookies->get($cookieName);
$abTest = new AbTest($this->getAbTestCode());
$event = new AbTestEvent($abTest);
$this->container->add($abTest);
$this->eventDispatcher->dispatch($event, AbTestEvent::INIT_EVENT);
if (!$abTest->isValidVariation($cookieValue)) {
$abTest->run();
$result = $abTest->getResult();
$attributes = array_merge($request->attributes->get('ab_test_cookies', []), [
$cookieName => ['value' => $result, 'duration' => $abTest->getDuration()],
]);
$request->attributes->set('ab_test_cookies', $attributes);
$this->eventDispatcher->dispatch($event, AbTestEvent::RUN_EVENT);
} else {
$abTest->setResult($cookieValue);
}
}
public function onKernelResponse(ResponseEvent $event)
{
$cookies = $event->getRequest()->attributes->get('ab_test_cookies', []);
foreach ($cookies as $name => $value) {
$cookie = Cookie::create($name, $value['value'], time() + $value['duration']);
$event->getResponse()->headers->setCookie($cookie);
}
}
protected function getCookieName(): string
{
return 'ab_test_'.$this->getAbTestCode();
}
protected function getAbTestCode(): string
{
return $this->node->getAbTestCode();
}
protected function supports(Request $request): bool
{
if (!$this->node) {
return false;
}
if (!$this->node->getHasAbTest()) {
return false;
}
if (!$this->node->getAbTestCode()) {
return false;
}
return true;
}
}

View File

@ -23,30 +23,18 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
*/
class AnalyticListener
{
protected NodeRepository $nodeRepository;
protected ViewRepositoryQuery $viewRepositoryQuery;
protected ViewFactory $viewFactory;
protected RefererRepositoryQuery $refererRepositoryQuery;
protected RefererFactory $refererFactory;
protected EntityManager $manager;
protected DeviceDetector $deviceDetector;
protected Request $request;
protected Node $node;
public function __construct(
NodeRepository $nodeRepository,
ViewRepositoryQuery $viewRepositoryQuery,
ViewFactory $viewFactory,
RefererRepositoryQuery $refererRepositoryQuery,
RefererFactory $refererFactory,
EntityManager $manager
protected NodeRepository $nodeRepository,
protected ViewRepositoryQuery $viewRepositoryQuery,
protected ViewFactory $viewFactory,
protected RefererRepositoryQuery $refererRepositoryQuery,
protected RefererFactory $refererFactory,
protected EntityManager $manager
) {
$this->nodeRepository = $nodeRepository;
$this->viewRepositoryQuery = $viewRepositoryQuery;
$this->viewFactory = $viewFactory;
$this->refererRepositoryQuery = $refererRepositoryQuery;
$this->refererFactory = $refererFactory;
$this->manager = $manager;
$this->createDeviceDetector();
}
@ -58,7 +46,7 @@ class AnalyticListener
return;
}
$this->deviceDetector->setUserAgent($request->headers->get('user-agent'));
$this->deviceDetector->setUserAgent((string) $request->headers->get('user-agent'));
$this->deviceDetector->parse();
if ($this->deviceDetector->isBot()) {

View File

@ -3,11 +3,10 @@
namespace App\Core\EventListener;
use App\Core\Repository\RedirectRepositoryQuery;
use App\Core\Router\RedirectBuilder;
use App\Core\Router\RedirectMatcher;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use App\Core\Router\RedirectBuilder;
/**
* class RedirectListener.
@ -16,15 +15,11 @@ use App\Core\Router\RedirectBuilder;
*/
class RedirectListener
{
protected RedirectMatcher $matcher;
protected RedirectBuilder $builder;
protected RedirectRepositoryQuery $repository;
public function __construct(RedirectMatcher $matcher, RedirectBuilder $builder, RedirectRepositoryQuery $repository)
{
$this->matcher = $matcher;
$this->builder = $builder;
$this->repository = $repository;
public function __construct(
protected RedirectMatcher $matcher,
protected RedirectBuilder $builder,
protected RedirectRepositoryQuery $repository
) {
}
public function onKernelException(ExceptionEvent $event)
@ -37,7 +32,7 @@ class RedirectListener
$redirects = $this->repository
->orderBy('.sortOrder')
->where('.isEnabled=1')
->where('.isEnabled=true')
->find()
;

View File

@ -0,0 +1,32 @@
<?php
namespace App\Core\EventSubscriber;
use App\Core\Event\Ab\AbTestEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* class AbEventSubscriber.
*
* @author Simon Vieille <simon@deblan.fr>
*/
abstract class AbEventSubscriber implements EventSubscriberInterface
{
protected static int $priority = 0;
public static function getSubscribedEvents()
{
return [
AbTestEvent::INIT_EVENT => ['onInit', self::$priority],
AbTestEvent::RUN_EVENT => ['onRun', self::$priority],
];
}
public function onInit(AbTestEvent $event)
{
}
public function onRun(AbTestEvent $event)
{
}
}

View File

@ -17,24 +17,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class PasswordRequestEventSubscriber implements EventSubscriberInterface
{
protected MailNotifier $notifier;
protected UrlGeneratorInterface $urlGenerator;
protected EntityManager $entityManager;
protected TokenGeneratorInterface $tokenGenerator;
protected TranslatorInterface $translator;
public function __construct(
MailNotifier $notifier,
UrlGeneratorInterface $urlGenerator,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator,
TranslatorInterface $translator
protected MailNotifier $notifier,
protected UrlGeneratorInterface $urlGenerator,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator,
protected TranslatorInterface $translator
) {
$this->notifier = $notifier;
$this->urlGenerator = $urlGenerator;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
$this->translator = $translator;
}
public static function getSubscribedEvents()

View File

@ -16,12 +16,12 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
*/
class RequestSecurityEventSubscriber implements EventSubscriberInterface
{
protected NodeRepository $nodeRepository;
protected AuthorizationChecker $authorizationChecker;
public function __construct(NodeRepository $nodeRepository, ContainerInterface $container)
{
$this->nodeRepository = $nodeRepository;
public function __construct(
protected NodeRepository $nodeRepository,
ContainerInterface $container
) {
$this->authorizationChecker = $container->get('security.authorization_checker');
}

View File

@ -4,18 +4,15 @@ namespace App\Core\EventSubscriber\Site;
use App\Core\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use function Symfony\Component\String\u;
class ForcedDomainEventSubscriber implements EventSubscriberInterface
{
protected SiteRequest $siteRequest;
public function __construct(SiteRequest $siteRequest)
public function __construct(protected SiteRequest $siteRequest)
{
$this->siteRequest = $siteRequest;
}
public function onKernelResponse(ResponseEvent $event)
@ -38,7 +35,8 @@ class ForcedDomainEventSubscriber implements EventSubscriberInterface
->replace(
'://'.$this->siteRequest->getDomain(),
'://'.$navigation->getDomain()
);
)
;
$event->getResponse()->headers->set('Location', $uri);
$event->getResponse()->setStatusCode(Response::HTTP_MOVED_PERMANENTLY);

View File

@ -20,37 +20,24 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class MenuEventSubscriber extends EntityManagerEventSubscriber
{
protected NodeFactory $nodeFactory;
protected NodeRepository $nodeRepository;
protected EntityManager $entityManager;
protected CodeSlugify $slugify;
protected SymfonyCacheManager $cacheManager;
protected TranslatorInterface $translation;
public function __construct(
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
CodeSlugify $slugify,
SymfonyCacheManager $cacheManager,
TranslatorInterface $translator
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected CodeSlugify $slugify,
protected SymfonyCacheManager $cacheManager,
protected TranslatorInterface $translator
) {
$this->nodeFactory = $nodeFactory;
$this->nodeRepository = $nodeRepository;
$this->entityManager = $entityManager;
$this->slugify = $slugify;
$this->cacheManager = $cacheManager;
$this->translator = $translator;
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Menu;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}
@ -60,13 +47,13 @@ class MenuEventSubscriber extends EntityManagerEventSubscriber
public function onCreate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}
$menu = $event->getEntity();
if (count($menu->getNodes()) > 2) {
if (count($menu->getNodes()) >= 2) {
return;
}
@ -84,10 +71,10 @@ class MenuEventSubscriber extends EntityManagerEventSubscriber
$menu->setRootNode($rootNode);
$this->entityManager->getEntityManager()->persist($rootNode);
$this->entityManager->getEntityManager()->persist($childNode);
foreach ([$rootNode, $childNode, $menu] as $entity) {
$this->entityManager->getEntityManager()->persist($entity);
}
$this->entityManager->getEntityManager()->persist($menu);
$this->entityManager->flush();
$this->nodeRepository->persistAsFirstChild($childNode, $rootNode);

View File

@ -17,21 +17,19 @@ use App\Core\Slugify\CodeSlugify;
class NavigationEventSubscriber extends EntityManagerEventSubscriber
{
public function __construct(
EntityManager $entityManager,
CodeSlugify $slugify
protected EntityManager $entityManager,
protected CodeSlugify $slugify
) {
$this->entityManager = $entityManager;
$this->slugify = $slugify;
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Navigation;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}

View File

@ -12,7 +12,6 @@ use App\Core\Repository\Site\NodeRepository;
use App\Core\Slugify\CodeSlugify;
use App\Core\Slugify\RouteParameterSlugify;
use App\Core\Slugify\Slugify;
use Symfony\Component\HttpKernel\KernelInterface;
use function Symfony\Component\String\u;
/**
@ -22,30 +21,17 @@ use function Symfony\Component\String\u;
*/
class NodeEventSubscriber extends EntityManagerEventSubscriber
{
protected NodeFactory $nodeFactory;
protected EntityManager $entityManager;
protected KernelInterface $kernel;
protected Slugify $slugify;
protected CodeSlugify $codeSlugify;
protected RouteParameterSlugify $routeParameterSlugify;
public function __construct(
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
Slugify $slugify,
CodeSlugify $codeSlugify,
RouteParameterSlugify $routeParameterSlugify
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected Slugify $slugify,
protected CodeSlugify $codeSlugify,
protected RouteParameterSlugify $routeParameterSlugify
) {
$this->nodeFactory = $nodeFactory;
$this->nodeRepository = $nodeRepository;
$this->entityManager = $entityManager;
$this->slugify = $slugify;
$this->codeSlugify = $codeSlugify;
$this->routeParameterSlugify = $routeParameterSlugify;
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Node;
}
@ -57,13 +43,13 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}
$node = $event->getEntity();
$node->setCode($this->codeSlugify->slugify($node->getCode()));
$node->setCode($this->codeSlugify->slugify($node->getCode() ?? ''));
if ($node->getDisableUrl()) {
$node->setUrl(null);
@ -159,7 +145,7 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
public function onDelete(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}

View File

@ -17,21 +17,18 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class BlockEventSubscriber extends EntityManagerEventSubscriber
{
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
public function __construct(protected FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Page;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}

View File

@ -16,21 +16,18 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class PageEventSubscriber extends EntityManagerEventSubscriber
{
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
public function __construct(protected FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Page;
}
public function onPreUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}

View File

@ -18,23 +18,20 @@ use Symfony\Component\HttpKernel\KernelInterface;
*/
class SiteEventSubscriber extends EntityManagerEventSubscriber
{
protected KernelInterface $kernel;
protected SymfonyCacheManager $cacheManager;
public function __construct(KernelInterface $kernel, SymfonyCacheManager $cacheManager)
{
$this->kernel = $kernel;
$this->cacheManager = $cacheManager;
public function __construct(
protected KernelInterface $kernel,
protected SymfonyCacheManager $cacheManager
) {
}
public function support(EntityInterface $entity)
public function supports(EntityInterface $entity): bool
{
return $entity instanceof Node || $entity instanceof Menu || $entity instanceof Navigation;
}
public function onUpdate(EntityManagerEvent $event)
{
if (!$this->support($event->getEntity())) {
if (!$this->supports($event->getEntity())) {
return;
}

View File

@ -13,11 +13,8 @@ use App\Core\Event\Task\TaskRunRequestedEvent;
*/
class CacheCleanTaskEventSubscriber extends TaskEventSubscriber
{
protected SymfonyCacheManager $cacheManager;
public function __construct(SymfonyCacheManager $cacheManager)
public function __construct(protected SymfonyCacheManager $cacheManager)
{
$this->cacheManager = $cacheManager;
}
public function onInit(TaskInitEvent $event)

View File

@ -2,7 +2,6 @@
namespace App\Core\Factory;
use App\Core\Factory\FactoryInterface;
use App\Core\Entity\Redirect as Entity;
class RedirectFactory implements FactoryInterface

View File

@ -23,22 +23,15 @@ class FsFileManager
protected string $path;
protected string $pathUri;
protected array $pathLocked;
protected FileUploadHandler $uploadHandler;
protected FileInformationFactory $fileInformationFactory;
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery;
public function __construct(
ParameterBagInterface $params,
FileUploadHandler $uploadHandler,
FileInformationFactory $fileInformationFactory,
FileInformationRepositoryQuery $fileInformationRepositoryQuery
protected FileUploadHandler $uploadHandler,
protected FileInformationFactory $fileInformationFactory,
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery
) {
$config = $params->get('core')['file_manager'];
$this->uploadHandler = $uploadHandler;
$this->fileInformationFactory = $fileInformationFactory;
$this->fileInformationRepositoryQuery = $fileInformationRepositoryQuery;
$this->mimes = $config['mimes'];
$this->path = $config['path'];
$this->pathUri = $this->normalizePath($config['path_uri']);
@ -91,12 +84,16 @@ class FsFileManager
$this->applySort($finder, $options['sort'] ?? 'name', $options['sort_direction'] ?? 'asc');
foreach ($finder as $file) {
$splInfo = $this->getSplInfo($directory.'/'.$file->getBasename());
$data['files'][] = [
'basename' => $file->getBasename(),
'path' => $directory,
'webPath' => $this->pathUri.'/'.$directory.'/'.$file->getBasename(),
'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
'mime' => mime_content_type($file->getRealPath()),
'size' => $splInfo ? $splInfo->getSize() : null,
'updated_at' => $splInfo ? date('Y-m-d H:i', $splInfo->getMTime()) : null,
];
}
@ -297,14 +294,16 @@ class FsFileManager
protected function applySort(Finder $finder, string $sort, string $direction)
{
if ('name' === $sort) {
$finder->sortByName();
} elseif ('modification_date' === $sort) {
$finder->sortByModifiedTime();
}
$sorts = [
'name' => 'sortByName',
'type' => 'sortByType',
'updated_at' => 'sortByModifiedTime',
];
if ('desc' === $direction) {
$finder->reverseSorting();
$finder->{$sorts[$sort]}()->reverseSorting();
} else {
$finder->{$sorts[$sort]}();
}
}

View File

@ -6,7 +6,6 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
class FilePickerType extends AbstractType
{

View File

@ -11,6 +11,15 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class FileUploadHandler
{
protected $filenameGenerator;
public function setFilenameGenerator(callable $filenameGenerator): self
{
$this->filenameGenerator = $filenameGenerator;
return $this;
}
public function handleForm(?UploadedFile $uploadedFile, string $path, ?callable $afterUploadCallback = null, bool $keepOriginalFilename = false): void
{
if (null === $uploadedFile) {
@ -21,9 +30,11 @@ class FileUploadHandler
if ($keepOriginalFilename) {
$filename = $originalFilename.'.'.$uploadedFile->guessExtension();
} else {
} elseif (!is_callable($this->filenameGenerator)) {
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
} else {
$filename = call_user_func($this->filenameGenerator, $uploadedFile);
}
$uploadedFile->move($path, $filename);

View File

@ -2,13 +2,11 @@
namespace App\Core\Form\Filter;
use App\Core\Entity\Redirect;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class RedirectFilterType extends AbstractType
{

View File

@ -4,12 +4,12 @@ namespace App\Core\Form;
use App\Core\Entity\Redirect;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class RedirectType extends AbstractType
{

View File

@ -7,6 +7,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class MenuType extends AbstractType
@ -23,6 +24,7 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -37,6 +39,7 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View File

@ -7,6 +7,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class NavigationAdditionalDomainType extends AbstractType
@ -24,6 +25,7 @@ class NavigationAdditionalDomainType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View File

@ -6,6 +6,7 @@ use App\Core\Entity\Site\Navigation;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -26,6 +27,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -38,6 +40,21 @@ class NavigationType extends AbstractType
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
$builder->add(
'color',
ColorType::class,
[
'label' => 'Color',
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
@ -54,6 +71,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -94,7 +112,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(['min' => 2, 'max' => 10]),
new Length(min: 2, max: 10),
],
]
);

View File

@ -58,13 +58,13 @@ class NodeSitemapParametersType extends AbstractType
'attr' => [
],
'choices' => [
'Toujours' => 'always',
'Toutes les heures' => 'hourly',
'Quotidienne' => 'daily',
'Hebdomadaire' => 'weekly',
'Mensuelle' => 'monthly',
'Annuelle' => 'yearly',
'Jamais' => 'never',
'Always' => 'always',
'Hourly' => 'hourly',
'Daily' => 'daily',
'Weekly' => 'weekly',
'Monthly' => 'monthly',
'Yearly' => 'yearly',
'Never' => 'never',
],
'constraints' => [
new NotBlank(),

View File

@ -13,6 +13,7 @@ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class NodeType extends AbstractType
@ -29,6 +30,7 @@ class NodeType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -43,6 +45,7 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -82,6 +85,7 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -116,6 +120,9 @@ class NodeType extends AbstractType
return $choices;
}),
'constraints' => [
new Length(max: 255),
],
]
);
@ -154,6 +161,37 @@ class NodeType extends AbstractType
);
}
$builder->add(
'hasAbTest',
CheckboxType::class,
[
'label' => 'Enable A/B Testing',
'required' => false,
]
);
$builder->add(
'abTestCode',
TextType::class,
[
'label' => 'Code',
'required' => $builder->getData()->getHasAbTest(),
]
);
$builder->add(
'securityOperator',
ChoiceType::class,
[
'label' => 'Condition',
'required' => true,
'choices' => [
'At least one role' => 'or',
'All roles' => 'and',
],
]
);
$actions = [
'New page' => 'new',
'Use an existing page' => 'existing',

View File

@ -0,0 +1,21 @@
<?php
namespace App\Core\Form\Site\Page;
use App\Core\Form\Type\EditorJsTextareaType;
use Symfony\Component\Form\FormBuilderInterface;
class EditorJsTextareaBlockType extends TextareaBlockType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
EditorJsTextareaType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
}

View File

@ -5,6 +5,8 @@ namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\FileBlock;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FileBlockType extends TextBlockType
@ -21,11 +23,21 @@ class FileBlockType extends TextBlockType
);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, [
'file_type' => $options['file_type'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FileBlock::class,
'block_prefix' => 'file_block',
'file_type' => 'auto',
'options' => [],
]);
}

View File

@ -2,11 +2,11 @@
namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\Block;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\AbstractType;
use App\Core\Entity\Site\Page\Block;
class FilePickerBlockType extends AbstractType
{

View File

@ -6,10 +6,10 @@ use App\Core\Entity\Site\Navigation;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class PageFilterType extends AbstractType
{

View File

@ -0,0 +1,21 @@
<?php
namespace App\Core\Form\Site\Page;
use App\Core\Form\Type\GrapesJsType;
use Symfony\Component\Form\FormBuilderInterface;
class GrapesJsBlockType extends TextareaBlockType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
GrapesJsType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Core\Form\Site\Page;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;
class ImageBlockType extends FileBlockType
@ -22,4 +23,11 @@ class ImageBlockType extends FileBlockType
], $options['options']),
);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefault('is_image', true);
}
}

View File

@ -10,6 +10,7 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class PageType extends AbstractType
@ -26,6 +27,7 @@ class PageType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -39,6 +41,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -52,6 +55,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -65,6 +69,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -78,6 +83,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);

View File

@ -0,0 +1,21 @@
<?php
namespace App\Core\Form\Site\Page;
use App\Core\Form\Type\TinymceTextareaType;
use Symfony\Component\Form\FormBuilderInterface;
class TinymceTextareaBlockType extends TextareaBlockType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
TinymceTextareaType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
}

View File

@ -20,6 +20,10 @@ class CollectionType extends BaseCollectionType
'collection_name' => $options['collection_name'],
'label_add' => $options['label_add'],
'label_delete' => $options['label_delete'],
'allow_add' => $options['allow_add'],
'allow_delete' => $options['allow_delete'],
'template_before_item' => $options['template_before_item'],
'template_after_item' => $options['template_after_item'],
]);
}
@ -31,6 +35,8 @@ class CollectionType extends BaseCollectionType
'collection_name' => '',
'label_add' => 'Add',
'label_delete' => 'Delete',
'template_before_item' => null,
'template_after_item' => null,
]);
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Core\Form\Type;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class EditorJsTextareaType extends TextareaType
{
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!isset($view->vars['attr']['data-editorjs'])) {
$view->vars['attr']['data-editorjs'] = '';
}
return parent::buildView($view, $form, $options);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Core\Form\Type;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class GrapesJsType extends TextareaType
{
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!isset($view->vars['attr']['data-grapesjs'])) {
$view->vars['attr']['data-grapesjs'] = '';
}
return parent::buildView($view, $form, $options);
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'grapesjs';
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Core\Form\Type;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
class TinymceTextareaType extends TextareaType
{
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!isset($view->vars['attr']['data-tinymce'])) {
$view->vars['attr']['data-tinymce'] = '';
}
return parent::buildView($view, $form, $options);
}
}

View File

@ -11,6 +11,7 @@ use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Filesystem\Filesystem;
use function Symfony\Component\String\u;
class MakeCrudController extends AbstractMaker
@ -103,6 +104,17 @@ class MakeCrudController extends AbstractMaker
$options
);
$views = ['_form.html.twig', '_show.html.twig'];
$directory = sprintf('templates/admin/%s_admin/', $options['route']);
$filesystem = new Filesystem();
$filesystem->mkdir($directory);
foreach ($views as $view) {
$filesystem->dumpFile(
$directory.$view,
sprintf("{{ include('@Core/admin/crud/%s') }}\n", $view)
);
}
$generator->writeChanges();
$this->writeSuccessMessage($io);

View File

@ -11,7 +11,6 @@ use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use function Symfony\Component\String\u;
class MakeFactory extends AbstractMaker
{

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