From 198733496dff781abe7b7cc4c82e6338bb9d63ac Mon Sep 17 00:00:00 2001 From: bcweaver <brianweaver@gmail.com> Date: Wed, 31 Jul 2019 12:41:50 -0400 Subject: [PATCH] Update webform module to 5.2 (and metatag thing) --- composer.json | 2 +- composer.lock | 34 +- vendor/composer/installed.json | 50 +- web/modules/webform/.eslintrc.json | 94 + web/modules/webform/.gitignore | 6 +- web/modules/webform/ISSUE_TEMPLATE.html | 2 +- web/modules/webform/README.md | 23 +- web/modules/webform/composer.json | 10 + web/modules/webform/composer.libraries.json | 429 +++ .../config/install/webform.settings.yml | 66 +- .../install/webform.webform.contact.yml | 48 +- .../webform.webform_options.gender.yml | 1 - ...m.webform_options.state_province_codes.yml | 4 +- ...m.webform_options.state_province_names.yml | 2 +- .../webform.webform_options.translations.yml | 11 + .../views.view.webform_submissions.yml | 3167 +++++++++++++++++ .../config/schema/webform.action.schema.yml | 4 - .../config/schema/webform.block.schema.yml | 12 +- .../schema/webform.entity.webform.schema.yml | 409 +-- .../webform.entity.webform_options.schema.yml | 8 +- .../config/schema/webform.field.schema.yml | 25 +- .../schema/webform.plugin.exporter.schema.yml | 10 +- .../schema/webform.plugin.handler.schema.yml | 160 +- .../config/schema/webform.settings.schema.yml | 150 +- .../webform.third_party.captcha.schema.yml | 7 + .../webform.third_party.honeypot.schema.yml | 3 +- web/modules/webform/css/webform.addons.css | 5 + .../webform/css/webform.admin.composite.css | 2 +- web/modules/webform/css/webform.admin.css | 80 +- .../webform/css/webform.admin.dropbutton.css | 13 + .../webform/css/webform.admin.settings.css | 13 + .../webform/css/webform.admin.tabledrag.css | 29 + web/modules/webform/css/webform.ajax.css | 4 +- web/modules/webform/css/webform.composite.css | 17 +- .../css/webform.element.checkbox_value.css | 2 +- .../webform/css/webform.element.composite.css | 2 +- .../webform/css/webform.element.computed.css | 8 + .../webform/css/webform.element.counter.css | 17 + .../webform/css/webform.element.date.css | 8 + .../webform/css/webform.element.datelist.css | 8 + .../css/webform.element.details.toggle.css | 31 + .../webform/css/webform.element.help.css | 22 +- .../css/webform.element.image_file.css | 4 + .../webform/css/webform.element.likert.css | 44 +- ... webform.element.location.geocomplete.css} | 10 +- .../css/webform.element.location.places.css | 16 + .../css/webform.element.managed_file.css | 30 + .../webform/css/webform.element.mapping.css | 13 + .../webform/css/webform.element.message.css | 2 +- .../webform/css/webform.element.multiple.css | 32 +- .../webform/css/webform.element.range.css | 2 +- .../webform/css/webform.element.states.css | 11 + .../css/webform.element.tableselect.css | 2 +- .../css/webform.element.video_file.css | 12 +- web/modules/webform/css/webform.filter.css | 34 + .../webform/css/webform.progress.tracker.css | 11 +- .../webform/css/webform.promotions.css | 9 +- .../webform/css/webform.theme.bartik.css | 23 + .../webform/css/webform.theme.classy.css | 14 + .../webform/css/webform.theme.seven.css | 120 +- web/modules/webform/css/webform.token.css | 20 + .../webform/css/webform.wizard.pages.css | 36 + .../webform/docs/DEVELOPMENT-CHEATSHEET.md | 89 +- web/modules/webform/docs/DEVELOPMENT-NOTES.md | 2 +- web/modules/webform/docs/FEATURES.md | 867 +++-- web/modules/webform/docs/RELEASE-NOTES.md | 171 +- web/modules/webform/docs/UPDATE-LIBRARIES.md | 85 + web/modules/webform/drush/webform.drush.inc | 46 +- .../webform/images/elements/date-calendar.png | 3 + web/modules/webform/includes/webform.date.inc | 108 +- .../webform/includes/webform.editor.inc | 267 ++ .../webform/includes/webform.install.inc | 177 +- .../includes/webform.install.requirements.inc | 2 +- .../includes/webform.install.update.inc | 978 ++++- .../webform/includes/webform.libraries.inc | 37 +- .../webform/includes/webform.options.inc | 13 + .../webform/includes/webform.theme.inc | 609 ++-- .../includes/webform.theme.template.inc | 123 +- .../webform/includes/webform.translation.inc | 143 +- web/modules/webform/js/webform.admin.js | 6 +- .../webform/js/webform.admin.tabledrag.js | 19 + web/modules/webform/js/webform.ajax.js | 153 +- web/modules/webform/js/webform.announce.js | 33 + .../webform/js/webform.confirmation.modal.js | 14 +- web/modules/webform/js/webform.contextual.js | 8 +- web/modules/webform/js/webform.dialog.js | 74 +- .../js/webform.element.buttons.buttonset.js | 38 - ...boxradio.js => webform.element.buttons.js} | 17 +- .../webform/js/webform.element.chosen.js | 45 +- .../webform/js/webform.element.codemirror.js | 87 +- .../webform/js/webform.element.color.js | 11 +- .../webform/js/webform.element.composite.js | 8 +- .../webform/js/webform.element.computed.js | 74 + .../webform/js/webform.element.counter.js | 37 +- .../webform/js/webform.element.date.js | 26 +- .../js/webform.element.details.save.js | 4 +- .../js/webform.element.details.toggle.js | 40 +- .../webform/js/webform.element.help.js | 47 +- .../webform/js/webform.element.html_editor.js | 8 +- .../webform/js/webform.element.icheck.js | 23 +- .../webform/js/webform.element.inputhide.js | 51 + ...> webform.element.location.geocomplete.js} | 33 +- .../js/webform.element.location.places.js | 101 + .../js/webform.element.managed_file.js | 109 + .../webform/js/webform.element.more.js | 39 +- .../webform/js/webform.element.multiple.js | 37 +- .../js/webform.element.options.admin.js | 4 +- .../webform/js/webform.element.other.js | 10 +- .../webform/js/webform.element.radios.js | 2 +- .../webform/js/webform.element.range.js | 22 +- .../webform/js/webform.element.rating.js | 5 + .../webform/js/webform.element.select2.js | 73 +- .../webform/js/webform.element.signature.js | 18 +- .../webform/js/webform.element.states.js | 102 + .../webform/js/webform.element.tableselect.js | 66 + .../webform/js/webform.element.telephone.js | 6 +- .../js/webform.element.terms_of_service.js | 34 +- .../webform/js/webform.element.time.js | 4 +- .../webform/js/webform.element.toggle.js | 8 +- web/modules/webform/js/webform.filter.js | 145 + web/modules/webform/js/webform.form.js | 139 +- .../webform/js/webform.form.submit_back.js | 2 +- .../webform/js/webform.form.submit_once.js | 18 +- web/modules/webform/js/webform.form.tabs.js | 24 +- .../webform/js/webform.form.unsaved.js | 46 +- web/modules/webform/js/webform.form.wizard.js | 42 - web/modules/webform/js/webform.imce.js | 5 + .../webform/js/webform.jquery.ui.dialog.js | 52 + web/modules/webform/js/webform.off-canvas.js | 39 + web/modules/webform/js/webform.states.js | 77 +- web/modules/webform/js/webform.tooltip.js | 12 +- .../webform/js/webform.wizard.pages.js | 61 + .../webform/js/webform.wizard.track.js | 67 + ...cess.entity.webform_access_type.schema.yml | 10 + .../config/schema/webform_access.schema.yml | 33 + .../webform_access/js/webform_access.admin.js | 36 + .../WebformAccessBreadcrumbBuilder.php | 92 + .../src/Entity/WebformAccessGroup.php | 220 ++ .../src/Entity/WebformAccessType.php | 75 + .../Block/WebformAccessGroupEntityBlock.php | 103 + .../WebformAccessSubmissionViewsTest.php | 99 + .../src/Tests/WebformAccessTest.php | 165 + .../src/Tests/WebformAccessTestBase.php | 97 + .../src/Tests/WebformAccessTokensTest.php | 54 + ...WebformAccessGroupAccessControlHandler.php | 24 + .../src/WebformAccessGroupDeleteForm.php | 35 + .../src/WebformAccessGroupForm.php | 317 ++ .../src/WebformAccessGroupInterface.php | 119 + .../src/WebformAccessGroupListBuilder.php | 210 ++ .../src/WebformAccessGroupStorage.php | 227 ++ .../WebformAccessGroupStorageInterface.php | 46 + .../WebformAccessTypeAccessControlHandler.php | 24 + .../src/WebformAccessTypeDeleteForm.php | 35 + .../src/WebformAccessTypeForm.php | 79 + .../src/WebformAccessTypeInterface.php | 12 + .../src/WebformAccessTypeListBuilder.php | 146 + .../src/WebformAccessTypeStorage.php | 12 + .../src/WebformAccessTypeStorageInterface.php | 13 + .../webform_access/webform_access.info.yml | 14 + .../webform_access/webform_access.install | 94 + .../webform_access.libraries.yml | 8 + .../webform_access.links.action.yml | 11 + .../webform_access.links.task.yml | 29 + .../webform_access/webform_access.module | 426 +++ .../webform_access/webform_access.routing.yml | 75 + .../webform_access.services.yml | 6 + .../webform_access/webform_access.tokens.inc | 77 + .../WebformAttachmentController.php | 125 + .../src/Element/WebformAttachmentBase.php | 153 + .../Element/WebformAttachmentInterface.php | 78 + .../src/Element/WebformAttachmentToken.php | 33 + .../src/Element/WebformAttachmentTwig.php | 34 + .../src/Element/WebformAttachmentUrl.php | 58 + .../WebformElement/WebformAttachmentBase.php | 238 ++ .../WebformElement/WebformAttachmentToken.php | 41 + .../WebformElement/WebformAttachmentTwig.php | 46 + .../WebformElement/WebformAttachmentUrl.php | 78 + .../src/Tests/WebformAttachmentTest.php | 230 ++ ...webform.webform.test_attachment_access.yml | 205 ++ .../webform.webform.test_attachment_email.yml | 259 ++ ...bform.webform.test_attachment_sanitize.yml | 183 + .../webform.webform.test_attachment_token.yml | 191 + .../webform.webform.test_attachment_twig.yml | 192 + .../webform.webform.test_attachment_url.yml | 193 + .../webform_attachment_test.info.yml | 13 + .../webform_attachment_test.module | 41 + .../webform_attachment.info.yml | 13 + .../webform_attachment.routing.yml | 6 + .../modules/webform_bootstrap/README.md | 2 +- .../css/webform_bootstrap.css | 16 + .../js/webform.element.help.js | 45 + .../js/webform_bootstrap.states.js | 28 + .../style-guide/images.html | 6 +- .../style-guide/inputs.html | 4 +- .../style-guide/typography.html | 4 +- .../webform_bootstrap_test_module.info.yml | 6 +- ...ebform_bootstrap_test_module.libraries.yml | 2 +- .../src/Plugin/Preprocess/Region.php | 7 +- .../webform_bootstrap_test_theme.info.yml | 6 +- .../webform_bootstrap.info.yml | 6 +- .../webform_bootstrap.libraries.yml | 4 + .../webform_bootstrap.module | 110 +- .../webform.webform.demo_application.yml | 34 +- ...rm.webform.demo_application_evaluation.yml | 34 +- ...ik_webform_demo_application_evaluation.yml | 1 + ...en_webform_demo_application_evaluation.yml | 1 + ...bform_demo_application_evaluation.info.yml | 6 +- ...isplay.node.webform_demo_event.default.yml | 9 +- ....field.node.webform_demo_event.webform.yml | 6 +- ...ebform.webform.demo_event_registration.yml | 56 +- .../webform_demo_event_registration.info.yml | 6 +- .../webform_demo_event_registration.install | 5 +- ...splay.node.webform_demo_region.default.yml | 83 + ...splay.node.webform_demo_region.default.yml | 36 + ...isplay.node.webform_demo_region.teaser.yml | 31 + ...ld.field.node.webform_demo_region.body.yml | 21 + ...field.node.webform_demo_region.webform.yml | 33 + .../install/node.type.webform_demo_region.yml | 20 + .../webform.webform.demo_region_contact.yml | 297 ++ ...block.block.bartik_webform_demo_region.yml | 22 + .../webform_demo_region_contact.info.yml | 17 + .../webform_demo_region_contact.install | 150 + .../config/install/webform_devel.settings.yml | 2 - .../config/schema/webform_devel.schema.yml | 13 - .../modules/webform_devel/drush.services.yml | 6 + .../src/Commands/WebformDevelCommands.php | 117 + .../src/Form/WebformDevelEntitySchemaForm.php | 2 +- .../Form/WebformDevelSubmissionApiForm.php | 6 +- .../src/Logger/WebformDevelLog.php | 76 - .../webform_devel/src/WebformDevelSchema.php | 3 +- .../webform_devel/webform_devel.drush.inc | 6 +- .../webform_devel/webform_devel.info.yml | 6 +- .../webform_devel.links.task.yml | 6 - .../webform_devel/webform_devel.module | 39 +- .../webform_devel/webform_devel.routing.yml | 8 - .../webform_devel/webform_devel.services.yml | 6 - .../webform_editorial.info.yml | 6 +- .../webform_editorial.links.menu.yml | 1 - ...form.webform.webform_example_composite.yml | 34 +- .../src/Tests/WebformExampleCompositeTest.php | 18 +- .../webform_example_composite.info.yml | 6 +- ...ebform.webform.webform_example_element.yml | 34 +- .../WebformElement/WebformExampleElement.php | 1 - .../src/Tests/WebformExampleElementTest.php | 2 +- .../webform_example_element.info.yml | 6 +- ...ebform.webform.webform_example_handler.yml | 188 + .../schema/webform_example_handler.schema.yml | 10 + .../WebformHandler/ExampleWebformHandler.php | 281 ++ .../webform-handler-example-summary.html.twig | 20 + .../webform_example_handler.info.yml | 13 + .../webform_example_handler.module | 17 + .../webform.webform.example_remote_post.yml | 47 +- .../webform_example_remote_post.info.yml | 7 +- .../webform_example_remote_post.module | 40 - ...form.webform.example_computed_elements.yml | 87 +- ...webform.example_computed_elements_ajax.yml | 269 ++ ...webform.webform.example_element_states.yml | 44 +- ...webform.webform.example_flexbox_layout.yml | 38 +- .../webform.webform.example_input_masks.yml | 50 +- .../webform.webform.example_style_guide.yml | 110 +- .../webform.webform.example_wizard.yml | 36 +- .../webform_examples.info.yml | 6 +- ...webform.example_accessibility_advanced.yml | 365 ++ ...rm.webform.example_accessibility_basic.yml | 281 ++ ...bform.example_accessibility_containers.yml | 206 ++ ...m.webform.example_accessibility_labels.yml | 336 ++ ...m.webform.example_accessibility_wizard.yml | 211 ++ .../css/webform_examples_accessibility.css | 52 + .../webform_examples_accessibility.info.yml | 13 + ...bform_examples_accessibility.libraries.yml | 5 + .../webform_examples_accessibility.module | 132 + .../schema/webform_image_select.schema.yml | 8 +- .../css/webform_image_select.element.css | 12 + .../js/webform_image_select.element.js | 67 + .../src/Access/WebformImageSelectAccess.php | 3 +- .../WebformImageSelectImagesController.php | 55 + .../src/Element/WebformImageSelect.php | 87 +- .../WebformImageSelectElementImages.php | 2 +- .../src/Element/WebformImageSelectImages.php | 23 +- .../src/Entity/WebformImageSelectImages.php | 12 +- .../WebformImageSelectImagesFilterForm.php | 88 + .../WebformElement/WebformImageSelect.php | 52 +- .../WebformImageSelectElementImagesTest.php | 8 + .../Tests/WebformImageSelectElementTest.php | 12 +- .../Tests/WebformImageSelectImagesTest.php | 34 +- ...mImageSelectImagesAccessControlHandler.php | 2 +- .../WebformImageSelectImagesDeleteForm.php | 72 +- .../src/WebformImageSelectImagesForm.php | 24 +- .../src/WebformImageSelectImagesInterface.php | 2 +- .../WebformImageSelectImagesListBuilder.php | 157 +- .../src/WebformImageSelectImagesStorage.php | 6 +- ...bformImageSelectImagesStorageInterface.php | 1 + ...form.webform.test_element_image_select.yml | 74 +- .../webform.webform.test_element_images.yml | 44 +- .../webform_image_select_test.info.yml | 6 +- .../webform_image_select.info.yml | 6 +- .../webform_image_select.module | 57 + .../webform_image_select.routing.yml | 7 + .../field.field.node.webform.webform.yml | 11 +- .../css/webform_node.entity_references.css | 2 +- .../src/Access/WebformNodeAccess.php | 41 +- .../WebformNodeReferencesListController.php | 116 +- .../WebformNodeSubmissionLogController.php | 22 - .../WebformNodeAccessPermissionsTest.php | 123 + .../Access/WebformNodeAccessRulesTest.php | 123 + .../src/Tests/WebformNodeAccessTest.php | 197 - .../Tests/WebformNodeEntityReferenceTest.php | 62 +- .../src/Tests/WebformNodeReferencesTest.php | 10 +- .../src/Tests/WebformNodeResultsTest.php | 60 +- .../src/Tests/WebformNodeTest.php | 45 +- .../src/Tests/WebformNodeTestBase.php | 8 +- .../src/Tests/WebformNodeTranslationTest.php | 38 + ...m.webform.webform_node_test_multiple_a.yml | 35 +- ...m.webform.webform_node_test_multiple_b.yml | 35 +- .../webform_node_test_multiple.info.yml | 6 +- ....webform.webform_node_test_translation.yml | 176 + .../webform_node_test_translation.info.yml | 16 + .../webform_node_test_translation.install | 59 + .../src/Kernel/WebformNodeUninstallTest.php | 2 +- .../webform_node/webform_node.info.yml | 6 +- .../webform_node/webform_node.libraries.yml | 2 + .../webform_node/webform_node.links.task.yml | 24 +- .../modules/webform_node/webform_node.module | 95 +- .../webform_node/webform_node.routing.yml | 58 +- .../webform_node/webform_node.tokens.inc | 2 +- .../webform_scheduled_email.settings.yml | 1 + ...scheduled_email.plugin.handler.schema.yml} | 12 + ...ebform_scheduled_email.settings.schema.yml | 7 + .../drush.services.yml | 2 +- .../drush/webform_scheduled_email.drush.inc | 2 + .../WebformScheduledEmailCommands.php | 25 +- .../WebformScheduledEmailController.php | 4 +- .../ScheduleEmailWebformHandler.php | 98 +- .../src/Tests/WebformScheduledEmailTest.php | 104 +- .../WebformScheduledEmailTranslationTest.php | 56 + .../src/WebformScheduledEmailManager.php | 224 +- .../WebformScheduledEmailManagerInterface.php | 50 +- ...-handler-scheduled-email-summary.html.twig | 4 + ...m.webform.test_handler_scheduled_email.yml | 121 +- .../webform_scheduled_email_test.info.yml | 6 +- .../config/install/language.entity.es.yml | 8 + ...bform.test_handler_scheduled_translate.yml | 5 + ...bform.test_handler_scheduled_translate.yml | 215 ++ ..._scheduled_email_test_translation.info.yml | 17 + .../webform_scheduled_email.info.yml | 6 +- .../webform_scheduled_email.install | 27 + .../webform_scheduled_email.module | 37 + .../webform_scheduled_email.services.yml | 2 +- .../install/webform_shortcuts.settings.yml | 6 + .../webform_shortcuts.settings.schema.yml | 22 + .../webform_shortcuts/js/webform_shortcuts.js | 62 + .../WebformShortcutsFunctionalTest.php | 54 + .../webform_shortcuts.info.yml | 14 + .../webform_shortcuts.install | 22 + .../webform_shortcuts.libraries.yml | 23 + .../webform_shortcuts.module | 161 + ...ebformSubmissionExportImportController.php | 177 + ...ebformSubmissionExportImportUploadForm.php | 727 ++++ ...mSubmissionExportImportWebformExporter.php | 91 + ...mSubmissionExportImportRouteSubscriber.php | 42 + .../WebformSubmissionExportImportImporter.php | 1172 ++++++ ...ubmissionExportImportImporterInterface.php | 226 ++ ....webform.test_submission_export_import.yml | 231 ++ ...test_submission_export_import-external.csv | 2 + .../test_submission_export_import-webform.csv | 4 + .../webform_submission_export_import_test.js | 27 + ...orm_submission_export_import_test.info.yml | 13 + ...form_submission_export_import_test.install | 24 + ...ubmission_export_import_test.libraries.yml | 8 + ...bform_submission_export_import_test.module | 87 + ...rmSubmissionImportExportFunctionalTest.php | 425 +++ .../webform_submission_export_import.info.yml | 14 + ...rm_submission_export_import.links.task.yml | 15 + .../webform_submission_export_import.module | 254 ++ ...bform_submission_export_import.routing.yml | 80 + ...form_submission_export_import.services.yml | 17 + .../WebformSubmissionLogController.php | 96 +- .../WebformSubmissionLogRouteSubscriber.php | 41 + .../Tests/WebformSubmissionLogNodeTest.php} | 19 +- .../src/Tests/WebformSubmissionLogTest.php | 65 +- .../src/Tests/WebformSubmissionLogTrait.php | 75 + .../src/WebformSubmissionLogLogger.php | 89 + .../src/WebformSubmissionLogManager.php | 147 + .../WebformSubmissionLogManagerInterface.php | 67 + .../webform_submission_log.info.yml | 13 + .../webform_submission_log.install | 90 + .../webform_submission_log.links.task.yml | 33 + .../webform_submission_log.module | 87 + .../webform_submission_log.permissions.yml | 3 + .../webform_submission_log.routing.yml | 68 + .../webform_submission_log.services.yml | 16 + .../webform.webform.template_contact.yml | 44 +- ...m.webform.template_employee_evaluation.yml | 40 +- .../webform.webform.template_feedback.yml | 48 +- .../webform.webform.template_issue.yml | 34 +- ...bform.webform.template_job_application.yml | 50 +- ...rm.webform.template_job_seeker_profile.yml | 45 +- .../webform.webform.template_registration.yml | 50 +- ...rm.webform.template_session_evaluation.yml | 44 +- .../webform.webform.template_subscribe.yml | 42 +- .../webform.webform.template_user_profile.yml | 56 +- .../Controller/WebformTemplatesController.php | 94 +- .../src/Form/WebformTemplatesFilterForm.php | 2 +- .../src/Tests/WebformTemplatesTest.php | 38 +- .../webform_templates.info.yml | 6 +- .../webform_templates.links.task.yml | 12 + .../webform_templates.permissions.yml | 4 + .../webform_templates.routing.yml | 14 +- .../webform_ui/css/webform_ui.module.css | 46 +- .../modules/webform_ui/js/webform_ui.js | 100 +- .../webform_ui/src/Access/WebformUiAccess.php | 32 +- .../src/Form/WebformUiElementAddForm.php | 16 +- .../src/Form/WebformUiElementDeleteForm.php | 154 +- .../Form/WebformUiElementDuplicateForm.php | 10 +- .../src/Form/WebformUiElementEditForm.php | 49 +- .../src/Form/WebformUiElementFormBase.php | 81 +- .../src/Form/WebformUiElementTestForm.php | 11 +- .../Form/WebformUiElementTypeChangeForm.php | 6 +- .../src/Form/WebformUiElementTypeFormBase.php | 42 +- .../Form/WebformUiElementTypeSelectForm.php | 22 +- .../PathProcessor/WebformUiPathProcessor.php | 2 +- .../Tests/WebformUiElementPropertiesTest.php | 2 +- .../src/Tests/WebformUiElementTest.php | 95 +- .../src/WebformUiEntityElementsForm.php | 250 +- .../webform_ui/src/WebformUiOptionsForm.php | 2 +- .../modules/webform_ui/webform_ui.info.yml | 6 +- .../webform_ui/webform_ui.libraries.yml | 2 +- .../webform_ui/webform_ui.links.action.yml | 29 + .../modules/webform_ui/webform_ui.module | 27 +- .../webform_ui/webform_ui.permissions.yml | 4 - .../modules/webform_ui/webform_ui.routing.yml | 24 +- .../text/example_accessibility_advanced.txt | 89 + .../text/example_accessibility_basic.txt | 19 + .../text/example_accessibility_containers.txt | 24 + .../text/example_accessibility_labels.txt | 44 + .../text/example_accessibility_wizard.txt | 14 + .../src/Access/WebformAccessResult.php | 107 + .../src/Access/WebformAccountAccess.php | 36 +- .../src/Access/WebformEntityAccess.php | 48 + .../src/Access/WebformHandlerAccess.php | 36 + .../src/Access/WebformSourceEntityAccess.php | 4 +- .../src/Access/WebformSubmissionAccess.php | 12 +- .../src/Ajax/WebformAnnounceCommand.php | 55 + .../webform/src/Annotation/WebformElement.php | 16 + .../src/Annotation/WebformExporter.php | 7 + .../webform/src/Annotation/WebformHandler.php | 9 +- .../src/Annotation/WebformSourceEntity.php | 9 + .../Breadcrumb/WebformBreadcrumbBuilder.php | 38 +- .../src/Commands/WebformCliService.php | 349 +- .../webform/src/Commands/WebformCommands.php | 65 +- .../src/Commands/WebformCommandsBase.php | 14 +- .../Controller/WebformAddonsController.php | 65 +- .../WebformContributeController.php | 38 +- .../Controller/WebformElementController.php | 3 +- .../Controller/WebformEntityController.php | 71 +- .../src/Controller/WebformHelpController.php | 51 + .../Controller/WebformOptionsController.php | 72 +- .../WebformPluginElementController.php | 62 +- .../WebformPluginExporterController.php | 14 +- .../WebformPluginHandlerController.php | 21 +- .../WebformResultsExportController.php | 25 +- .../WebformSubmissionController.php | 171 +- .../WebformSubmissionViewController.php | 19 +- .../WebformSubmissionsController.php | 71 + .../src/Controller/WebformTestController.php | 55 +- web/modules/webform/src/Element/Webform.php | 80 +- .../webform/src/Element/WebformActions.php | 3 +- .../webform/src/Element/WebformAddress.php | 2 +- .../webform/src/Element/WebformButtons.php | 9 +- .../src/Element/WebformButtonsOther.php | 9 +- .../webform/src/Element/WebformCodeMirror.php | 168 +- .../src/Element/WebformCompositeBase.php | 22 +- .../WebformCompositeFormElementTrait.php | 124 + .../src/Element/WebformComputedBase.php | 268 +- .../src/Element/WebformComputedToken.php | 2 +- .../src/Element/WebformComputedTwig.php | 32 +- .../webform/src/Element/WebformContact.php | 2 +- .../src/Element/WebformElementAttributes.php | 15 +- .../src/Element/WebformElementComposite.php | 77 +- .../src/Element/WebformElementOptions.php | 27 +- .../src/Element/WebformElementStates.php | 340 +- .../src/Element/WebformEmailConfirm.php | 19 +- .../src/Element/WebformEntityTrait.php | 19 +- .../src/Element/WebformExcludedBase.php | 3 + .../src/Element/WebformExcludedColumns.php | 6 + .../webform/src/Element/WebformHelp.php | 2 + .../webform/src/Element/WebformHtmlEditor.php | 48 +- .../src/Element/WebformImageResolution.php | 119 + .../webform/src/Element/WebformLikert.php | 53 +- ...rmLocation.php => WebformLocationBase.php} | 129 +- .../Element/WebformLocationGeocomplete.php | 64 + .../src/Element/WebformLocationPlaces.php | 64 + .../src/Element/WebformManagedFileBase.php | 2 +- .../webform/src/Element/WebformMapping.php | 7 +- .../webform/src/Element/WebformMarkup.php | 41 +- .../webform/src/Element/WebformMultiple.php | 299 +- .../webform/src/Element/WebformOptions.php | 101 +- .../webform/src/Element/WebformOtherBase.php | 148 +- .../src/Element/WebformPermissions.php | 5 +- .../webform/src/Element/WebformRating.php | 34 +- .../src/Element/WebformSelectOther.php | 1 + .../webform/src/Element/WebformSignature.php | 11 + .../Element/WebformSubmissionInformation.php | 59 + .../Element/WebformSubmissionNavigation.php | 24 + .../src/Element/WebformSubmissionViews.php | 190 + .../Element/WebformSubmissionViewsReplace.php | 116 + .../src/Element/WebformTableSelectSort.php | 24 +- .../webform/src/Element/WebformTableSort.php | 9 +- .../src/Element/WebformTermCheckboxes.php | 8 +- .../src/Element/WebformTermReferenceTrait.php | 23 +- .../src/Element/WebformTermsOfService.php | 26 +- .../webform/src/Element/WebformTime.php | 74 +- web/modules/webform/src/Entity/Webform.php | 526 +-- .../webform/src/Entity/WebformOptions.php | 17 +- .../webform/src/Entity/WebformSubmission.php | 73 +- .../WebformEntitySettingsAccessForm.php | 80 +- .../WebformEntitySettingsAssetsForm.php | 3 + .../WebformEntitySettingsBaseForm.php | 22 +- .../WebformEntitySettingsConfirmationForm.php | 35 +- .../WebformEntitySettingsFormForm.php | 230 +- .../WebformEntitySettingsGeneralForm.php | 148 +- .../WebformEntitySettingsSubmissionsForm.php | 429 ++- .../WebformExceptionHtmlSubscriber.php | 325 ++ .../src/EventSubscriber/WebformSubscriber.php | 220 -- .../WebformAdminConfigAdvancedForm.php | 121 +- .../WebformAdminConfigBaseForm.php | 67 +- .../WebformAdminConfigElementsForm.php | 184 +- .../WebformAdminConfigExportersForm.php | 60 +- .../WebformAdminConfigFormsForm.php | 212 +- .../WebformAdminConfigHandlersForm.php | 22 +- .../WebformAdminConfigLibrariesForm.php | 114 +- .../WebformAdminConfigSubmissionsForm.php | 120 +- .../webform/src/Form/WebformAjaxFormTrait.php | 122 +- .../WebformConfigEntityDeleteFormBase.php | 199 ++ .../src/Form/WebformContributeForm.php | 32 +- .../src/Form/WebformDeleteFormBase.php | 105 + .../src/Form/WebformDeleteFormInterface.php | 41 + .../src/Form/WebformDialogFormTrait.php | 55 +- .../src/Form/WebformEntityFilterForm.php | 10 +- .../src/Form/WebformHandlerAddForm.php | 15 +- .../src/Form/WebformHandlerDeleteForm.php | 57 +- .../src/Form/WebformHandlerEditForm.php | 6 + .../src/Form/WebformHandlerFormBase.php | 131 +- .../webform/src/Form/WebformHelpVideoForm.php | 18 +- .../src/Form/WebformOptionsFilterForm.php | 88 + .../src/Form/WebformResultsClearForm.php | 84 +- .../src/Form/WebformResultsCustomForm.php | 35 +- .../src/Form/WebformResultsExportForm.php | 9 +- .../src/Form/WebformSubmissionDeleteForm.php | 71 +- .../Form/WebformSubmissionDeleteMultiple.php | 8 +- .../src/Form/WebformSubmissionFilterForm.php | 35 +- .../src/Form/WebformSubmissionResendForm.php | 6 +- .../Form/WebformSubmissionsDeleteFormBase.php | 51 +- .../src/Form/WebformSubmissionsPurgeForm.php | 97 +- .../Plugin/Action/DeleteWebformSubmission.php | 8 +- .../webform/src/Plugin/Block/WebformBlock.php | 81 +- .../Block/WebformSubmissionLimitBlock.php | 2 +- .../webform/src/Plugin/Condition/Webform.php | 7 +- .../WebformSubmissionDevelGenerate.php | 31 +- .../WebformSelection.php | 2 + .../WebformEntityReferenceEntityFormatter.php | 148 +- .../WebformEntityReferenceFormatterBase.php | 44 +- .../WebformEntityReferenceLinkFormatter.php | 92 +- .../WebformEntityReferenceUrlFormatter.php | 52 + .../FieldType/WebformEntityReferenceItem.php | 23 +- ...bformEntityReferenceAutocompleteWidget.php | 213 +- .../WebformEntityReferenceSelectWidget.php | 126 +- .../WebformEntityReferenceWidgetTrait.php | 271 ++ .../src/Plugin/Mail/WebformPhpMail.php | 13 + .../LocalAction/WebformDialogLocalAction.php | 37 + .../src/Plugin/WebformElement/Address.php | 429 +++ .../src/Plugin/WebformElement/Captcha.php | 1 - .../src/Plugin/WebformElement/Checkbox.php | 43 + .../src/Plugin/WebformElement/Checkboxes.php | 24 +- .../src/Plugin/WebformElement/Container.php | 4 + .../Plugin/WebformElement/ContainerBase.php | 74 +- .../src/Plugin/WebformElement/Date.php | 42 +- .../src/Plugin/WebformElement/DateBase.php | 216 +- .../src/Plugin/WebformElement/DateList.php | 39 +- .../src/Plugin/WebformElement/DateTime.php | 132 +- .../src/Plugin/WebformElement/Details.php | 19 +- .../src/Plugin/WebformElement/Email.php | 5 +- .../WebformElement/EntityAutocomplete.php | 13 +- .../src/Plugin/WebformElement/Fieldset.php | 7 + .../src/Plugin/WebformElement/Hidden.php | 34 +- .../src/Plugin/WebformElement/Item.php | 5 +- .../src/Plugin/WebformElement/NumericBase.php | 4 +- .../src/Plugin/WebformElement/OptionsBase.php | 106 +- .../Plugin/WebformElement/PasswordConfirm.php | 44 +- .../Plugin/WebformElement/ProcessedText.php | 3 +- .../src/Plugin/WebformElement/Radios.php | 2 + .../src/Plugin/WebformElement/Range.php | 20 +- .../src/Plugin/WebformElement/Select.php | 65 +- .../src/Plugin/WebformElement/Table.php | 3 +- .../src/Plugin/WebformElement/Telephone.php | 146 +- .../src/Plugin/WebformElement/TextBase.php | 321 +- .../Plugin/WebformElement/TextBaseTrait.php | 105 + .../src/Plugin/WebformElement/TextField.php | 9 +- .../src/Plugin/WebformElement/TextFormat.php | 50 +- .../src/Plugin/WebformElement/Textarea.php | 21 +- .../webform/src/Plugin/WebformElement/Url.php | 5 +- .../src/Plugin/WebformElement/Value.php | 10 + .../Plugin/WebformElement/WebformActions.php | 18 +- .../Plugin/WebformElement/WebformAddress.php | 7 + .../WebformElement/WebformCheckboxesOther.php | 31 +- .../WebformElement/WebformCodeMirror.php | 6 + .../WebformElement/WebformCompositeBase.php | 458 ++- .../WebformElement/WebformComputedBase.php | 105 +- .../WebformElement/WebformComputedTwig.php | 35 +- .../WebformElement/WebformCustomComposite.php | 35 +- .../Plugin/WebformElement/WebformElement.php | 19 +- .../WebformElement/WebformEmailConfirm.php | 9 + .../WebformEntityOptionsTrait.php | 8 + .../WebformEntityReferenceTrait.php | 55 +- .../WebformElement/WebformHorizontalRule.php | 2 +- .../WebformElement/WebformImageFile.php | 60 +- .../Plugin/WebformElement/WebformLikert.php | 31 +- .../src/Plugin/WebformElement/WebformLink.php | 15 + .../WebformElement/WebformLocationBase.php | 162 + ...ion.php => WebformLocationGeocomplete.php} | 177 +- .../WebformElement/WebformLocationPlaces.php | 97 + .../WebformElement/WebformManagedFileBase.php | 554 ++- .../Plugin/WebformElement/WebformMapping.php | 3 + .../WebformElement/WebformMarkupBase.php | 1 + .../Plugin/WebformElement/WebformRating.php | 9 + .../Plugin/WebformElement/WebformSection.php | 7 + .../WebformElement/WebformTableTrait.php | 136 +- .../WebformTermReferenceTrait.php | 10 + .../src/Plugin/WebformElement/WebformTime.php | 35 +- .../Plugin/WebformElement/WebformToggle.php | 2 + .../WebformElement/WebformToggleTrait.php | 11 + .../Plugin/WebformElement/WebformToggles.php | 12 +- .../WebformElement/WebformWizardPage.php | 24 +- .../WebformElementAttachmentInterface.php | 32 + .../webform/src/Plugin/WebformElementBase.php | 686 ++-- .../src/Plugin/WebformElementInterface.php | 58 +- .../src/Plugin/WebformElementManager.php | 19 + .../Plugin/WebformElementManagerInterface.php | 30 +- .../TabularBaseWebformExporter.php | 13 +- .../src/Plugin/WebformExporterBase.php | 9 +- .../src/Plugin/WebformExporterInterface.php | 8 + .../WebformHandler/ActionWebformHandler.php | 41 +- .../WebformHandler/DebugWebformHandler.php | 10 +- .../WebformHandler/EmailWebformHandler.php | 571 ++- .../RemotePostWebformHandler.php | 158 +- .../WebformHandler/SettingsWebformHandler.php | 31 +- .../webform/src/Plugin/WebformHandlerBase.php | 213 +- .../src/Plugin/WebformHandlerInterface.php | 47 +- .../QueryStringWebformSourceEntity.php | 53 +- .../RouteParametersWebformSourceEntity.php | 30 +- .../Plugin/WebformSourceEntityInterface.php | 4 +- .../src/Plugin/WebformSourceEntityManager.php | 53 +- .../WebformSourceEntityManagerInterface.php | 4 +- .../src/Routing/WebformRouteSubscriber.php | 2 +- .../Access/WebformAccessEntityJsonApiTest.php | 81 + .../WebformAccessEntityPermissionsTest.php | 105 + .../Access/WebformAccessEntityRestTest.php | 80 + .../WebformAccessEntityRulesTest.php} | 204 +- ...WebformAccessSubmissionPermissionsTest.php | 239 ++ .../Tests/Block/WebformBlockContextTest.php | 8 +- .../src/Tests/Block/WebformBlockTest.php | 15 +- .../WebformCompositeCustomFileTest.php | 62 + .../Composite/WebformCompositeCustomTest.php | 9 +- .../Composite/WebformCompositeFormatTest.php | 119 +- .../WebformCompositePluginFileTest.php | 103 + .../Composite/WebformCompositePluginTest.php | 6 +- .../Tests/Composite/WebformCompositeTest.php | 11 +- .../Element/WebformElementAccessTest.php | 70 +- .../Element/WebformElementActionsTest.php | 14 +- .../Element/WebformElementAddressTest.php | 155 + .../Element/WebformElementAllowsTagsTest.php | 8 +- .../WebformElementAutocompleteTest.php | 22 +- .../Element/WebformElementCaptchaTest.php | 94 + .../Element/WebformElementCheckboxesTest.php | 49 + .../Element/WebformElementCodeMirrorTest.php | 15 +- .../Element/WebformElementCompositeTest.php | 50 +- .../Element/WebformElementComputedTest.php | 68 +- .../Element/WebformElementCounterTest.php | 58 + .../WebformElementCustomPropertiesTest.php | 6 +- .../Element/WebformElementDateListTest.php | 51 +- .../Tests/Element/WebformElementDateTest.php | 4 +- .../Element/WebformElementDateTimeTest.php | 46 +- .../Element/WebformElementDetailsTest.php | 37 + .../Tests/Element/WebformElementEmailTest.php | 10 +- .../WebformElementEntityReferenceTest.php | 2 +- .../Element/WebformElementFieldsetTest.php | 43 + .../WebformElementFormatCustomTest.php | 48 +- .../Element/WebformElementFormatTest.php | 30 +- .../Tests/Element/WebformElementHelpTest.php | 26 +- .../WebformElementHorizontalRuleTest.php | 2 +- .../Element/WebformElementHtmlEditorTest.php | 51 +- .../Element/WebformElementIcheckTest.php | 4 +- .../WebformElementImageResolutionTest.php | 44 + .../Element/WebformElementInputMaskTest.php | 96 + .../Element/WebformElementLikertTest.php | 32 +- .../WebformElementLocationPlacesTest.php | 78 + .../WebformElementManagedFileImageTest.php | 48 + .../WebformElementManagedFileLimitTest.php | 84 + .../WebformElementManagedFilePreviewTest.php | 58 + .../WebformElementManagedFilePrivateTest.php | 40 +- .../WebformElementManagedFilePublicTest.php | 10 +- .../Element/WebformElementManagedFileTest.php | 51 +- .../WebformElementManagedFileTestBase.php | 22 +- .../Element/WebformElementMappingTest.php | 12 +- .../Element/WebformElementMarkupTest.php | 19 +- .../Element/WebformElementMediaFileTest.php | 2 +- .../Element/WebformElementMessageTest.php | 18 +- .../Tests/Element/WebformElementMoreTest.php | 24 +- .../Element/WebformElementMultipleTest.php | 43 +- .../Element/WebformElementOptionsTest.php | 14 +- .../Tests/Element/WebformElementOtherTest.php | 60 +- .../Element/WebformElementPatternTest.php | 52 + .../Element/WebformElementPluginTest.php | 8 +- .../Element/WebformElementPrepopulateTest.php | 8 +- .../Element/WebformElementPrivateTest.php | 25 +- .../Element/WebformElementRadiosTest.php | 8 +- .../Tests/Element/WebformElementRangeTest.php | 6 +- .../Element/WebformElementRatingTest.php | 27 +- .../Element/WebformElementReadonlyTest.php | 2 +- .../Element/WebformElementSectionTest.php | 26 +- .../Element/WebformElementSelectTest.php | 6 +- .../Element/WebformElementSignatureTest.php | 5 +- .../WebformElementStatesSelectorsTest.php | 6 +- .../Element/WebformElementStatesTest.php | 39 +- ...bformElementSubmissionViewsReplaceTest.php | 84 + .../WebformElementSubmissionViewsTest.php | 139 + .../Tests/Element/WebformElementTableTest.php | 2 +- .../Element/WebformElementTelephoneTest.php | 69 + .../WebformElementTermReferenceTest.php | 8 +- .../WebformElementTermsOfServiceTest.php | 15 +- .../Element/WebformElementTextFormatTest.php | 145 +- .../Tests/Element/WebformElementTextTest.php | 68 - .../Tests/Element/WebformElementTimeTest.php | 44 +- .../Element/WebformElementToggleTest.php | 16 +- .../WebformElementValidateMinlengthTest.php | 9 + .../WebformElementValidateMultipleTest.php | 13 +- .../WebformElementValidateUniqueTest.php | 27 +- .../Exporter/WebformExporterExcludedTest.php | 4 +- .../Tests/Form/WebformFormPropertiesTest.php | 6 +- .../Handler/WebformHandlerActionTest.php | 2 +- .../Handler/WebformHandlerConditionsTest.php | 2 +- .../WebformHandlerEmailAdvancedTest.php | 59 +- .../Handler/WebformHandlerEmailBasicTest.php | 58 +- .../WebformHandlerEmailMappingTest.php | 2 +- .../WebformHandlerEmailRenderingTest.php | 4 +- .../Handler/WebformHandlerEmailRolesTest.php | 8 +- .../Handler/WebformHandlerEmailStatesTest.php | 11 +- .../Handler/WebformHandlerEmailTwigTest.php | 1 + .../Handler/WebformHandlerExcludedTest.php | 25 +- .../WebformHandlerInvokeAlterHookTest.php | 43 + .../Handler/WebformHandlerPluginTest.php | 2 +- .../Handler/WebformHandlerRemotePostTest.php | 90 +- .../Handler/WebformHandlerSettingsTest.php | 4 +- .../src/Tests/Handler/WebformHandlerTest.php | 55 +- .../WebformSettingsAccessDeniedTest.php | 178 + .../Settings/WebformSettingsAdminTest.php | 33 +- .../Settings/WebformSettingsArchivedTest.php | 83 + .../Settings/WebformSettingsAssetsTest.php | 6 +- .../WebformSettingsAutofillTest.php} | 15 +- .../Settings/WebformSettingsBehaviorsTest.php | 177 +- .../WebformSettingsConfidentialTest.php | 39 +- .../WebformSettingsConfirmationTest.php} | 40 +- .../WebformSettingsDraftTest.php} | 142 +- .../Settings/WebformSettingsFormTitleTest.php | 89 + .../WebformSettingsLimitUniqueTest.php | 164 + .../WebformSettingsLimitsTest.php} | 35 +- .../Settings/WebformSettingsLoginTest.php | 55 - .../Settings/WebformSettingsPathTest.php | 78 +- .../WebformSettingsPrepopulateTest.php | 23 +- .../WebformSettingsPreviewTest.php} | 77 +- .../Settings/WebformSettingsPreviousTest.php | 76 + .../WebformSettingsRemoteAddrTest.php | 36 + .../Settings/WebformSettingsScheduleTest.php | 18 +- .../Settings/WebformSettingsStatusTest.php | 47 + .../Tests/States/WebformStatesPreviewTest.php | 95 + .../WebformStatesServerTest.php} | 235 +- .../Tests/States/WebformStatesWizardTest.php | 203 ++ .../Tests/Views/WebformViewsBulkFormTest.php | 24 +- .../src/Tests/WebformAlterHooksTest.php | 2 +- .../webform/src/Tests/WebformEditorTest.php | 258 ++ .../src/Tests/WebformEmailProviderTest.php | 53 + .../Tests/WebformEntityTranslationTest.php | 146 +- .../webform/src/Tests/WebformHelpTest.php | 26 +- .../src/Tests/WebformLibrariesTest.php | 106 +- .../webform/src/Tests/WebformOptionsTest.php | 40 +- .../src/Tests/WebformRenderingTest.php | 2 +- .../src/Tests/WebformResultsDisabledTest.php | 18 +- .../WebformResultsExportDownloadTest.php | 10 - .../Tests/WebformResultsExportOptionsTest.php | 30 +- .../src/Tests/WebformSubmissionAccessTest.php | 146 - .../src/Tests/WebformSubmissionApiTest.php | 27 +- .../Tests/WebformSubmissionGenerateTest.php | 6 +- .../WebformSubmissionListBuilderTest.php | 95 +- .../Tests/WebformSubmissionStorageTest.php | 4 + .../src/Tests/WebformSubmissionTest.php | 33 +- .../WebformSubmissionTokenUpdateTest.php | 20 +- .../src/Tests/WebformSubmissionViewTest.php | 14 +- .../src/Tests/WebformSubmissionViewsTest.php | 182 + .../webform/src/Tests/WebformTestBase.php | 214 +- .../webform/src/Tests/WebformTestTrait.php | 1 + .../Tests/WebformThirdPartySettingsTest.php | 14 +- .../Tests/WebformTokenSubmissionValueTest.php | 49 +- .../src/Tests/WebformTokenSuffixesTest.php | 98 + .../Tests/Wizard/WebformWizardAccessTest.php | 4 +- .../Wizard/WebformWizardAdvancedTest.php | 16 +- .../Tests/Wizard/WebformWizardBasicTest.php | 6 +- .../Wizard/WebformWizardConditionalTest.php | 2 +- .../Tests/Wizard/WebformWizardCustomTest.php | 2 +- .../Tests/Wizard/WebformWizardLinksTest.php | 85 + .../Wizard/WebformWizardValidateTest.php | 3 +- .../webform/src/Twig/TwigExtension.php | 110 +- web/modules/webform/src/Utility/Mail.php | 71 + .../Utility/WebformAccessibilityHelper.php | 72 + .../src/Utility/WebformArrayHelper.php | 19 + .../webform/src/Utility/WebformDateHelper.php | 100 +- .../src/Utility/WebformDialogHelper.php | 13 +- .../src/Utility/WebformElementHelper.php | 188 +- .../webform/src/Utility/WebformFormHelper.php | 5 +- .../src/Utility/WebformObjectHelper.php | 25 + .../src/Utility/WebformOptionsHelper.php | 2 +- .../webform/src/Utility/WebformTextHelper.php | 82 + .../webform/src/Utility/WebformXss.php | 40 + .../webform/src/Utility/WebformYaml.php | 86 +- .../webform/src/WebformAccessRulesManager.php | 193 + .../WebformAccessRulesManagerInterface.php | 119 + .../webform/src/WebformAddonsManager.php | 527 ++- .../webform/src/WebformContributeManager.php | 3 +- .../webform/src/WebformEmailProvider.php | 18 +- .../src/WebformEntityAccessControlHandler.php | 211 +- .../webform/src/WebformEntityAddForm.php | 13 +- .../webform/src/WebformEntityDeleteForm.php | 38 +- .../webform/src/WebformEntityElementsForm.php | 10 +- .../src/WebformEntityElementsValidator.php | 140 +- ...ebformEntityElementsValidatorInterface.php | 4 +- .../WebformEntityExportForm.php} | 4 +- .../webform/src/WebformEntityHandlersForm.php | 120 +- .../webform/src/WebformEntityListBuilder.php | 411 ++- .../src/WebformEntityReferenceManager.php | 79 +- ...WebformEntityReferenceManagerInterface.php | 16 +- .../webform/src/WebformEntityStorage.php | 111 +- .../src/WebformEntityStorageInterface.php | 9 + .../webform/src/WebformEntityViewBuilder.php | 7 +- .../webform/src/WebformHelpManager.php | 530 ++- .../src/WebformHelpManagerInterface.php | 6 +- web/modules/webform/src/WebformInterface.php | 181 +- .../webform/src/WebformLibrariesManager.php | 132 +- .../webform/src/WebformMessageManager.php | 175 +- .../src/WebformMessageManagerInterface.php | 66 +- .../WebformOptionsAccessControlHandler.php | 2 +- .../webform/src/WebformOptionsDeleteForm.php | 95 +- .../webform/src/WebformOptionsForm.php | 18 +- .../webform/src/WebformOptionsListBuilder.php | 161 +- .../webform/src/WebformOptionsStorage.php | 10 +- web/modules/webform/src/WebformRequest.php | 20 +- .../webform/src/WebformRequestInterface.php | 26 + .../WebformSubmissionAccessControlHandler.php | 103 +- .../WebformSubmissionConditionsValidator.php | 544 ++- ...SubmissionConditionsValidatorInterface.php | 20 +- .../webform/src/WebformSubmissionExporter.php | 109 +- .../WebformSubmissionExporterInterface.php | 2 +- .../webform/src/WebformSubmissionForm.php | 699 +++- .../webform/src/WebformSubmissionGenerate.php | 18 +- .../src/WebformSubmissionInterface.php | 21 +- .../src/WebformSubmissionListBuilder.php | 929 +++-- .../src/WebformSubmissionNotesForm.php | 11 +- .../webform/src/WebformSubmissionStorage.php | 429 ++- .../src/WebformSubmissionStorageInterface.php | 124 +- .../src/WebformSubmissionStorageSchema.php | 71 +- .../src/WebformSubmissionViewBuilder.php | 49 +- .../src/WebformSubmissionViewsData.php | 2 + .../webform/src/WebformThemeManager.php | 68 +- .../src/WebformThemeManagerInterface.php | 23 +- .../src/WebformThirdPartySettingsManager.php | 2 +- .../webform/src/WebformTokenManager.php | 336 +- .../src/WebformTokenManagerInterface.php | 51 +- .../webform/src/WebformTranslationManager.php | 62 +- .../WebformTranslationManagerInterface.php | 8 + .../webform-composite-address.html.twig | 1 - .../webform-composite-contact.html.twig | 1 - .../webform-composite-link.html.twig | 1 - .../webform-composite-location.html.twig | 1 - .../webform-composite-name.html.twig | 1 - .../webform-composite-telephone.html.twig | 2 - .../templates/webform-confirmation.html.twig | 2 +- .../templates/webform-element-help.html.twig | 2 +- .../templates/webform-element-more.html.twig | 9 +- .../templates/webform-email-html.html.twig | 16 + .../webform-email-message-html.html.twig | 2 +- .../webform-email-message-text.html.twig | 2 +- .../webform-handler-email-summary.html.twig | 3 +- .../webform-help-video-youtube.html.twig | 2 +- .../webform-html-editor-markup.html.twig | 21 + .../webform-progress-tracker.html.twig | 38 +- .../webform-submission-form.html.twig | 12 + .../webform-submission-information.html.twig | 120 +- .../webform-submission-navigation.html.twig | 2 + .../templates/webform-submission.html.twig | 4 +- web/modules/webform/tests/files/sample.html | 4 +- .../install/webform.webform.test_ajax.yml | 34 +- ....webform.test_ajax_confirmation_inline.yml | 34 +- ...webform.test_ajax_confirmation_message.yml | 34 +- ...m.webform.test_ajax_confirmation_modal.yml | 34 +- ...rm.webform.test_ajax_confirmation_page.yml | 34 +- ...orm.webform.test_ajax_confirmation_url.yml | 34 +- ...webform.test_ajax_confirmation_url_msg.yml | 34 +- .../webform.webform.test_composite.yml | 40 +- .../webform.webform.test_composite_custom.yml | 54 +- ...orm.webform.test_composite_custom_file.yml | 200 ++ .../webform.webform.test_composite_format.yml | 141 +- ...webform.test_composite_format_multiple.yml | 296 +- ...bform.webform.test_confirmation_inline.yml | 34 +- ...form.webform.test_confirmation_message.yml | 34 +- ...ebform.webform.test_confirmation_modal.yml | 34 +- ...webform.webform.test_confirmation_none.yml | 176 + ...webform.webform.test_confirmation_page.yml | 34 +- ....webform.test_confirmation_page_custom.yml | 34 +- .../webform.webform.test_confirmation_url.yml | 34 +- ....webform.test_confirmation_url_message.yml | 34 +- .../install/webform.webform.test_element.yml | 38 +- .../webform.webform.test_element_access.yml | 46 +- .../webform.webform.test_element_actions.yml | 34 +- ...m.webform.test_element_actions_buttons.yml | 34 +- .../webform.webform.test_element_address.yml | 234 ++ ...form.webform.test_element_allowed_tags.yml | 36 +- ...ebform.webform.test_element_attributes.yml | 35 +- ...form.webform.test_element_autocomplete.yml | 36 +- .../webform.webform.test_element_buttons.yml | 34 +- .../webform.webform.test_element_captcha.yml | 39 +- ...rm.webform.test_element_checkbox_value.yml | 37 +- ...ebform.webform.test_element_checkboxes.yml | 45 +- ...ebform.webform.test_element_codemirror.yml | 44 +- ...webform.webform.test_element_composite.yml | 36 +- ...webform.test_element_composite_wrapper.yml | 297 ++ ...orm.webform.test_element_computed_ajax.yml | 221 ++ ...rm.webform.test_element_computed_token.yml | 46 +- ...orm.webform.test_element_computed_twig.yml | 59 +- ...webform.webform.test_element_container.yml | 36 +- .../webform.webform.test_element_counter.yml | 243 ++ .../webform.webform.test_element_date.yml | 67 +- .../webform.webform.test_element_datelist.yml | 88 +- .../webform.webform.test_element_datetime.yml | 108 +- ...bform.test_element_description_tooltip.yml | 120 +- .../webform.webform.test_element_details.yml | 202 ++ .../webform.webform.test_element_disabled.yml | 58 +- .../webform.webform.test_element_email.yml | 34 +- ....webform.test_element_entity_reference.yml | 34 +- .../webform.webform.test_element_fieldset.yml | 211 ++ .../webform.webform.test_element_flexbox.yml | 250 +- ...form.webform.test_element_flexbox_flex.yml | 34 +- .../webform.webform.test_element_format.yml | 144 +- ...orm.webform.test_element_format_custom.yml | 121 +- ...m.webform.test_element_format_multiple.yml | 155 +- ...form.webform.test_element_format_token.yml | 56 +- .../webform.webform.test_element_help.yml | 39 +- ...m.webform.test_element_horizontal_rule.yml | 34 +- ...bform.webform.test_element_html_editor.yml | 39 +- ...bform.webform.test_element_html_escape.yml | 56 +- ...bform.webform.test_element_html_markup.yml | 56 +- .../webform.webform.test_element_icheck.yml | 42 +- ...orm.webform.test_element_icheck_styles.yml | 114 +- ...ebform.test_element_ignored_properties.yml | 34 +- ...bform.webform.test_element_image_file.yml} | 72 +- ...webform.test_element_image_resolution.yml} | 109 +- ...ebform.webform.test_element_input_mask.yml | 239 ++ .../webform.webform.test_element_invalid.yml | 39 +- .../webform.webform.test_element_likert.yml | 61 +- ....webform.test_element_loc_geocomplete.yml} | 62 +- ...ebform.webform.test_element_loc_places.yml | 212 ++ ...form.webform.test_element_managed_file.yml | 45 +- ....webform.test_element_managed_file_dis.yml | 34 +- ...webform.test_element_managed_file_help.yml | 228 ++ ...ebform.test_element_managed_file_limit.yml | 183 + ...webform.test_element_managed_file_name.yml | 34 +- ...webform.test_element_managed_file_prev.yml | 217 ++ .../webform.webform.test_element_mapping.yml | 36 +- .../webform.webform.test_element_markup.yml | 38 +- ...ebform.webform.test_element_media_file.yml | 34 +- .../webform.webform.test_element_message.yml | 34 +- .../webform.webform.test_element_more.yml | 36 +- .../webform.webform.test_element_multiple.yml | 62 +- ...orm.webform.test_element_multiple_date.yml | 40 +- ...webform.test_element_multiple_property.yml | 38 +- ...orm.webform.test_element_multiple_text.yml | 34 +- .../webform.webform.test_element_options.yml | 44 +- .../webform.webform.test_element_other.yml | 81 +- .../webform.webform.test_element_pattern.yml | 195 + ...bform.webform.test_element_prepopulate.yml | 38 +- .../webform.webform.test_element_private.yml | 34 +- .../webform.webform.test_element_radios.yml | 35 +- .../webform.webform.test_element_range.yml | 40 +- .../webform.webform.test_element_rating.yml | 42 +- .../webform.webform.test_element_readonly.yml | 34 +- .../webform.webform.test_element_section.yml | 59 +- .../webform.webform.test_element_select.yml | 107 +- ...webform.webform.test_element_signature.yml | 34 +- .../webform.webform.test_element_states.yml | 44 +- ....webform.test_element_submission_views.yml | 210 ++ ...ebform.test_element_submission_views_r.yml | 204 ++ ...m.webform.test_element_submitted_value.yml | 34 +- .../webform.webform.test_element_table.yml | 34 +- ...webform.webform.test_element_telephone.yml | 44 +- ...rm.webform.test_element_term_reference.yml | 38 +- ....webform.test_element_terms_of_service.yml | 40 +- ...bform.webform.test_element_text_format.yml | 35 +- .../webform.webform.test_element_time.yml | 44 +- .../webform.webform.test_element_toggle.yml | 34 +- ...bform.webform.test_element_users_roles.yml | 34 +- ...ebform.test_element_validate_minlength.yml | 40 +- ...webform.test_element_validate_multiple.yml | 38 +- ...webform.test_element_validate_required.yml | 34 +- ...m.webform.test_element_validate_unique.yml | 43 +- .../webform.webform.test_example_elements.yml | 56 +- ...ebform.test_example_elements_composite.yml | 93 +- ...webform.test_exporter_entity_reference.yml | 34 +- .../webform.webform.test_exporter_options.yml | 34 +- ...ebform.webform.test_form_access_denied.yml | 173 + .../install/webform.webform.test_form_api.yml | 34 +- ...=> webform.webform.test_form_archived.yml} | 45 +- .../webform.webform.test_form_assets.yml | 36 +- .../webform.webform.test_form_autofill.yml | 34 +- .../webform.webform.test_form_autofocus.yml | 34 +- .../webform.webform.test_form_closed.yml | 34 +- ...webform.webform.test_form_confidential.yml | 34 +- ...bform.webform.test_form_details_toggle.yml | 34 +- ...webform.test_form_disable_autocomplete.yml | 34 +- ...webform.webform.test_form_disable_back.yml | 34 +- ...ebform.test_form_disable_inline_errors.yml | 34 +- ...form.webform.test_form_draft_anonymous.yml | 36 +- ....webform.test_form_draft_authenticated.yml | 36 +- ...bform.webform.test_form_draft_multiple.yml | 36 +- ...ebform.webform.test_form_inline_errors.yml | 34 +- .../webform.webform.test_form_limit.yml | 34 +- ...m.webform.test_form_limit_total_unique.yml | 174 + ...rm.webform.test_form_limit_user_unique.yml | 174 + .../webform.webform.test_form_long_100.yml | 34 +- .../webform.webform.test_form_long_200.yml | 34 +- .../webform.webform.test_form_long_300.yml | 34 +- .../webform.webform.test_form_novalidate.yml | 34 +- .../webform.webform.test_form_opening.yml | 34 +- .../webform.webform.test_form_prepopulate.yml | 34 +- .../webform.webform.test_form_preview.yml | 83 +- .../webform.webform.test_form_properties.yml | 34 +- .../webform.webform.test_form_remote_addr.yml | 174 + .../webform.webform.test_form_required.yml | 34 +- .../webform.webform.test_form_reset.yml | 34 +- ...orm.webform.test_form_results_disabled.yml | 34 +- .../webform.webform.test_form_submit_back.yml | 34 +- .../webform.webform.test_form_submit_once.yml | 34 +- .../webform.webform.test_form_submit_text.yml | 34 +- .../webform.webform.test_form_template.yml | 34 +- .../webform.webform.test_form_unsaved.yml | 42 +- ...bform.webform.test_form_unsaved_wizard.yml | 183 + .../webform.webform.test_form_validate.yml | 34 +- ...ebform.webform.test_form_wizard_access.yml | 34 +- ...form.webform.test_form_wizard_advanced.yml | 34 +- ...webform.webform.test_form_wizard_basic.yml | 34 +- ...m.webform.test_form_wizard_conditional.yml | 48 +- ...ebform.webform.test_form_wizard_custom.yml | 34 +- ...webform.webform.test_form_wizard_links.yml | 185 + ...form.webform.test_form_wizard_long_100.yml | 34 +- ...form.webform.test_form_wizard_long_200.yml | 34 +- ...form.webform.test_form_wizard_long_300.yml | 34 +- ...form.webform.test_form_wizard_validate.yml | 34 +- ...webform.test_form_wizard_validate_comp.yml | 34 +- .../webform.webform.test_handler_action.yml | 35 +- .../webform.webform.test_handler_email.yml | 38 +- ...rm.webform.test_handler_email_advanced.yml | 40 +- ...orm.webform.test_handler_email_mapping.yml | 74 +- ...bform.webform.test_handler_email_roles.yml | 46 +- ...form.webform.test_handler_email_states.yml | 116 +- ...ebform.webform.test_handler_email_twig.yml | 37 +- .../webform.webform.test_handler_settings.yml | 40 +- ...ebform.webform.test_libraries_optional.yml | 36 +- .../webform.webform.test_rendering.yml | 50 +- ...es.yml => webform.webform.test_states.yml} | 618 ++-- ...bform.webform.test_states_autocomplete.yml | 315 ++ .../webform.webform.test_states_crosspage.yml | 201 ++ ...bform.webform.test_states_server_clear.yml | 266 ++ ...bform.webform.test_states_server_comp.yml} | 54 +- ....webform.test_states_server_containers.yml | 229 ++ ...orm.webform.test_states_server_custom.yml} | 40 +- ...m.webform.test_states_server_multiple.yml} | 38 +- ...form.webform.test_states_server_nested.yml | 214 ++ ...rm.webform.test_states_server_preview.yml} | 36 +- ...m.webform.test_states_server_required.yml} | 62 +- ...ebform.webform.test_states_server_save.yml | 234 ++ ...orm.webform.test_states_server_wizard.yml} | 60 +- ... webform.webform.test_states_triggers.yml} | 36 +- .../webform.webform.test_submission_label.yml | 34 +- .../webform.webform.test_submission_log.yml | 34 +- .../webform.webform.test_submission_views.yml | 204 ++ .../install/webform.webform.test_token.yml | 48 +- ...rm.webform.test_token_submission_value.yml | 90 +- .../webform.webform.test_token_update.yml | 44 +- ..._test.test_element_description_tooltip.inc | 4 +- .../webform_test.test_element_format.inc | 5 +- ...ebform_test.test_element_icheck_styles.inc | 2 +- .../webform_test.test_example_elements.inc | 4 +- .../webform_test.test_form_states.inc | 10 +- .../webform_test/webform_test.info.yml | 6 +- .../modules/webform_test/webform_test.module | 47 +- .../block.block.bartik_webform_test_ajax.yml | 7 +- .../src/Plugin/Block/WebformTestAjaxBlock.php | 36 +- .../webform_test_ajax.info.yml | 6 +- .../webform_test_alter_hooks.info.yml | 6 +- .../webform_test_alter_hooks.module | 18 +- .../webform_test_block_context.info.yml | 6 +- .../webform_test_block_custom.info.yml | 6 +- ...bform_test_block_submission_limit.info.yml | 6 +- .../webform_test_config_performance.info.yml | 6 +- .../webform_test_custom_properties.info.yml | 6 +- ....webform.test_element_comp_file_plugin.yml | 191 + ....webform.test_element_composite_plugin.yml | 34 +- .../webform.webform.test_element_plugin.yml | 35 +- .../src/Element/WebformTestComposite.php | 1 - .../src/Element/WebformTestCompositeFile.php | 37 + .../WebformTestCompositeFile.php | 28 + .../WebformElement/WebformTestElement.php | 4 +- .../WebformTestElementProperties.php | 4 +- .../webform_test_element.info.yml | 6 +- ...view.webform_test_entity_reference_vs.yml} | 143 +- ...bform.test_element_entity_reference_vs.yml | 229 ++ ...bform_test_entity_reference_views.info.yml | 16 + .../WebformExporter/TestWebformExporter.php | 59 + .../webform_test_exporter.info.yml | 13 + ...ebform.webform.test_handler_conditions.yml | 35 +- .../webform.webform.test_handler_test.yml | 43 +- .../webform_test_handler.webform.schema.yml | 11 +- .../TestEntityMappingWebformHandler.php | 4 +- .../WebformHandler/TestWebformHandler.php | 4 +- .../webform_test_handler.info.yml | 6 +- ...webform_test_handler_invoke_alter.info.yml | 13 + .../webform_test_handler_invoke_alter.module | 31 + ...ebform.webform.test_handler_remote_get.yml | 45 +- ...bform.webform.test_handler_remote_post.yml | 44 +- ....webform.test_handler_remote_post_file.yml | 44 +- ...ebform.webform.test_handler_remote_put.yml | 257 ++ .../WebformTestHandlerRemotePostClient.php | 5 +- .../webform_test_handler_remote_post.info.yml | 6 +- .../webform_test_markup.info.yml | 13 + .../webform_test_markup.module | 17 + .../webform_test_message_custom.info.yml | 6 +- .../webform_test_message_custom.install | 1 - .../install/webform.webform.test_options.yml | 35 +- .../webform_test_options.info.yml | 6 +- .../webform_test_options.module | 2 +- .../webform_test_paragraphs.info.yml | 6 +- .../install/rest.resource.entity.webform.yml | 19 + .../rest.resource.entity.webform_options.yml | 19 + ...est.resource.entity.webform_submission.yml | 19 + .../webform_test_rest.info.yml | 16 + .../webform.webform.test_submissions.yml | 35 +- .../webform_test_submissions.info.yml | 6 +- ...webform_test_third_party_settings.info.yml | 6 +- ...form_test_third_party_settings.webform.inc | 2 +- .../install/language/es/webform.settings.yml | 10 +- .../es/webform.webform.test_translation.yml | 11 +- ...bform.webform_options.test_translation.yml | 1 + .../webform.webform.test_translation.yml | 59 +- .../webform_test_translation.info.yml | 6 +- .../webform_test_translation.install | 60 + .../install/language/es/webform.settings.yml | 2 +- .../install/language/ru/webform.settings.yml | 2 +- ...webform_test_translation_lingotek.info.yml | 6 +- .../webform_test_validate.info.yml | 6 +- .../views.view.webform_test_views_access.yml | 298 ++ ...iews.view.webform_test_views_bulk_form.yml | 47 +- .../webform_test_views.info.yml | 6 +- .../webform_test_wizard_custom.info.yml | 6 +- .../Functional/WebformAssertLegacyTrait.php | 768 ++++ .../src/Functional/WebformBlockCacheTest.php | 10 +- .../src/Functional/WebformBrowserTestBase.php | 475 +++ .../Functional/WebformBrowserTestBaseTest.php | 81 + .../WebformContributeFunctionalTest.php | 4 +- .../src/Functional/WebformListBuilderTest.php | 23 +- .../WebformSubmissionViewsAccessTest.php | 199 ++ .../WebformSubmissionToggleFlagsTest.php | 2 +- .../WebformBreadcrumbBuilderTest.php | 14 +- .../src/Kernel/Entity/WebformEntityTest.php | 14 +- .../Utility/WebformDialogHelperTest.php | 4 +- .../WebformEntityElementsValidationTest.php | 23 +- .../Kernel/WebformSubmissionStorageTest.php | 6 +- .../Unit/Access/WebformAccessCheckTest.php | 114 - .../src/Unit/Access/WebformAccessTestBase.php | 65 + .../Unit/Access/WebformAccountAccessTest.php | 53 + .../Access/WebformSourceEntityAccessTest.php | 68 + .../Access/WebformSubmissionAccessTest.php | 86 + .../Unit/Plugin/Block/WebformBlockTest.php | 83 +- .../QueryStringWebformSourceEntityTest.php | 324 +- .../field/WebformSubmissionBulkFormTest.php | 16 +- .../Unit/Utility/WebformDateHelperTest.php | 54 - .../Unit/Utility/WebformElementHelperTest.php | 1 - .../Unit/Utility/WebformFormHelperTest.php | 12 +- .../Unit/Utility/WebformObjectHelperTest.php | 64 + .../WebformEntityAccessControlHandlerTest.php | 958 +++-- ...ntact--email--email-confirmation.html.twig | 2 +- .../webform-email-message-html.html.twig | 2 +- .../webform_test_bartik.info.yml | 6 +- .../third_party_settings/webform.antibot.inc | 2 +- .../third_party_settings/webform.captcha.inc | 148 + .../third_party_settings/webform.honeypot.inc | 2 +- .../third_party_settings/webform.maillog.inc | 11 +- web/modules/webform/webform.api.php | 167 +- web/modules/webform/webform.info.yml | 15 +- web/modules/webform/webform.install | 10 +- web/modules/webform/webform.libraries.yml | 334 +- web/modules/webform/webform.links.action.yml | 24 + web/modules/webform/webform.links.menu.yml | 5 + web/modules/webform/webform.links.task.yml | 109 +- web/modules/webform/webform.module | 482 ++- web/modules/webform/webform.permissions.yml | 35 +- web/modules/webform/webform.routing.yml | 206 +- web/modules/webform/webform.services.yml | 25 +- web/modules/webform/webform.tokens.inc | 211 +- 1214 files changed, 72143 insertions(+), 13545 deletions(-) create mode 100644 web/modules/webform/.eslintrc.json create mode 100644 web/modules/webform/composer.libraries.json create mode 100644 web/modules/webform/config/install/webform.webform_options.translations.yml create mode 100644 web/modules/webform/config/optional/views.view.webform_submissions.yml create mode 100644 web/modules/webform/config/schema/webform.third_party.captcha.schema.yml create mode 100644 web/modules/webform/css/webform.admin.settings.css create mode 100644 web/modules/webform/css/webform.admin.tabledrag.css create mode 100644 web/modules/webform/css/webform.element.computed.css create mode 100644 web/modules/webform/css/webform.element.counter.css create mode 100644 web/modules/webform/css/webform.element.date.css create mode 100644 web/modules/webform/css/webform.element.datelist.css rename web/modules/webform/css/{webform.element.location.css => webform.element.location.geocomplete.css} (72%) create mode 100644 web/modules/webform/css/webform.element.location.places.css create mode 100644 web/modules/webform/css/webform.element.managed_file.css create mode 100644 web/modules/webform/css/webform.element.mapping.css create mode 100644 web/modules/webform/css/webform.filter.css create mode 100644 web/modules/webform/css/webform.theme.classy.css create mode 100644 web/modules/webform/css/webform.token.css create mode 100644 web/modules/webform/css/webform.wizard.pages.css create mode 100644 web/modules/webform/docs/UPDATE-LIBRARIES.md create mode 100644 web/modules/webform/images/elements/date-calendar.png create mode 100644 web/modules/webform/includes/webform.editor.inc create mode 100644 web/modules/webform/js/webform.admin.tabledrag.js create mode 100644 web/modules/webform/js/webform.announce.js delete mode 100644 web/modules/webform/js/webform.element.buttons.buttonset.js rename web/modules/webform/js/{webform.element.buttons.checkboxradio.js => webform.element.buttons.js} (78%) create mode 100644 web/modules/webform/js/webform.element.computed.js create mode 100644 web/modules/webform/js/webform.element.inputhide.js rename web/modules/webform/js/{webform.element.location.js => webform.element.location.geocomplete.js} (61%) create mode 100644 web/modules/webform/js/webform.element.location.places.js create mode 100644 web/modules/webform/js/webform.element.managed_file.js create mode 100644 web/modules/webform/js/webform.element.states.js create mode 100644 web/modules/webform/js/webform.element.tableselect.js create mode 100644 web/modules/webform/js/webform.filter.js delete mode 100644 web/modules/webform/js/webform.form.wizard.js create mode 100644 web/modules/webform/js/webform.jquery.ui.dialog.js create mode 100644 web/modules/webform/js/webform.off-canvas.js create mode 100644 web/modules/webform/js/webform.wizard.pages.js create mode 100644 web/modules/webform/js/webform.wizard.track.js create mode 100644 web/modules/webform/modules/webform_access/config/schema/webform_access.entity.webform_access_type.schema.yml create mode 100644 web/modules/webform/modules/webform_access/config/schema/webform_access.schema.yml create mode 100644 web/modules/webform/modules/webform_access/js/webform_access.admin.js create mode 100644 web/modules/webform/modules/webform_access/src/Breadcrumb/WebformAccessBreadcrumbBuilder.php create mode 100644 web/modules/webform/modules/webform_access/src/Entity/WebformAccessGroup.php create mode 100644 web/modules/webform/modules/webform_access/src/Entity/WebformAccessType.php create mode 100644 web/modules/webform/modules/webform_access/src/Plugin/Block/WebformAccessGroupEntityBlock.php create mode 100644 web/modules/webform/modules/webform_access/src/Tests/WebformAccessSubmissionViewsTest.php create mode 100644 web/modules/webform/modules/webform_access/src/Tests/WebformAccessTest.php create mode 100644 web/modules/webform/modules/webform_access/src/Tests/WebformAccessTestBase.php create mode 100644 web/modules/webform/modules/webform_access/src/Tests/WebformAccessTokensTest.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupAccessControlHandler.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupDeleteForm.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupForm.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupInterface.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupListBuilder.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupStorage.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessGroupStorageInterface.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeAccessControlHandler.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeDeleteForm.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeForm.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeInterface.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeListBuilder.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeStorage.php create mode 100644 web/modules/webform/modules/webform_access/src/WebformAccessTypeStorageInterface.php create mode 100644 web/modules/webform/modules/webform_access/webform_access.info.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.install create mode 100644 web/modules/webform/modules/webform_access/webform_access.libraries.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.links.action.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.links.task.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.module create mode 100644 web/modules/webform/modules/webform_access/webform_access.routing.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.services.yml create mode 100644 web/modules/webform/modules/webform_access/webform_access.tokens.inc create mode 100644 web/modules/webform/modules/webform_attachment/src/Controller/WebformAttachmentController.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentBase.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentInterface.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentToken.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentTwig.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentUrl.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentBase.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentToken.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentTwig.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentUrl.php create mode 100644 web/modules/webform/modules/webform_attachment/src/Tests/WebformAttachmentTest.php create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_access.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_email.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_sanitize.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_token.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_twig.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_url.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.info.yml create mode 100644 web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.module create mode 100644 web/modules/webform/modules/webform_attachment/webform_attachment.info.yml create mode 100644 web/modules/webform/modules/webform_attachment/webform_attachment.routing.yml create mode 100644 web/modules/webform/modules/webform_bootstrap/js/webform.element.help.js create mode 100644 web/modules/webform/modules/webform_bootstrap/js/webform_bootstrap.states.js create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_form_display.node.webform_demo_region.default.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.default.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.teaser.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.body.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.webform.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/node.type.webform_demo_region.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/webform.webform.demo_region_contact.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/optional/block.block.bartik_webform_demo_region.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.info.yml create mode 100644 web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.install delete mode 100644 web/modules/webform/modules/webform_devel/config/install/webform_devel.settings.yml delete mode 100644 web/modules/webform/modules/webform_devel/config/schema/webform_devel.schema.yml create mode 100644 web/modules/webform/modules/webform_devel/drush.services.yml create mode 100644 web/modules/webform/modules/webform_devel/src/Commands/WebformDevelCommands.php delete mode 100644 web/modules/webform/modules/webform_devel/src/Logger/WebformDevelLog.php create mode 100644 web/modules/webform/modules/webform_example_handler/config/install/webform.webform.webform_example_handler.yml create mode 100644 web/modules/webform/modules/webform_example_handler/config/schema/webform_example_handler.schema.yml create mode 100644 web/modules/webform/modules/webform_example_handler/src/Plugin/WebformHandler/ExampleWebformHandler.php create mode 100644 web/modules/webform/modules/webform_example_handler/templates/webform-handler-example-summary.html.twig create mode 100644 web/modules/webform/modules/webform_example_handler/webform_example_handler.info.yml create mode 100644 web/modules/webform/modules/webform_example_handler/webform_example_handler.module delete mode 100644 web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.module create mode 100644 web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements_ajax.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_advanced.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_basic.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_containers.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_labels.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_wizard.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/css/webform_examples_accessibility.css create mode 100644 web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.info.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.libraries.yml create mode 100644 web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.module create mode 100644 web/modules/webform/modules/webform_image_select/src/Controller/WebformImageSelectImagesController.php create mode 100644 web/modules/webform/modules/webform_image_select/src/Form/WebformImageSelectImagesFilterForm.php delete mode 100644 web/modules/webform/modules/webform_node/src/Controller/WebformNodeSubmissionLogController.php create mode 100644 web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessPermissionsTest.php create mode 100644 web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessRulesTest.php delete mode 100644 web/modules/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php create mode 100644 web/modules/webform/modules/webform_node/src/Tests/WebformNodeTranslationTest.php create mode 100644 web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/config/install/webform.webform.webform_node_test_translation.yml create mode 100644 web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.info.yml create mode 100644 web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.install create mode 100644 web/modules/webform/modules/webform_scheduled_email/config/install/webform_scheduled_email.settings.yml rename web/modules/webform/modules/webform_scheduled_email/config/schema/{webform_scheduled_email.yml => webform_scheduled_email.plugin.handler.schema.yml} (86%) create mode 100644 web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.settings.schema.yml create mode 100644 web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTranslationTest.php create mode 100644 web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language.entity.es.yml create mode 100644 web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language/es/webform.webform.test_handler_scheduled_translate.yml create mode 100644 web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/webform.webform.test_handler_scheduled_translate.yml create mode 100644 web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/webform_scheduled_email_test_translation.info.yml create mode 100644 web/modules/webform/modules/webform_shortcuts/config/install/webform_shortcuts.settings.yml create mode 100644 web/modules/webform/modules/webform_shortcuts/config/schema/webform_shortcuts.settings.schema.yml create mode 100644 web/modules/webform/modules/webform_shortcuts/js/webform_shortcuts.js create mode 100644 web/modules/webform/modules/webform_shortcuts/tests/src/Functional/WebformShortcutsFunctionalTest.php create mode 100644 web/modules/webform/modules/webform_shortcuts/webform_shortcuts.info.yml create mode 100644 web/modules/webform/modules/webform_shortcuts/webform_shortcuts.install create mode 100644 web/modules/webform/modules/webform_shortcuts/webform_shortcuts.libraries.yml create mode 100644 web/modules/webform/modules/webform_shortcuts/webform_shortcuts.module create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/Controller/WebformSubmissionExportImportController.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/Form/WebformSubmissionExportImportUploadForm.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/Plugin/WebformExporter/WebformSubmissionExportImportWebformExporter.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/Routing/WebformSubmissionExportImportRouteSubscriber.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporter.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporterInterface.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/config/install/webform.webform.test_submission_export_import.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-external.csv create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-webform.csv create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/js/webform_submission_export_import_test.js create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.info.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.install create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.libraries.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.module create mode 100644 web/modules/webform/modules/webform_submission_export_import/tests/src/Functional/WebformSubmissionImportExportFunctionalTest.php create mode 100644 web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.info.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.links.task.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.module create mode 100644 web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.routing.yml create mode 100644 web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.services.yml rename web/modules/webform/{ => modules/webform_submission_log}/src/Controller/WebformSubmissionLogController.php (72%) create mode 100644 web/modules/webform/modules/webform_submission_log/src/Routing/WebformSubmissionLogRouteSubscriber.php rename web/modules/webform/modules/{webform_node/src/Tests/WebformNodeSubmissionLogTest.php => webform_submission_log/src/Tests/WebformSubmissionLogNodeTest.php} (69%) rename web/modules/webform/{ => modules/webform_submission_log}/src/Tests/WebformSubmissionLogTest.php (72%) create mode 100644 web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTrait.php create mode 100644 web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogLogger.php create mode 100644 web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManager.php create mode 100644 web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManagerInterface.php create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.info.yml create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.install create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.links.task.yml create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.module create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.permissions.yml create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.routing.yml create mode 100644 web/modules/webform/modules/webform_submission_log/webform_submission_log.services.yml create mode 100644 web/modules/webform/modules/webform_templates/webform_templates.permissions.yml create mode 100644 web/modules/webform/modules/webform_ui/webform_ui.links.action.yml delete mode 100644 web/modules/webform/modules/webform_ui/webform_ui.permissions.yml create mode 100644 web/modules/webform/reports/accessiblity/text/example_accessibility_advanced.txt create mode 100644 web/modules/webform/reports/accessiblity/text/example_accessibility_basic.txt create mode 100644 web/modules/webform/reports/accessiblity/text/example_accessibility_containers.txt create mode 100644 web/modules/webform/reports/accessiblity/text/example_accessibility_labels.txt create mode 100644 web/modules/webform/reports/accessiblity/text/example_accessibility_wizard.txt create mode 100644 web/modules/webform/src/Access/WebformAccessResult.php create mode 100644 web/modules/webform/src/Access/WebformHandlerAccess.php create mode 100644 web/modules/webform/src/Ajax/WebformAnnounceCommand.php create mode 100644 web/modules/webform/src/Controller/WebformHelpController.php create mode 100644 web/modules/webform/src/Controller/WebformSubmissionsController.php create mode 100644 web/modules/webform/src/Element/WebformCompositeFormElementTrait.php create mode 100644 web/modules/webform/src/Element/WebformImageResolution.php rename web/modules/webform/src/Element/{WebformLocation.php => WebformLocationBase.php} (54%) create mode 100644 web/modules/webform/src/Element/WebformLocationGeocomplete.php create mode 100644 web/modules/webform/src/Element/WebformLocationPlaces.php create mode 100644 web/modules/webform/src/Element/WebformSubmissionInformation.php create mode 100644 web/modules/webform/src/Element/WebformSubmissionNavigation.php create mode 100644 web/modules/webform/src/Element/WebformSubmissionViews.php create mode 100644 web/modules/webform/src/Element/WebformSubmissionViewsReplace.php create mode 100644 web/modules/webform/src/EventSubscriber/WebformExceptionHtmlSubscriber.php delete mode 100644 web/modules/webform/src/EventSubscriber/WebformSubscriber.php create mode 100644 web/modules/webform/src/Form/WebformConfigEntityDeleteFormBase.php create mode 100644 web/modules/webform/src/Form/WebformDeleteFormBase.php create mode 100644 web/modules/webform/src/Form/WebformDeleteFormInterface.php create mode 100644 web/modules/webform/src/Form/WebformOptionsFilterForm.php create mode 100644 web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceUrlFormatter.php create mode 100644 web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceWidgetTrait.php create mode 100644 web/modules/webform/src/Plugin/Menu/LocalAction/WebformDialogLocalAction.php create mode 100644 web/modules/webform/src/Plugin/WebformElement/Address.php create mode 100644 web/modules/webform/src/Plugin/WebformElement/TextBaseTrait.php create mode 100644 web/modules/webform/src/Plugin/WebformElement/WebformLocationBase.php rename web/modules/webform/src/Plugin/WebformElement/{WebformLocation.php => WebformLocationGeocomplete.php} (55%) create mode 100644 web/modules/webform/src/Plugin/WebformElement/WebformLocationPlaces.php create mode 100644 web/modules/webform/src/Plugin/WebformElementAttachmentInterface.php create mode 100644 web/modules/webform/src/Tests/Access/WebformAccessEntityJsonApiTest.php create mode 100644 web/modules/webform/src/Tests/Access/WebformAccessEntityPermissionsTest.php create mode 100644 web/modules/webform/src/Tests/Access/WebformAccessEntityRestTest.php rename web/modules/webform/src/Tests/{WebformEntityAccessTest.php => Access/WebformAccessEntityRulesTest.php} (66%) create mode 100644 web/modules/webform/src/Tests/Access/WebformAccessSubmissionPermissionsTest.php create mode 100644 web/modules/webform/src/Tests/Composite/WebformCompositeCustomFileTest.php create mode 100644 web/modules/webform/src/Tests/Composite/WebformCompositePluginFileTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementAddressTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementCaptchaTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementCheckboxesTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementCounterTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementDetailsTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementFieldsetTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementImageResolutionTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementInputMaskTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementLocationPlacesTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementManagedFileImageTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementManagedFileLimitTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementManagedFilePreviewTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementPatternTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsReplaceTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsTest.php create mode 100644 web/modules/webform/src/Tests/Element/WebformElementTelephoneTest.php delete mode 100644 web/modules/webform/src/Tests/Element/WebformElementTextTest.php create mode 100644 web/modules/webform/src/Tests/Handler/WebformHandlerInvokeAlterHookTest.php create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsAccessDeniedTest.php create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsArchivedTest.php rename web/modules/webform/src/Tests/{WebformSubmissionFormAutofillTest.php => Settings/WebformSettingsAutofillTest.php} (83%) rename web/modules/webform/src/Tests/{WebformSubmissionFormConfirmationTest.php => Settings/WebformSettingsConfirmationTest.php} (83%) rename web/modules/webform/src/Tests/{WebformSubmissionFormDraftTest.php => Settings/WebformSettingsDraftTest.php} (63%) create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsFormTitleTest.php create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsLimitUniqueTest.php rename web/modules/webform/src/Tests/{WebformSubmissionFormLimitsTest.php => Settings/WebformSettingsLimitsTest.php} (85%) delete mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsLoginTest.php rename web/modules/webform/src/Tests/{WebformSubmissionFormPreviewTest.php => Settings/WebformSettingsPreviewTest.php} (54%) create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsPreviousTest.php create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsRemoteAddrTest.php create mode 100644 web/modules/webform/src/Tests/Settings/WebformSettingsStatusTest.php create mode 100644 web/modules/webform/src/Tests/States/WebformStatesPreviewTest.php rename web/modules/webform/src/Tests/{WebformSubmissionConditionsValidatorTest.php => States/WebformStatesServerTest.php} (60%) create mode 100644 web/modules/webform/src/Tests/States/WebformStatesWizardTest.php create mode 100644 web/modules/webform/src/Tests/WebformEditorTest.php create mode 100644 web/modules/webform/src/Tests/WebformEmailProviderTest.php delete mode 100644 web/modules/webform/src/Tests/WebformSubmissionAccessTest.php create mode 100644 web/modules/webform/src/Tests/WebformSubmissionViewsTest.php create mode 100644 web/modules/webform/src/Tests/WebformTokenSuffixesTest.php create mode 100644 web/modules/webform/src/Tests/Wizard/WebformWizardLinksTest.php create mode 100644 web/modules/webform/src/Utility/Mail.php create mode 100644 web/modules/webform/src/Utility/WebformAccessibilityHelper.php create mode 100644 web/modules/webform/src/Utility/WebformObjectHelper.php create mode 100644 web/modules/webform/src/Utility/WebformTextHelper.php create mode 100644 web/modules/webform/src/Utility/WebformXss.php create mode 100644 web/modules/webform/src/WebformAccessRulesManager.php create mode 100644 web/modules/webform/src/WebformAccessRulesManagerInterface.php rename web/modules/webform/{modules/webform_devel/src/Form/WebformDevelEntityExportForm.php => src/WebformEntityExportForm.php} (95%) create mode 100644 web/modules/webform/templates/webform-email-html.html.twig create mode 100644 web/modules/webform/templates/webform-html-editor-markup.html.twig create mode 100644 web/modules/webform/templates/webform-submission-form.html.twig create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom_file.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_none.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_address.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite_wrapper.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_ajax.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_counter.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_details.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_fieldset.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_element_text.yml => webform.webform.test_element_image_file.yml} (71%) rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_nested.yml => webform.webform.test_element_image_resolution.yml} (64%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_input_mask.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_element_location.yml => webform.webform.test_element_loc_geocomplete.yml} (79%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_places.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_help.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_limit.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_prev.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_pattern.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views_r.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_access_denied.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_login.yml => webform.webform.test_form_archived.yml} (74%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_total_unique.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_user_unique.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_remote_addr.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved_wizard.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_links.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states.yml => webform.webform.test_states.yml} (93%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_autocomplete.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_crosspage.yml create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_clear.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_comp.yml => webform.webform.test_states_server_comp.yml} (74%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_containers.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_custom.yml => webform.webform.test_states_server_custom.yml} (83%) rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_multiple.yml => webform.webform.test_states_server_multiple.yml} (78%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_nested.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_preview.yml => webform.webform.test_states_server_preview.yml} (81%) rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_required.yml => webform.webform.test_states_server_required.yml} (90%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_save.yml rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_server_wizard.yml => webform.webform.test_states_server_wizard.yml} (87%) rename web/modules/webform/tests/modules/webform_test/config/install/{webform.webform.test_form_states_triggers.yml => webform.webform.test_states_triggers.yml} (87%) create mode 100644 web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_views.yml create mode 100644 web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_comp_file_plugin.yml create mode 100644 web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestCompositeFile.php create mode 100644 web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestCompositeFile.php rename web/modules/webform/tests/modules/{webform_test_views/config/install/views.view.webform_test_entity_reference.yml => webform_test_entity_reference/config/install/views.view.webform_test_entity_reference_vs.yml} (56%) create mode 100644 web/modules/webform/tests/modules/webform_test_entity_reference/config/install/webform.webform.test_element_entity_reference_vs.yml create mode 100644 web/modules/webform/tests/modules/webform_test_entity_reference/webform_test_entity_reference_views.info.yml create mode 100644 web/modules/webform/tests/modules/webform_test_exporter/src/Plugin/WebformExporter/TestWebformExporter.php create mode 100644 web/modules/webform/tests/modules/webform_test_exporter/webform_test_exporter.info.yml create mode 100644 web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.info.yml create mode 100644 web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.module create mode 100644 web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_put.yml create mode 100644 web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.info.yml create mode 100644 web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.module create mode 100644 web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform.yml create mode 100644 web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_options.yml create mode 100644 web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_submission.yml create mode 100644 web/modules/webform/tests/modules/webform_test_rest/webform_test_rest.info.yml create mode 100644 web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.install create mode 100644 web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_access.yml create mode 100644 web/modules/webform/tests/src/Functional/WebformAssertLegacyTrait.php create mode 100644 web/modules/webform/tests/src/Functional/WebformBrowserTestBase.php create mode 100644 web/modules/webform/tests/src/Functional/WebformBrowserTestBaseTest.php create mode 100644 web/modules/webform/tests/src/Functional/WebformSubmissionViewsAccessTest.php delete mode 100644 web/modules/webform/tests/src/Unit/Access/WebformAccessCheckTest.php create mode 100644 web/modules/webform/tests/src/Unit/Access/WebformAccessTestBase.php create mode 100644 web/modules/webform/tests/src/Unit/Access/WebformAccountAccessTest.php create mode 100644 web/modules/webform/tests/src/Unit/Access/WebformSourceEntityAccessTest.php create mode 100644 web/modules/webform/tests/src/Unit/Access/WebformSubmissionAccessTest.php delete mode 100644 web/modules/webform/tests/src/Unit/Utility/WebformDateHelperTest.php create mode 100644 web/modules/webform/tests/src/Unit/Utility/WebformObjectHelperTest.php create mode 100644 web/modules/webform/third_party_settings/webform.captcha.inc diff --git a/composer.json b/composer.json index 5d2e44182e..2c8c75f307 100644 --- a/composer.json +++ b/composer.json @@ -180,7 +180,7 @@ "drupal/views_fieldsets": "3.3", "drupal/views_infinite_scroll": "1.5", "drupal/views_slideshow": "4.4", - "drupal/webform": "5.0-rc12", + "drupal/webform": "5.2", "drupal/webform_views": "5.0-alpha2", "drush-ops/behat-drush-endpoint": "^0.0.5", "drush/drush": "^9", diff --git a/composer.lock b/composer.lock index 4716f95d4e..0daea23f3b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7604230aa9adf47dda5866685c99ae24", + "content-hash": "05189e0457ebaecf7dad6250a18bce1b", "packages": [ { "name": "alchemy/zippy", @@ -8026,26 +8026,35 @@ }, { "name": "drupal/webform", - "version": "5.0.0-rc12", + "version": "5.2.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/webform.git", - "reference": "8.x-5.0-rc12" + "reference": "8.x-5.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/webform-8.x-5.0-rc12.zip", - "reference": "8.x-5.0-rc12", - "shasum": "d677eb59bf2084267476fa482b5fbccb0dc8afb9" + "url": "https://ftp.drupal.org/files/projects/webform-8.x-5.2.zip", + "reference": "8.x-5.2", + "shasum": "44e67c377e156f7f8d6f26bba43240dfa1885637" }, "require": { "drupal/core": "*" }, "require-dev": { + "drupal/address": "~1.4", + "drupal/captcha": "~1.0", + "drupal/chosen": "~2.6", "drupal/devel": "*", - "drupal/token": "*", + "drupal/jsonapi": "~2.0", + "drupal/mailsystem": "~4.0", + "drupal/select2": "~1.1", + "drupal/telephone_validation": "^2.2", + "drupal/token": "~1.3", + "drupal/webform_access": "*", "drupal/webform_node": "*", - "drupal/webform_scheduled_email": "*" + "drupal/webform_scheduled_email": "*", + "drupal/webform_ui": "*" }, "type": "drupal-module", "extra": { @@ -8053,11 +8062,11 @@ "dev-5.x": "5.x-dev" }, "drupal": { - "version": "8.x-5.0-rc12", - "datestamp": "1528137480", + "version": "8.x-5.2", + "datestamp": "1563460085", "security-coverage": { - "status": "not-covered", - "message": "RC releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } }, "drush": { @@ -8118,6 +8127,7 @@ "homepage": "https://drupal.org/project/webform", "support": { "source": "http://cgit.drupalcode.org/webform", + "error": "Invalid dependency: \"telephone_validation/telephone_validation\" is an unknown drupal 8 package name", "issues": "https://www.drupal.org/project/issues/webform?version=8.x", "docs": "https://www.drupal.org/docs/8/modules/webform", "forum": "https://drupal.stackexchange.com/questions/tagged/webform" diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 16c9d039d4..81688fa97c 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -6032,18 +6032,18 @@ }, { "name": "drupal/metatag", - "version": "1.8.0", - "version_normalized": "1.8.0.0", + "version": "1.9.0", + "version_normalized": "1.9.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/metatag.git", - "reference": "8.x-1.8" + "reference": "8.x-1.9" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/metatag-8.x-1.8.zip", - "reference": "8.x-1.8", - "shasum": "fb5d31aa08c8c2e175f096f9917e9741db152ea8" + "url": "https://ftp.drupal.org/files/projects/metatag-8.x-1.9.zip", + "reference": "8.x-1.9", + "shasum": "230960752c5afa17337fb69bae853bccb1a26ecd" }, "require": { "drupal/core": "*", @@ -6065,8 +6065,8 @@ "dev-1.x": "1.x-dev" }, "drupal": { - "version": "8.x-1.8", - "datestamp": "1550692511", + "version": "8.x-1.9", + "datestamp": "1563995941", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -8280,27 +8280,36 @@ }, { "name": "drupal/webform", - "version": "5.0.0-rc12", - "version_normalized": "5.0.0.0-RC12", + "version": "5.2.0", + "version_normalized": "5.2.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/webform.git", - "reference": "8.x-5.0-rc12" + "reference": "8.x-5.2" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/webform-8.x-5.0-rc12.zip", - "reference": "8.x-5.0-rc12", - "shasum": "d677eb59bf2084267476fa482b5fbccb0dc8afb9" + "url": "https://ftp.drupal.org/files/projects/webform-8.x-5.2.zip", + "reference": "8.x-5.2", + "shasum": "44e67c377e156f7f8d6f26bba43240dfa1885637" }, "require": { "drupal/core": "*" }, "require-dev": { + "drupal/address": "~1.4", + "drupal/captcha": "~1.0", + "drupal/chosen": "~2.6", "drupal/devel": "*", - "drupal/token": "*", + "drupal/jsonapi": "~2.0", + "drupal/mailsystem": "~4.0", + "drupal/select2": "~1.1", + "drupal/telephone_validation": "^2.2", + "drupal/token": "~1.3", + "drupal/webform_access": "*", "drupal/webform_node": "*", - "drupal/webform_scheduled_email": "*" + "drupal/webform_scheduled_email": "*", + "drupal/webform_ui": "*" }, "type": "drupal-module", "extra": { @@ -8308,11 +8317,11 @@ "dev-5.x": "5.x-dev" }, "drupal": { - "version": "8.x-5.0-rc12", - "datestamp": "1528137480", + "version": "8.x-5.2", + "datestamp": "1563460085", "security-coverage": { - "status": "not-covered", - "message": "RC releases are not covered by Drupal security advisories." + "status": "covered", + "message": "Covered by Drupal's security advisory policy" } }, "drush": { @@ -8374,6 +8383,7 @@ "homepage": "https://drupal.org/project/webform", "support": { "source": "http://cgit.drupalcode.org/webform", + "error": "Invalid dependency: \"telephone_validation/telephone_validation\" is an unknown drupal 8 package name", "issues": "https://www.drupal.org/project/issues/webform?version=8.x", "docs": "https://www.drupal.org/docs/8/modules/webform", "forum": "https://drupal.stackexchange.com/questions/tagged/webform" diff --git a/web/modules/webform/.eslintrc.json b/web/modules/webform/.eslintrc.json new file mode 100644 index 0000000000..f9dbd5b660 --- /dev/null +++ b/web/modules/webform/.eslintrc.json @@ -0,0 +1,94 @@ +{ + "extends": "eslint:recommended", + "root": true, + "env": { + "browser": true + }, + "globals": { + "Drupal": true, + "drupalSettings": true, + "drupalTranslations": true, + "domready": true, + "jQuery": true, + "_": true, + "matchMedia": true, + "Backbone": true, + "Modernizr": true, + "CKEDITOR": true + }, + "rules": { + "array-bracket-spacing": ["error", "never"], + "block-scoped-var": "error", + "brace-style": ["error", "stroustrup", {"allowSingleLine": true}], + "comma-dangle": ["error", "never"], + "comma-spacing": "error", + "comma-style": ["error", "last"], + "computed-property-spacing": ["error", "never"], + "curly": ["error", "all"], + "eol-last": "error", + "eqeqeq": ["error", "smart"], + "guard-for-in": "error", + "indent": ["error", 2, {"SwitchCase": 1}], + "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], + "keyword-spacing": ["error", {"before": true, "after": true}], + "linebreak-style": ["error", "unix"], + "lines-around-comment": ["error", {"beforeBlockComment": true, "afterBlockComment": false}], + "new-parens": "error", + "no-array-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-parens": ["error", "functions"], + "no-implied-eval": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-loop-func": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-native-reassign": "error", + "no-nested-ternary": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-return-assign": "error", + "no-script-url": "error", + "no-sequences": "error", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-undefined": "error", + "no-unused-expressions": "error", + "no-unused-vars": ["error", {"vars": "all", "args": "none"}], + "no-with": "error", + "object-curly-spacing": ["error", "never"], + "one-var": ["error", "never"], + "quote-props": ["error", "consistent-as-needed"], + "quotes": ["error", "single", "avoid-escape"], + "semi": ["error", "always"], + "semi-spacing": ["error", {"before": false, "after": true}], + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": ["error", { "words": true, "nonwords": false }], + "spaced-comment": ["error", "always"], + "strict": ["error", "function"], + "yoda": ["error", "never"], + "max-nested-callbacks": ["warn", 3], + "valid-jsdoc": ["warn", { + "prefer": { + "returns": "return", + "property": "prop" + }, + "requireReturn": false + }] + } +} diff --git a/web/modules/webform/.gitignore b/web/modules/webform/.gitignore index 852c70afd6..15e10416dd 100644 --- a/web/modules/webform/.gitignore +++ b/web/modules/webform/.gitignore @@ -1,8 +1,10 @@ *.patch interdiff-*.txt -# Ignore the HTML directory used for generate Webform documentation. -html +# Ignore the HTML and reports directory used for generate Webform documentation. +/html +/reports/accessiblity/html +/reports/accessiblity/pdf # Ignore all *.features.yml files. *.features.yml diff --git a/web/modules/webform/ISSUE_TEMPLATE.html b/web/modules/webform/ISSUE_TEMPLATE.html index b31b6d7e3d..dc0aee293d 100644 --- a/web/modules/webform/ISSUE_TEMPLATE.html +++ b/web/modules/webform/ISSUE_TEMPLATE.html @@ -30,4 +30,4 @@ <h3 id="summary-data-model-changes">Data model changes</h3> (OPTIONAL: Database or configuration data changes that would make stored data on an existing site incompatible with the site's updated codebase, including changes to hook_schema(), configuration schema or keys, or the expected format of stored data, etc.) <h3 id="summary-additional-information">Additional information</h3> -(OPTIONAL: Include any additional information about your specific environment that could be helpful, including Drupal version, Browser version, etc...) +(OPTIONAL: Include any additional information about your specific environment that could be helpful, including Drupal version, Browser version, etc…) diff --git a/web/modules/webform/README.md b/web/modules/webform/README.md index 2ce4711e4e..46e94cbe98 100644 --- a/web/modules/webform/README.md +++ b/web/modules/webform/README.md @@ -58,18 +58,17 @@ The primary use case for this module is to: 7. (optional) Install add-on contrib modules](/admin/structure/webform/addons). -### Releases - -Even though the Webform module is still under active development with -regular [beta releases](https://www.drupal.org/documentation/version-info/alpha-beta-rc), -all existing configuration and submission data will be maintained and updated -between releases. **APIs can and will be changing** while this module moves -from beta releases to a final release candidate. - -Simply put, if you install and use the Webform module out of the box AS-IS, -you _should_ be okay. Once you start extending webforms with plugins, altering -hooks, and overriding templates, you will need to read each release's -notes and assume that _things will be changing_. +### Upgrading from pre-release versions + +All existing configuration and submission data was maintained and updated +through the beta and rc release cycles. +**APIs have changed** during these release cycles. + +Simply put, if you installed and used the Webform module out of the box AS-IS, +and now you want to upgrade to a full release, then +you _should_ be okay. If you extended webforms with plugins, altered +hooks, and overrode templates, you will need to read each release's +notes and assume that _things have changed_. ### Project Status diff --git a/web/modules/webform/composer.json b/web/modules/webform/composer.json index 1114953a28..7be12f1613 100644 --- a/web/modules/webform/composer.json +++ b/web/modules/webform/composer.json @@ -29,5 +29,15 @@ "drush.services.yml": "^9" } } + }, + "require-dev": { + "drupal/address": "~1.4", + "drupal/captcha": "~1.0", + "drupal/chosen": "~2.6", + "drupal/jsonapi": "~2.0", + "drupal/mailsystem": "~4.0", + "drupal/select2": "~1.1", + "drupal/telephone_validation": "^2.2", + "drupal/token": "~1.3" } } diff --git a/web/modules/webform/composer.libraries.json b/web/modules/webform/composer.libraries.json new file mode 100644 index 0000000000..31bda67b00 --- /dev/null +++ b/web/modules/webform/composer.libraries.json @@ -0,0 +1,429 @@ +{ + "name": "drupal/webform", + "description": "Enables the creation of webforms and questionnaires.", + "type": "drupal-module", + "license": "GPL-2.0+", + "minimum-stability": "dev", + "homepage": "https://drupal.org/project/webform", + "authors": [ + { + "name": "Jacob Rockowitz (jrockowitz)", + "homepage": "https://www.drupal.org/u/jrockowitz", + "role": "Maintainer" + }, + { + "name": "Alexander Trotsenko (bucefal91)", + "homepage": "https://www.drupal.org/u/bucefal91", + "role": "Co-maintainer" + } + ], + "support": { + "issues": "https://www.drupal.org/project/issues/webform?version=8.x", + "source": "http://cgit.drupalcode.org/webform", + "docs": "https://www.drupal.org/docs/8/modules/webform", + "forum": "https://drupal.stackexchange.com/questions/tagged/webform" + }, + "repositories": { + "algolia.places": { + "type": "package", + "package": { + "name": "algolia/places", + "version": "1.16.1", + "type": "drupal-library", + "extra": { + "installer-name": "algolia.places" + }, + "dist": { + "url": "https://registry.npmjs.org/places.js/-/places.js-1.16.1.tgz", + "type": "tar" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "ckeditor.autogrow": { + "type": "package", + "package": { + "name": "ckeditor/autogrow", + "version": "4.10.1", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.autogrow" + }, + "dist": { + "url": "https://download.ckeditor.com/autogrow/releases/autogrow_4.10.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "ckeditor.codemirror": { + "type": "package", + "package": { + "name": "ckeditor/codemirror", + "version": "v1.17.7", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.codemirror" + }, + "dist": { + "url": "https://github.com/w8tcha/CKEditor-CodeMirror-Plugin/releases/download/v1.17.7/CKEditor-CodeMirror-Plugin.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "ckeditor.fakeobjects": { + "type": "package", + "package": { + "name": "ckeditor/fakeobjects", + "version": "4.10.1", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.fakeobjects" + }, + "dist": { + "url": "https://download.ckeditor.com/fakeobjects/releases/fakeobjects_4.10.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "ckeditor.image": { + "type": "package", + "package": { + "name": "ckeditor/image", + "version": "4.10.1", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.image" + }, + "dist": { + "url": "https://download.ckeditor.com/image/releases/image_4.10.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "ckeditor.link": { + "type": "package", + "package": { + "name": "ckeditor/link", + "version": "4.10.1", + "type": "drupal-library", + "extra": { + "installer-name": "ckeditor.link" + }, + "dist": { + "url": "https://download.ckeditor.com/link/releases/link_4.10.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "codemirror": { + "type": "package", + "package": { + "name": "codemirror/codemirror", + "version": "5.44.0", + "type": "drupal-library", + "extra": { + "installer-name": "codemirror" + }, + "dist": { + "url": "https://github.com/components/codemirror/archive/5.44.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.chosen": { + "type": "package", + "package": { + "name": "jquery/chosen", + "version": "1.8.7", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.chosen" + }, + "dist": { + "url": "https://github.com/harvesthq/chosen/releases/download/v1.8.7/chosen_v1.8.7.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.geocomplete": { + "type": "package", + "package": { + "name": "jquery/geocomplete", + "version": "1.7.0", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.geocomplete" + }, + "dist": { + "url": "https://github.com/ubilabs/geocomplete/archive/1.7.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.hotkeys": { + "type": "package", + "package": { + "name": "jquery/hotkeys", + "version": "0.2.0", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.hotkeys" + }, + "dist": { + "url": "https://github.com/jeresig/jquery.hotkeys/archive/0.2.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.icheck": { + "type": "package", + "package": { + "name": "jquery/icheck", + "version": "1.0.2 ", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.icheck" + }, + "dist": { + "url": "https://github.com/fronteed/icheck/archive/1.0.2.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.image-picker": { + "type": "package", + "package": { + "name": "jquery/image-picker", + "version": "0.3.0", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.image-picker" + }, + "dist": { + "url": "https://github.com/rvera/image-picker/archive/0.3.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.inputmask": { + "type": "package", + "package": { + "name": "jquery/inputmask", + "version": "4.0.6", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.inputmask" + }, + "dist": { + "url": "https://github.com/RobinHerbots/jquery.inputmask/archive/4.0.6.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.intl-tel-input": { + "type": "package", + "package": { + "name": "jquery/intl-tel-input", + "version": "15.0.1", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.intl-tel-input" + }, + "dist": { + "url": "https://github.com/jackocnr/intl-tel-input/archive/v15.0.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.rateit": { + "type": "package", + "package": { + "name": "jquery/rateit", + "version": "1.1.1", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.rateit" + }, + "dist": { + "url": "https://github.com/gjunge/rateit.js/archive/1.1.1.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.select2": { + "type": "package", + "package": { + "name": "jquery/select2", + "version": "4.0.5", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.select2" + }, + "dist": { + "url": "https://github.com/select2/select2/archive/4.0.5.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.textcounter": { + "type": "package", + "package": { + "name": "jquery/textcounter", + "version": "0.8.0", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.textcounter" + }, + "dist": { + "url": "https://github.com/ractoon/jQuery-Text-Counter/archive/0.8.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.timepicker": { + "type": "package", + "package": { + "name": "jquery/timepicker", + "version": "1.11.14", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.timepicker" + }, + "dist": { + "url": "https://github.com/jonthornton/jquery-timepicker/archive/1.11.14.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "jquery.toggles": { + "type": "package", + "package": { + "name": "jquery/toggles", + "version": "4.0.0", + "type": "drupal-library", + "extra": { + "installer-name": "jquery.toggles" + }, + "dist": { + "url": "https://github.com/simontabor/jquery-toggles/archive/v4.0.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "progress-tracker": { + "type": "package", + "package": { + "name": "progress-tracker/progress-tracker", + "version": "1.4.0", + "type": "drupal-library", + "extra": { + "installer-name": "progress-tracker" + }, + "dist": { + "url": "https://github.com/NigelOToole/progress-tracker/archive/v1.4.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + }, + "signature_pad": { + "type": "package", + "package": { + "name": "signature_pad/signature_pad", + "version": "2.3.0", + "type": "drupal-library", + "extra": { + "installer-name": "signature_pad" + }, + "dist": { + "url": "https://github.com/szimek/signature_pad/archive/v2.3.0.zip", + "type": "zip" + }, + "require": { + "composer/installers": "~1.0" + } + } + } + }, + "require": { + "algolia/places": "1.16.1", + "ckeditor/autogrow": "4.10.1", + "ckeditor/codemirror": "v1.17.7", + "ckeditor/fakeobjects": "4.10.1", + "ckeditor/image": "4.10.1", + "ckeditor/link": "4.10.1", + "codemirror/codemirror": "5.44.0", + "jquery/chosen": "1.8.7", + "jquery/geocomplete": "1.7.0", + "jquery/hotkeys": "0.2.0", + "jquery/icheck": "1.0.2 ", + "jquery/image-picker": "0.3.0", + "jquery/inputmask": "4.0.6", + "jquery/intl-tel-input": "15.0.1", + "jquery/rateit": "1.1.1", + "jquery/select2": "4.0.5", + "jquery/textcounter": "0.8.0", + "jquery/timepicker": "1.11.14", + "jquery/toggles": "4.0.0", + "progress-tracker/progress-tracker": "1.4.0", + "signature_pad/signature_pad": "2.3.0" + } +} diff --git a/web/modules/webform/config/install/webform.settings.yml b/web/modules/webform/config/install/webform.settings.yml index 01a48cc7a2..d8f6d95081 100644 --- a/web/modules/webform/config/install/webform.settings.yml +++ b/web/modules/webform/config/install/webform.settings.yml @@ -1,13 +1,14 @@ settings: + default_status: open default_page_base_path: form default_form_open_message: 'This form has not yet been opened to submissions.' - default_form_close_message: 'Sorry...This form is closed to new submissions.' + default_form_close_message: 'Sorry…This form is closed to new submissions.' default_form_exception_message: 'Unable to display this webform. Please contact the site administrator.' default_submit_button_label: Submit default_reset_button_label: Reset default_form_submit_once: false default_form_confidential_message: 'This form is confidential. You must <a href="[site:login-url]/logout?destination=[current-page:url:relative]">Log out</a> to submit it.' - default_form_login_message: 'Please login to access this form.' + default_form_access_denied_message: 'Please login to access this form.' default_form_disable_back: false default_form_submit_back: false default_form_unsaved: false @@ -16,12 +17,14 @@ settings: default_form_required: false default_form_required_label: 'Indicates required field' default_form_details_toggle: true + default_form_file_limit: '' form_classes: | container-inline clearfix form--inline clearfix messages messages--error messages messages--warning messages messages--status + button_classes: '' default_wizard_prev_button_label: '< Previous Page' default_wizard_next_button_label: 'Next Page >' @@ -38,29 +41,59 @@ settings: default_confirmation_message: 'New submission added to [webform:title].' default_confirmation_back_label: 'Back to form' default_submission_label: '[webform_submission:submitted-to]: Submission #[webform_submission:serial]' - default_submission_login_message: 'Please login to access this submission.' + default_submission_access_denied_message: 'Please login to access this submission.' default_submission_exception_message: 'Unable to process this submission. Please contact the site administrator.' default_submission_locked_message: 'This submission has been locked.' default_submission_log: false + default_submission_views: { } + default_submission_views_replace: + global_routes: + - entity.webform_submission.collection + - entity.webform_submission.user + webform_routes: + - entity.webform.results_submissions + - entity.webform.user.drafts + - entity.webform.user.submissions + node_routes: + - entity.node.webform.results_submissions + - entity.node.webform.user.drafts + - entity.node.webform.user.submissions + default_previous_submission_message: 'You have already submitted this webform. <a href="#">View your previous submission</a>.' + default_previous_submissions_message: 'You have already submitted this webform. <a href="#">View your previous submissions</a>.' default_autofill_message: 'This submission has been autofilled with your previous submission.' preview_classes: | messages messages--error messages messages--warning messages messages--status + confirmation_classes: | messages messages--error messages messages--warning messages messages--status + confirmation_back_classes: | button + default_limit_total_message: 'No more submissions are permitted.' default_limit_user_message: 'No more submissions are permitted.' + dialog: false + dialog_options: + narrow: + title: Narrow + width: 600 + normal: + title: Normal + width: 800 + wide: + title: Wide + width: 1000 assets: css: '' javascript: '' handler: excluded_handlers: { } export: + temp_directory: '' exporter: delimited multiple_delimiter: ; header_format: label @@ -82,6 +115,7 @@ export: excluded_exporters: { } batch: default_batch_export_size: 500 + default_batch_import_size: 100 default_batch_update_size: 500 default_batch_delete_size: 500 default_batch_email_size: 500 @@ -96,12 +130,14 @@ element: messages messages--error messages messages--warning messages messages--status + classes: | container-inline clearfix form--inline clearfix messages messages--error messages messages--warning messages messages--status + horizontal_rule_classes: | webform-horizontal-rule--solid webform-horizontal-rule--dashed @@ -112,6 +148,7 @@ element: webform-horizontal-rule--thick webform-horizontal-rule--flaired webform-horizontal-rule--glyph + default_description_display: '' default_more_title: More default_section_title_tag: h2 @@ -120,13 +157,18 @@ element: default_empty_option_optional: '' default_icheck: '' default_google_maps_api_key: '' + default_algolia_places_app_id: '' + default_algolia_places_api_key: '' excluded_elements: password: password password_confirm: password_confirm + webform_location_geocomplete: webform_location_geocomplete html_editor: disabled: false - format: '' + element_format: '' + mail_format: '' tidy: true + make_unused_managed_files_temporary: true file: file_public: false file_private_redirect: true @@ -135,7 +177,7 @@ file: default_managed_file_extensions: 'gif jpg png bmp eps tif pict psd txt rtf html odf pdf doc docx ppt pptx xls xlsx xml avi mov mp3 ogg wav bz2 dmg gz jar rar sit svg tar zip' default_audio_file_extensions: 'mp3 ogg wav' default_document_file_extensions: 'txt rtf pdf doc docx odt ppt pptx odp xls xlsx ods' - default_image_file_extensions: 'gif jpg png svg' + default_image_file_extensions: 'gif jpg png' default_video_file_extensions: 'avi mov mp4 ogg wav webm' make_unused_managed_files_temporary: true delete_temporary_managed_files: true @@ -155,11 +197,13 @@ mail: Submitted values are: [webform_submission:values] + default_body_html: | <p>Submitted on [webform_submission:created]</p> <p>Submitted by: [webform_submission:user]</p> <p>Submitted values are:</p> [webform_submission:values] + roles: { } test: types: | @@ -199,13 +243,10 @@ test: - 'random@random.com' webform_email_multiple: - 'example@example.com, test@test.com, random@random.com' - webform_location: - - value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA' - - value: 'London SW1A 1AA, United Kingdom' - - value: 'Moscow, Russia, 10307' webform_time: - '09:00' - '17:00' + names: | first_name: - 'John' @@ -245,9 +286,11 @@ test: - 'Loremipsum' - 'Oratione' - 'Dixisset' + ui: video_display: dialog details_save: true + help_disabled: false dialog_disabled: false offcanvas_disabled: false contribute_disabled: false @@ -257,6 +300,7 @@ libraries: excluded_libraries: - jquery.chosen - jquery.icheck + - jquery.toggles requirements: cdn: true bootstrap: true @@ -265,4 +309,6 @@ contribute: account_type: user account_id: null langcode: en -third_party_settings: { } +third_party_settings: + captcha: + replace_administration_mode: true diff --git a/web/modules/webform/config/install/webform.webform.contact.yml b/web/modules/webform/config/install/webform.webform.contact.yml index 907046d346..4bfa609398 100644 --- a/web/modules/webform/config/install/webform.webform.contact.yml +++ b/web/modules/webform/config/install/webform.webform.contact.yml @@ -6,8 +6,10 @@ dependencies: - webform open: null close: null +weight: 0 uid: null template: false +archive: false id: contact title: Contact description: 'Basic email contact webform.' @@ -17,12 +19,12 @@ elements: | '#title': 'Your Name' '#type': textfield '#required': true - '#default_value': '[webform-authenticated-user:display-name]' + '#default_value': '[current-user:display-name]' email: '#title': 'Your Email' '#type': email '#required': true - '#default_value': '[webform-authenticated-user:mail]' + '#default_value': '[current-user:mail]' subject: '#title': Subject '#type': textfield @@ -45,6 +47,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -52,6 +55,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -67,22 +71,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -93,6 +112,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -111,9 +131,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -166,6 +188,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_confirmation: id: email @@ -183,17 +209,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: '[webform_submission:values:subject:raw]' body: '[webform_submission:values:message:value]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' @@ -209,7 +237,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -223,9 +251,11 @@ handlers: excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/config/install/webform.webform_options.gender.yml b/web/modules/webform/config/install/webform.webform_options.gender.yml index 52669de9c7..47d9f18f8d 100644 --- a/web/modules/webform/config/install/webform.webform_options.gender.yml +++ b/web/modules/webform/config/install/webform.webform_options.gender.yml @@ -11,4 +11,3 @@ likert: false options: | Male: Male Female: Female - Transgender: Transgender diff --git a/web/modules/webform/config/install/webform.webform_options.state_province_codes.yml b/web/modules/webform/config/install/webform.webform_options.state_province_codes.yml index 61c9feca08..d30fa1423a 100644 --- a/web/modules/webform/config/install/webform.webform_options.state_province_codes.yml +++ b/web/modules/webform/config/install/webform.webform_options.state_province_codes.yml @@ -14,7 +14,7 @@ options: | AS: 'American Samoa' AZ: Arizona AR: Arkansas - AE: 'Armed Forces (Canada, Europe, Africa, or Middle East' + AE: 'Armed Forces (Canada, Europe, Africa, or Middle East)' AA: 'Armed Forces Americas' AP: 'Armed Forces Pacific' CA: California @@ -22,7 +22,7 @@ options: | CT: Connecticut DE: Delaware DC: 'District of Columbia' - FM: 'Federate States of Micronesia' + FM: 'Federated States of Micronesia' FL: Florida GA: Georgia GU: Guam diff --git a/web/modules/webform/config/install/webform.webform_options.state_province_names.yml b/web/modules/webform/config/install/webform.webform_options.state_province_names.yml index cb000a3abc..50708b088d 100644 --- a/web/modules/webform/config/install/webform.webform_options.state_province_names.yml +++ b/web/modules/webform/config/install/webform.webform_options.state_province_names.yml @@ -22,7 +22,7 @@ options: | Connecticut: Connecticut Delaware: Delaware 'District of Columbia': 'District of Columbia' - 'Federate States of Micronesia': 'Federate States of Micronesia' + 'Federated States of Micronesia': 'Federated States of Micronesia' Florida: Florida Georgia: Georgia Guam: Guam diff --git a/web/modules/webform/config/install/webform.webform_options.translations.yml b/web/modules/webform/config/install/webform.webform_options.translations.yml new file mode 100644 index 0000000000..9f87b76f13 --- /dev/null +++ b/web/modules/webform/config/install/webform.webform_options.translations.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: translations +label: Translations +category: Language +likert: false +options: '' diff --git a/web/modules/webform/config/optional/views.view.webform_submissions.yml b/web/modules/webform/config/optional/views.view.webform_submissions.yml new file mode 100644 index 0000000000..27283e5b57 --- /dev/null +++ b/web/modules/webform/config/optional/views.view.webform_submissions.yml @@ -0,0 +1,3167 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform + module: + - user + - webform +id: webform_submissions +label: 'Webform submissions' +module: views +description: 'Default webform submissions views.' +tag: '' +base_table: webform_submission +base_field: sid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 25 + offset: 0 + id: 0 + total_pages: null + tags: + previous: ‹‹ + next: ›› + first: '« First' + last: 'Last »' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50, 100, 200' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + webform_submission_bulk_form: webform_submission_bulk_form + sid: sid + in_draft: in_draft + sticky: sticky + locked: locked + created: created + completed: completed + remote_addr: remote_addr + view_webform_submission: view_webform_submission + edit_webform_submission: view_webform_submission + info: + webform_submission_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + in_draft: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + sticky: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + locked: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + completed: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + remote_addr: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + view_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + edit_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + sid: + id: sid + table: webform_submission + field: sid + relationship: none + group_type: group + admin_label: '' + label: '#' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sid + plugin_id: field + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + label: Draft + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: in_draft + plugin_id: field + created: + id: created + table: webform_submission + field: created + relationship: none + group_type: group + admin_label: '' + label: Created + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: created + plugin_id: field + remote_addr: + id: remote_addr + table: webform_submission + field: remote_addr + relationship: none + group_type: group + admin_label: '' + label: 'IP address' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: remote_addr + plugin_id: field + view_webform_submission: + id: view_webform_submission + table: webform_submission + field: view_webform_submission + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: false + absolute: false + entity_type: webform_submission + plugin_id: entity_link + filters: { } + sorts: { } + header: + result: + id: result + table: views + field: result + relationship: none + group_type: group + admin_label: '' + empty: false + content: 'Displaying @start - @end of @total' + plugin_id: result + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No submissions available.' + plugin_id: text_custom + relationships: { } + arguments: + webform_id: + id: webform_id + table: webform_submission + field: webform_id + entity_type: webform_submission + entity_field: webform_id + plugin_id: string + entity_type: + id: entity_type + table: webform_submission + field: entity_type + entity_type: webform_submission + entity_field: entity_type + plugin_id: string + entity_id: + id: entity_id + table: webform_submission + field: entity_id + entity_type: webform_submission + entity_field: entity_id + plugin_id: string + uid: + id: uid + table: webform_submission + field: uid + entity_type: webform_submission + entity_field: uid + plugin_id: numeric + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + not: false + entity_type: webform_submission + entity_field: in_draft + plugin_id: numeric + display_extenders: { } + filter_groups: + operator: AND + groups: { } + title: 'Webform submissions' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + tags: { } + embed_default: + display_plugin: embed + id: embed_default + display_title: 'Embed: Default' + position: 1 + display_options: + display_extenders: { } + display_description: 'Display submissions.' + defaults: + fields: true + style: false + row: false + filters: true + filter_groups: true + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + sid: sid + in_draft: in_draft + created: created + remote_addr: remote_addr + view_webform_submission: view_webform_submission + info: + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + in_draft: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + remote_addr: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + view_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + default: created + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + tags: { } + embed_administer: + display_plugin: embed + id: embed_administer + display_title: 'Embed: Administer' + position: 2 + display_options: + display_extenders: { } + display_description: 'Administer submissions.' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + webform_submission_bulk_form: webform_submission_bulk_form + sid: sid + in_draft: in_draft + sticky: sticky + locked: locked + created: created + completed: completed + remote_addr: remote_addr + view_webform_submission: view_webform_submission + edit_webform_submission: view_webform_submission + info: + webform_submission_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + in_draft: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + sticky: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + locked: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + completed: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + remote_addr: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + view_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + edit_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: false + defaults: + style: false + row: false + access: false + fields: false + filters: false + filter_groups: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + access: + type: perm + options: + perm: 'administer webform submission' + fields: + webform_submission_bulk_form: + id: webform_submission_bulk_form + table: webform_submission + field: webform_submission_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Webform submission operations bulk form' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + action_title: Action + include_exclude: exclude + selected_actions: { } + entity_type: webform_submission + plugin_id: webform_submission_bulk_form + sid: + id: sid + table: webform_submission + field: sid + relationship: none + group_type: group + admin_label: '' + label: '#' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sid + plugin_id: field + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + label: Draft + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: in_draft + plugin_id: field + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + label: Sticky + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sticky + plugin_id: field + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + label: Locked + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: locked + plugin_id: field + created: + id: created + table: webform_submission + field: created + relationship: none + group_type: group + admin_label: '' + label: Created + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: created + plugin_id: field + completed: + id: completed + table: webform_submission + field: completed + relationship: none + group_type: group + admin_label: '' + label: Completed + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: completed + plugin_id: field + remote_addr: + id: remote_addr + table: webform_submission + field: remote_addr + relationship: none + group_type: group + admin_label: '' + label: 'IP address' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: remote_addr + plugin_id: field + operations: + id: operations + table: webform_submission + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: null + entity_field: null + plugin_id: entity_operations + filters: + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Is draft' + description: '' + use_operator: false + operator: in_draft_op + identifier: in_draft + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: in_draft + plugin_id: boolean + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Sticky + description: '' + use_operator: false + operator: sticky_op + identifier: sticky + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: sticky + plugin_id: boolean + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Locked + description: '' + use_operator: false + operator: locked_op + identifier: locked + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + demo_region: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: locked + plugin_id: boolean + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - user.permissions + tags: { } + embed_manage: + display_plugin: embed + id: embed_manage + display_title: 'Embed: Manage' + position: 3 + display_options: + display_extenders: { } + display_description: 'Manage submissions.' + fields: + webform_submission_bulk_form: + id: webform_submission_bulk_form + table: webform_submission + field: webform_submission_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Webform submission operations bulk form' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + action_title: Action + include_exclude: include + selected_actions: + - webform_submission_make_lock_action + - webform_submission_make_sticky_action + - webform_submission_make_unlock_action + - webform_submission_make_unsticky_action + entity_type: webform_submission + plugin_id: webform_submission_bulk_form + sid: + id: sid + table: webform_submission + field: sid + relationship: none + group_type: group + admin_label: '' + label: '#' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sid + plugin_id: field + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + label: Draft + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: in_draft + plugin_id: field + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + label: Sticky + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sticky + plugin_id: field + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + label: Locked + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: locked + plugin_id: field + created: + id: created + table: webform_submission + field: created + relationship: none + group_type: group + admin_label: '' + label: Created + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: created + plugin_id: field + completed: + id: completed + table: webform_submission + field: completed + relationship: none + group_type: group + admin_label: '' + label: Completed + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: completed + plugin_id: field + remote_addr: + id: remote_addr + table: webform_submission + field: remote_addr + relationship: none + group_type: group + admin_label: '' + label: 'IP address' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: remote_addr + plugin_id: field + view_webform_submission: + id: view_webform_submission + table: webform_submission + field: view_webform_submission + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: false + absolute: false + entity_type: webform_submission + plugin_id: entity_link + edit_webform_submission: + id: edit_webform_submission + table: webform_submission + field: edit_webform_submission + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: edit + output_url_as_text: false + absolute: false + entity_type: webform_submission + plugin_id: entity_link_edit + defaults: + fields: false + style: false + row: false + access: false + filters: false + filter_groups: false + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + webform_submission_bulk_form: webform_submission_bulk_form + sid: sid + in_draft: in_draft + sticky: sticky + locked: locked + created: created + completed: completed + remote_addr: remote_addr + view_webform_submission: view_webform_submission + edit_webform_submission: view_webform_submission + info: + webform_submission_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + in_draft: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + sticky: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + locked: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + completed: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + remote_addr: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + view_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + edit_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + access: + type: perm + options: + perm: 'edit any webform submission' + filters: + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Is draft' + description: '' + use_operator: false + operator: in_draft_op + identifier: in_draft + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: in_draft + plugin_id: boolean + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Sticky + description: '' + use_operator: false + operator: sticky_op + identifier: sticky + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: sticky + plugin_id: boolean + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Locked + description: '' + use_operator: false + operator: locked_op + identifier: locked + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + demo_region: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: locked + plugin_id: boolean + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - user.permissions + tags: { } + embed_review: + display_plugin: embed + id: embed_review + display_title: 'Embed: Review' + position: 4 + display_options: + display_extenders: { } + display_description: 'Review submissions.' + fields: + sid: + id: sid + table: webform_submission + field: sid + relationship: none + group_type: group + admin_label: '' + label: '#' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sid + plugin_id: field + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + label: Draft + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: in_draft + plugin_id: field + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + label: Sticky + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sticky + plugin_id: field + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + label: Locked + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: yes-no + format_custom_true: '' + format_custom_false: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: locked + plugin_id: field + created: + id: created + table: webform_submission + field: created + relationship: none + group_type: group + admin_label: '' + label: Created + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: created + plugin_id: field + completed: + id: completed + table: webform_submission + field: completed + relationship: none + group_type: group + admin_label: '' + label: Completed + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: completed + plugin_id: field + remote_addr: + id: remote_addr + table: webform_submission + field: remote_addr + relationship: none + group_type: group + admin_label: '' + label: 'IP address' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: remote_addr + plugin_id: field + view_webform_submission: + id: view_webform_submission + table: webform_submission + field: view_webform_submission + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + text: view + output_url_as_text: false + absolute: false + entity_type: webform_submission + plugin_id: entity_link + defaults: + fields: false + style: false + row: false + access: false + filters: false + filter_groups: false + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + webform_submission_bulk_form: webform_submission_bulk_form + sid: sid + in_draft: in_draft + sticky: sticky + locked: locked + created: created + completed: completed + remote_addr: remote_addr + view_webform_submission: view_webform_submission + edit_webform_submission: view_webform_submission + info: + webform_submission_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + in_draft: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + sticky: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + locked: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + completed: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + remote_addr: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + view_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: ' ' + empty_column: false + responsive: '' + edit_webform_submission: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + access: + type: perm + options: + perm: 'view any webform submission' + filters: + in_draft: + id: in_draft + table: webform_submission + field: in_draft + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Is draft' + description: '' + use_operator: false + operator: in_draft_op + identifier: in_draft + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: in_draft + plugin_id: boolean + sticky: + id: sticky + table: webform_submission + field: sticky + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Sticky + description: '' + use_operator: false + operator: sticky_op + identifier: sticky + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: sticky + plugin_id: boolean + locked: + id: locked + table: webform_submission + field: locked + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Locked + description: '' + use_operator: false + operator: locked_op + identifier: locked + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + demo_region: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: webform_submission + entity_field: locked + plugin_id: boolean + filter_groups: + operator: AND + groups: + 1: AND + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - user.permissions + tags: { } diff --git a/web/modules/webform/config/schema/webform.action.schema.yml b/web/modules/webform/config/schema/webform.action.schema.yml index 0cf20edbd7..4ebbd15057 100644 --- a/web/modules/webform/config/schema/webform.action.schema.yml +++ b/web/modules/webform/config/schema/webform.action.schema.yml @@ -1,19 +1,15 @@ action.configuration.webform_submission_delete_action: type: action_configuration_default label: 'Delete submission configuration' - action.configuration.webform_submission_make_sticky_action: type: action_configuration_default label: 'Star/Flag selected submission configuration' - action.configuration.webform_submission_make_unsticky_action: type: action_configuration_default label: 'Unstar/Unflag selected submission configuration' - action.configuration.webform_submission_make_lock_action: type: action_configuration_default label: 'Lock selected submission configuration' - action.configuration.webform_submission_make_unlock_action: type: action_configuration_default label: 'Unlock selected submission configuration' diff --git a/web/modules/webform/config/schema/webform.block.schema.yml b/web/modules/webform/config/schema/webform.block.schema.yml index 1b24a175e7..ebe1345221 100644 --- a/web/modules/webform/config/schema/webform.block.schema.yml +++ b/web/modules/webform/config/schema/webform.block.schema.yml @@ -4,23 +4,25 @@ block.settings.webform_block: mapping: webform_id: type: string - label: 'Webform' + label: Webform default_data: type: text label: 'Default webform submission data' - + redirect: + type: boolean + label: 'Redirect to the webform' block.settings.webform_submission_limit_block: type: block_settings label: 'Webform submission limits block' mapping: type: type: text - label: 'Type' + label: Type source_entity: type: boolean label: 'Source entity' content: - label: 'Content' + label: Content type: text progress_bar: type: boolean @@ -33,7 +35,7 @@ block.settings.webform_submission_limit_block: label: 'Progress bar message' webform_id: type: string - label: 'Webform' + label: Webform entity_type: type: string label: 'Source entity type' diff --git a/web/modules/webform/config/schema/webform.entity.webform.schema.yml b/web/modules/webform/config/schema/webform.entity.webform.schema.yml index 3c751a4156..804ebdb13c 100644 --- a/web/modules/webform/config/schema/webform.entity.webform.schema.yml +++ b/web/modules/webform/config/schema/webform.entity.webform.schema.yml @@ -1,10 +1,13 @@ -webform.webform.*: +'webform.webform.*': type: config_entity - label: 'Webforms' + label: Webforms mapping: status: type: string - label: 'Status' + label: Status + weight: + type: integer + label: Weight open: type: string label: 'Open date/time' @@ -13,22 +16,25 @@ webform.webform.*: label: 'Close date/time' uid: type: integer - label: 'Author' + label: Author template: type: boolean - label: 'Template' + label: Template + archive: + type: boolean + label: Archive id: type: string label: 'Machine name' title: type: label - label: 'Title' + label: Title description: type: label label: 'Administrative description' category: type: label - label: 'Category' + label: Category elements: type: text label: 'Elements (YAML)' @@ -37,12 +43,10 @@ webform.webform.*: label: 'CSS (Cascading Style Sheets)' javascript: type: string - label: 'JavaScript' + label: JavaScript settings: type: mapping - label: 'Settings' - # Below mapping is copied to: webform.handler.settings - #@see webform.plugin.handler.schema.yml + label: Settings mapping: ajax: type: boolean @@ -59,6 +63,9 @@ webform.webform.*: page_confirm_path: type: string label: 'Page confirm URL alias' + form_title: + type: string + label: 'Form title display' form_submit_once: type: boolean label: 'Prevent duplicate submissions' @@ -80,6 +87,9 @@ webform.webform.*: form_confidential_message: type: text label: 'Form confidential message' + form_remote_addr: + type: boolean + label: 'Track user IP address' form_convert_anonymous: type: boolean label: 'Convert anonymous drafts and submissions to authenticated' @@ -118,19 +128,28 @@ webform.webform.*: label: 'Display required indicator' form_autofocus: type: boolean - label: 'Autofocus' + label: Autofocus form_details_toggle: type: boolean label: 'Display collapse/expand all details link' form_reset: type: boolean label: 'Display reset button' - form_login: - type: boolean - label: 'Redirect to login when access denied to webform' - form_login_message: + form_access_denied: + type: string + label: 'Form access denied action' + form_access_denied_title: + type: label + label: 'Form access denied title' + form_access_denied_message: type: text - label: 'Login message when access denied to webform' + label: 'Form access denied message' + form_access_denied_attributes: + type: ignore + label: 'Form access denied message attributes' + form_file_limit: + type: string + label: 'Form file upload limit' submission_label: type: label label: 'Default submission label' @@ -143,18 +162,92 @@ webform.webform.*: submission_log: type: boolean label: 'Submission logging' + submission_excluded_elements: + type: sequence + label: 'Submission excluded elements' + sequence: + type: string + label: 'Element key' + submission_exclude_empty: + type: boolean + label: 'Submission exclude empty elements' + submission_exclude_empty_checkbox: + type: boolean + label: 'Submission exclude unselected checkboxes' + submission_views: + type: sequence + label: 'Submission views' + sequence: + type: mapping + label: 'Submission view' + mapping: + title: + type: text + label: Title + view: + type: string + label: 'View name / Display ID' + webform_routes: + type: sequence + label: 'Apply to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Apply to node' + sequence: + type: string + label: Route + submission_views_replace: + type: mapping + label: 'Submission view replace' + mapping: + global_routes: + type: sequence + label: 'Replace to global' + sequence: + type: string + label: Route + webform_routes: + type: sequence + label: 'Replace to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Replace to node' + sequence: + type: string + label: Route submission_user_columns: type: sequence label: 'Submission user columns' sequence: type: string label: 'Column name' - submission_login: + submission_user_duplicate: type: boolean - label: 'Redirect to login when access denied to submission' - submission_login_message: + label: 'Submission user duplicate' + submission_access_denied: + type: string + label: 'Submission access denied action' + submission_access_denied_title: + type: label + label: 'Submission access denied title' + submission_access_denied_message: + type: text + label: 'Submission access denied message' + submission_access_denied_attributes: + type: ignore + label: 'Submission access denied message attributes' + previous_submission_message: type: text - label: 'Login message when access denied to submission' + label: 'Previous submission message' + previous_submissions_message: + type: text + label: 'Previous submissions message' autofill: type: boolean label: 'Autofill with previous submission' @@ -176,12 +269,18 @@ webform.webform.*: wizard_progress_percentage: type: boolean label: 'Show wizard progress pages' + wizard_progress_link: + type: boolean + label: 'Link to previous pages in progress bar' wizard_start_label: type: label label: 'Wizard start label' wizard_start_attributes: type: ignore label: 'Wizard start attributes' + wizard_preview_link: + type: boolean + label: 'Link to previous pages in preview' wizard_confirmation: type: boolean label: 'Include confirmation page in progress' @@ -215,6 +314,9 @@ webform.webform.*: preview_exclude_empty: type: boolean label: 'Preview exclude empty elements' + preview_exclude_empty_checkbox: + type: boolean + label: 'Preview exclude unselected checkboxes' draft: type: string label: 'Allow users to save and finish the webform later.' @@ -237,7 +339,7 @@ webform.webform.*: type: string label: 'Confirmation URL' confirmation_title: - type: text + type: label label: 'Confirmation title' confirmation_message: type: text @@ -269,6 +371,9 @@ webform.webform.*: limit_total_message: type: text label: 'Limit total message' + limit_total_unique: + type: boolean + label: 'Limit total to one submission per source entity' limit_user: type: integer label: 'Limit user submissions' @@ -278,6 +383,9 @@ webform.webform.*: limit_user_message: type: text label: 'Limit user message' + limit_user_unique: + type: boolean + label: 'Limit user to one submission per source entity' entity_limit_total: type: integer label: 'Entity limit total submissions' @@ -306,229 +414,30 @@ webform.webform.*: type: boolean label: 'Allow updates using token' access: - type: mapping - label: 'Access' - mapping: - create: - type: mapping - label: 'Create webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - view_any: - type: mapping - label: 'View any webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - update_any: - type: mapping - label: 'Update any webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - delete_any: - type: mapping - label: 'Delete any webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - purge_any: - type: mapping - label: 'Purge any webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - view_own: - type: mapping - label: 'View own webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - update_own: - type: mapping - label: 'Update own webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - delete_own: - type: mapping - label: 'Delete own webform submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - administer: - type: mapping - label: 'Administer webform and submissions' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' - test: - type: mapping - label: 'Test webform' - mapping: - roles: - type: sequence - label: 'Roles' - sequence: - type: string - label: 'Role' - users: - type: sequence - label: 'Users' - sequence: - type: integer - label: 'User IDs' - permissions: - type: sequence - label: 'Permissions' - sequence: - type: string - label: 'Permission' + type: sequence + label: 'Access Rules' + sequence: + type: mapping + label: 'Access Rule' + mapping: + roles: + type: sequence + label: Roles + sequence: + type: string + label: Role + users: + type: sequence + label: Users + sequence: + type: integer + label: 'User IDs' + permissions: + type: sequence + label: Permissions + sequence: + type: string + label: Permission handlers: type: sequence label: 'Webform handlers' @@ -543,20 +452,20 @@ webform.webform.*: label: 'Handler instance ID' label: type: label - label: 'Label' + label: Label status: type: boolean - label: 'Status' + label: Status conditions: type: ignore label: 'Conditional logic' weight: type: integer - label: 'Weight' + label: Weight settings: - type: webform.handler.[%parent.id] + type: 'webform.handler.[%parent.id]' third_party_settings: type: sequence label: 'Third party settings' sequence: - type: webform.settings.third_party.[%key] + type: 'webform.settings.third_party.[%key]' diff --git a/web/modules/webform/config/schema/webform.entity.webform_options.schema.yml b/web/modules/webform/config/schema/webform.entity.webform_options.schema.yml index cee99cc44a..9973faf295 100644 --- a/web/modules/webform/config/schema/webform.entity.webform_options.schema.yml +++ b/web/modules/webform/config/schema/webform.entity.webform_options.schema.yml @@ -1,16 +1,16 @@ -webform.webform_options.*: +'webform.webform_options.*': type: config_entity - label: 'Options' + label: Options mapping: id: type: string label: 'Machine name' label: type: label - label: 'Label' + label: Label category: type: label - label: 'Category' + label: Category likert: type: boolean label: 'Use as Likert' diff --git a/web/modules/webform/config/schema/webform.field.schema.yml b/web/modules/webform/config/schema/webform.field.schema.yml index caa1d1558d..14582b2600 100644 --- a/web/modules/webform/config/schema/webform.field.schema.yml +++ b/web/modules/webform/config/schema/webform.field.schema.yml @@ -1,3 +1,4 @@ +# @see field.storage_settings.entity_reference field.storage_settings.webform: type: mapping label: 'Webform field storage settings' @@ -6,22 +7,25 @@ field.storage_settings.webform: type: string label: 'Type of item to reference' -base_webform_field_field_settings: +# @see field.field_settings.entity_reference +field.field_settings.webform: type: mapping + label: 'Entity reference field settings' mapping: handler: type: string label: 'Reference method' handler_settings: type: entity_reference_selection.[%parent.handler] - label: 'Entity reference selection settings' + label: 'Entity reference selection plugin settings' -field.field_settings.webform: - type: base_webform_field_field_settings - label: 'Webform settings' +# @see field.value.entity_reference +field.value.webform: + type: field.value.entity_reference + label: 'Default value' mapping: default_data: - type: string + type: text label: 'Default webform submission data' status: type: string @@ -45,7 +49,7 @@ field.widget.settings.webform_entity_reference_autocomplete: label: 'Size of textfield' placeholder: type: label - label: 'Placeholder' + label: Placeholder default_data: type: boolean label: 'Default submission data' @@ -57,7 +61,6 @@ field.widget.settings.webform_entity_reference_select: default_data: type: boolean label: 'Default submission data' - field.formatter.settings.webform_entity_reference_entity_view: type: mapping label: 'Display the referenced webform with default submission data.' @@ -73,3 +76,9 @@ field.formatter.settings.webform_entity_reference_link: label: type: label label: 'Link label to the referenced webform' + dialog: + type: string + label: 'Open referenced webform in modal dialog' + attributes: + type: ignore + label: 'Link attributes' diff --git a/web/modules/webform/config/schema/webform.plugin.exporter.schema.yml b/web/modules/webform/config/schema/webform.plugin.exporter.schema.yml index 742724c9ad..161d852deb 100644 --- a/web/modules/webform/config/schema/webform.plugin.exporter.schema.yml +++ b/web/modules/webform/config/schema/webform.plugin.exporter.schema.yml @@ -1,11 +1,13 @@ -webform.exporter.*: +'webform.exporter.*': type: mapping label: 'Exporter settings' - webform.exporter.delimited_text: type: mapping - label: 'Delimiter' + label: Delimiter mapping: delimiter: type: string - label: 'Delimiter' + label: Delimiter + excel: + type: boolean + label: Excel diff --git a/web/modules/webform/config/schema/webform.plugin.handler.schema.yml b/web/modules/webform/config/schema/webform.plugin.handler.schema.yml index efa4556189..35130ebda7 100644 --- a/web/modules/webform/config/schema/webform.plugin.handler.schema.yml +++ b/web/modules/webform/config/schema/webform.plugin.handler.schema.yml @@ -1,31 +1,30 @@ -webform.handler.*: +'webform.handler.*': type: mapping label: 'Handler settings' - webform.handler.action: type: mapping - label: 'Action' + label: Action mapping: states: type: sequence - label: 'States' + label: States sequence: type: string - label: 'State' + label: State notes: - label: 'Notes' + label: Notes type: text sticky: - label: 'Flag' + label: Flag type: boolean locked: - label: 'Locked' + label: Locked type: boolean data: - label: 'Data' + label: Data type: text message: - label: 'Message' + label: Message type: text message_type: label: 'Message type' @@ -33,22 +32,20 @@ webform.handler.action: debug: type: boolean label: 'Enable debugging' - webform.handler.log: type: mapping - label: 'Log' + label: Log mapping: { } - webform.handler.email: type: mapping - label: 'Email' + label: Email mapping: states: type: sequence - label: 'States' + label: States sequence: type: string - label: 'State' + label: State to_mail: label: 'Email to address' type: email @@ -105,29 +102,34 @@ webform.handler.email: label: 'Always include private and restricted access elements.' exclude_empty: type: boolean - label: 'Preview exclude empty elements' + label: 'Exclude empty elements' + exclude_empty_checkbox: + type: boolean + label: 'Exclude unselected checkboxes' html: type: boolean - label: 'HTML' + label: HTML attachments: type: boolean - label: 'Attachments' + label: Attachments twig: type: boolean - label: 'Twig' + label: Twig + theme_name: + type: string + label: 'Theme name' debug: type: boolean label: 'Enable debugging' - webform.handler.remote_post: type: mapping label: 'Remote Post' mapping: method: - label: 'Method' + label: Method type: string type: - label: 'Type' + label: Type type: string excluded_data: type: sequence @@ -182,7 +184,7 @@ webform.handler.remote_post: type: sequence sequence: type: mapping - label: 'Message' + label: Message mapping: code: type: integer @@ -190,18 +192,13 @@ webform.handler.remote_post: message: type: text label: 'Response message' - webform.handler.settings: type: mapping - label: 'Settings' + label: Settings mapping: debug: type: string label: 'Enable debugging' - # Below mapping is copied from: webform.webform.* - # @see webform.entity.webform.schema.yml - # NOTE: 'type: string' is changed to 'type: string - # NOTE: 'type: string' is changed to 'type: string ajax: type: string label: 'Use Ajax' @@ -276,19 +273,25 @@ webform.handler.settings: label: 'Display required indicator' form_autofocus: type: string - label: 'Autofocus' + label: Autofocus form_details_toggle: type: string label: 'Display collapse/expand all details link' form_reset: type: string label: 'Display reset button' - form_login: + form_access_denied: type: string - label: 'Redirect to login when access denied to webform' - form_login_message: + label: 'Form access denied action' + form_access_denied_title: + type: label + label: 'Form access denied title' + form_access_denied_message: type: text - label: 'Login message when access denied to webform' + label: 'Form access denied message' + form_access_denied_attributes: + type: ignore + label: 'Form access denied message attributes' submission_label: type: label label: 'Default submission label' @@ -301,18 +304,80 @@ webform.handler.settings: submission_log: type: string label: 'Submission logging' + submission_views: + type: sequence + label: 'Submission views' + sequence: + type: mapping + label: 'Submission view' + mapping: + title: + type: text + label: Title + view: + type: string + label: 'View name / Display ID' + webform_routes: + type: sequence + label: 'Apply to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Apply to node' + sequence: + type: string + label: Route + submission_views_replace: + type: mapping + label: 'Submission view replace' + mapping: + global_routes: + type: sequence + label: 'Replace to global' + sequence: + type: string + label: Route + webform_routes: + type: sequence + label: 'Replace to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Replace to node' + sequence: + type: string + label: Route submission_user_columns: type: sequence label: 'Submission user columns' sequence: type: string label: 'Column name' - submission_login: + submission_user_duplicate: + type: string + label: 'Submission user duplicate' + submission_access_denied: type: string - label: 'Redirect to login when access denied to submission' - submission_login_message: + label: 'Submission access denied action' + submission_access_denied_title: + type: label + label: 'Submission access denied title' + submission_access_denied_message: + type: text + label: 'Submission access denied message' + submission_access_denied_attributes: + type: ignore + label: 'Submission access denied message attributes' + previous_submission_message: type: text - label: 'Login message when access denied to submission' + label: 'Previous submission message' + previous_submissions_message: + type: text + label: 'Previous submissions message' autofill: type: string label: 'Autofill with previous submission' @@ -334,12 +399,18 @@ webform.handler.settings: wizard_progress_percentage: type: string label: 'Show wizard progress pages' + wizard_progress_link: + type: string + label: 'Link to previous pages in progress bar' wizard_start_label: type: label label: 'Wizard start label' wizard_start_attributes: type: ignore label: 'Wizard start attributes' + wizard_preview_link: + type: string + label: 'Link to previous pages in preview' wizard_confirmation: type: string label: 'Include confirmation page in progress' @@ -373,6 +444,9 @@ webform.handler.settings: preview_exclude_empty: type: string label: 'Preview exclude empty elements' + preview_exclude_empty_checkbox: + type: string + label: 'Preview exclude unselected checkboxes' draft: type: string label: 'Allow users to save and finish the webform later.' @@ -395,7 +469,7 @@ webform.handler.settings: type: string label: 'Confirmation URL' confirmation_title: - type: text + type: label label: 'Confirmation title' confirmation_message: type: text @@ -427,6 +501,9 @@ webform.handler.settings: limit_total_message: type: text label: 'Limit total message' + limit_total_unique: + type: string + label: 'Limit total to one submission per source entity' limit_user: type: string label: 'Limit user submissions' @@ -436,6 +513,9 @@ webform.handler.settings: limit_user_message: type: text label: 'Limit user message' + limit_user_unique: + type: string + label: 'Limit user to one submission per source entity' entity_limit_total: type: string label: 'Entity limit total submissions' diff --git a/web/modules/webform/config/schema/webform.settings.schema.yml b/web/modules/webform/config/schema/webform.settings.schema.yml index d29a2e7429..546b0f5833 100644 --- a/web/modules/webform/config/schema/webform.settings.schema.yml +++ b/web/modules/webform/config/schema/webform.settings.schema.yml @@ -6,33 +6,36 @@ webform.settings: type: mapping label: 'Webform default settings' mapping: + default_status: + type: string + label: 'Default status' default_page_base_path: type: string label: 'Default base path' default_submit_button_label: type: label - label: 'Default webform submit button label' + label: 'Default submit button label' default_reset_button_label: type: label - label: 'Default webform reset button label' + label: 'Default reset button label' default_form_submit_once: type: boolean label: 'Prevent duplicate submissions' default_form_open_message: type: text - label: 'Default webform open message' + label: 'Default form open message' default_form_close_message: type: text - label: 'Default webform close message' + label: 'Default form close message' default_form_exception_message: type: text - label: 'Default webform exception message' + label: 'Default form exception message' default_form_confidential_message: type: text - label: 'Default webform confidential message' - default_form_login_message: + label: 'Default form confidential message' + default_form_access_denied_message: type: text - label: 'Default login message when access denied to webform' + label: 'Default form access denied message' default_form_novalidate: type: boolean label: 'Disable client-side validation' @@ -57,6 +60,9 @@ webform.settings: default_form_details_toggle: type: boolean label: 'Display collapse/expand all details link' + default_form_file_limit: + type: string + label: 'Default file upload limit per form' default_wizard_prev_button_label: type: label label: 'Default wizard previous page button label' @@ -111,15 +117,74 @@ webform.settings: default_submission_log: type: boolean label: 'Default submission logging' - default_submission_login_message: + default_submission_views: + type: sequence + label: 'Default submission views' + sequence: + type: mapping + label: 'Submission view' + mapping: + title: + type: text + label: Title + view: + type: string + label: 'View name / Display ID' + global_routes: + type: sequence + label: 'Apply to global' + sequence: + type: string + label: Route + webform_routes: + type: sequence + label: 'Apply to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Apply to node' + sequence: + type: string + label: Route + default_submission_views_replace: + type: mapping + label: 'Default submission view replace' + mapping: + global_routes: + type: sequence + label: 'Replace to global' + sequence: + type: string + label: Route + webform_routes: + type: sequence + label: 'Replace to webform' + sequence: + type: string + label: Route + node_routes: + type: sequence + label: 'Replace to node' + sequence: + type: string + label: Route + default_submission_access_denied_message: type: text - label: 'Default login message when access denied to submission' + label: 'Default submission access denied message' default_submission_exception_message: type: text label: 'Default submission exception message' default_submission_locked_message: type: text label: 'Default submission locked message' + default_previous_submission_message: + type: text + label: 'Default previous submission message' + default_previous_submissions_message: + type: text + label: 'Default previous submissions message' default_autofill_message: type: text label: 'Default submission autofill message' @@ -138,6 +203,28 @@ webform.settings: confirmation_back_classes: type: string label: 'Confirmation back link CSS classes' + dialog: + type: boolean + label: 'Enable webform dialog support' + dialog_options: + type: sequence + label: 'Preset dialog options' + sequence: + type: mapping + label: 'Dialog options' + mapping: + name: + type: string + label: 'Dialog name' + title: + type: label + label: 'Dialog title' + width: + type: integer + label: 'Dialog width' + height: + type: integer + label: 'Dialog height' assets: type: mapping label: 'Global Assets (CSS/JavaScript)' @@ -147,7 +234,7 @@ webform.settings: label: 'CSS (Cascading Style Sheets)' javascript: type: string - label: 'JavaScript' + label: JavaScript element: type: mapping label: 'Element default settings' @@ -191,6 +278,12 @@ webform.settings: default_google_maps_api_key: type: string label: 'Default Google Maps API key' + default_algolia_places_app_id: + type: string + label: 'Default Algolia application id' + default_algolia_places_api_key: + type: string + label: 'Default Algolia API key' excluded_elements: type: ignore label: 'Excluded types' @@ -201,12 +294,18 @@ webform.settings: disabled: type: boolean label: 'Disable HTML editor' - format: + element_format: type: string - label: 'Text format' + label: 'Element text format' + mail_format: + type: string + label: 'Mail text format' tidy: type: boolean label: 'Tidy HTML markup' + make_unused_managed_files_temporary: + type: boolean + label: 'Controls if unused HTML editor files should be marked temporary' file: type: mapping label: 'File upload default settings' @@ -222,7 +321,7 @@ webform.settings: label: 'Login message when access denied to private file uploads.' default_max_filesize: type: string - label: 'Default maximum upload size' + label: 'Default maximum file upload size' default_managed_file_extensions: type: string label: 'Default allowed managed file extensions' @@ -240,10 +339,10 @@ webform.settings: label: 'Default allowed document file extensions' make_unused_managed_files_temporary: type: boolean - label: 'Controls if unused files should be marked temporary' + label: 'Controls if unused webform submission files should be marked temporary' delete_temporary_managed_files: type: boolean - label: 'Immediately deleted temporary managed files' + label: 'Immediately deleted temporary managed webform submission files' format: type: ignore label: 'Format default settings' @@ -283,14 +382,17 @@ webform.settings: label: 'Default email body (HTML)' roles: type: sequence - label: 'Roles' + label: Roles sequence: type: string - label: 'Role' + label: Role export: type: mapping label: 'Export default settings' mapping: + temp_directory: + type: string + label: 'Export temporary directory' exporter: type: string label: 'Results exporter' @@ -359,6 +461,9 @@ webform.settings: default_batch_export_size: type: integer label: 'Batch export size' + default_batch_import_size: + type: integer + label: 'Batch import size' default_batch_update_size: type: integer label: 'Batch update size' @@ -392,6 +497,9 @@ webform.settings: video_display: type: string label: 'Video display' + help_disabled: + type: boolean + label: 'Disable help' dialog_disabled: type: boolean label: 'Disable dialogs' @@ -422,7 +530,7 @@ webform.settings: label: 'Use CDN' requirements: type: mapping - label: 'Requirements' + label: Requirements mapping: cdn: type: boolean @@ -435,7 +543,7 @@ webform.settings: label: 'Check if SPAM protection module is installed' contribute: type: mapping - label: 'Contribute' + label: Contribute mapping: account_type: type: string @@ -447,4 +555,4 @@ webform.settings: type: sequence label: 'Third party settings' sequence: - type: webform.admin_settings.third_party.[%key] + type: 'webform.admin_settings.third_party.[%key]' diff --git a/web/modules/webform/config/schema/webform.third_party.captcha.schema.yml b/web/modules/webform/config/schema/webform.third_party.captcha.schema.yml new file mode 100644 index 0000000000..fbbc4c2ada --- /dev/null +++ b/web/modules/webform/config/schema/webform.third_party.captcha.schema.yml @@ -0,0 +1,7 @@ +webform.admin_settings.third_party.captcha: + type: mapping + label: 'CAPTCHA third party settings' + mapping: + replace_administration_mode: + type: boolean + label: 'Replace ''Add CAPTCHA administration links to forms'' with CAPTCHA webform element' diff --git a/web/modules/webform/config/schema/webform.third_party.honeypot.schema.yml b/web/modules/webform/config/schema/webform.third_party.honeypot.schema.yml index a85d6cddec..21e4469c78 100644 --- a/web/modules/webform/config/schema/webform.third_party.honeypot.schema.yml +++ b/web/modules/webform/config/schema/webform.third_party.honeypot.schema.yml @@ -1,6 +1,6 @@ webform.admin_settings.third_party.honeypot: type: mapping - label: 'Webform test third party settings' + label: 'Honeypot third party settings' mapping: honeypot: type: boolean @@ -8,7 +8,6 @@ webform.admin_settings.third_party.honeypot: time_restriction: type: boolean label: 'Add time limit to all webforms' - webform.settings.third_party.honeypot: type: mapping label: 'Webform test third party settings' diff --git a/web/modules/webform/css/webform.addons.css b/web/modules/webform/css/webform.addons.css index 25284d472b..6d79fb45de 100644 --- a/web/modules/webform/css/webform.addons.css +++ b/web/modules/webform/css/webform.addons.css @@ -3,6 +3,11 @@ * Addons styles */ +.webform-addons-summary { + float: left; + margin: 0 0 0.5em 0; +} + /** * Projects. */ diff --git a/web/modules/webform/css/webform.admin.composite.css b/web/modules/webform/css/webform.admin.composite.css index 018b22903d..a4af14e8ec 100644 --- a/web/modules/webform/css/webform.admin.composite.css +++ b/web/modules/webform/css/webform.admin.composite.css @@ -13,6 +13,6 @@ width: 100%; } -.webform-admin-composite-elements table .form-type-checkbox { +.webform-admin-composite-elements table .form-type-checkbox.form-no-label { text-align: center; } diff --git a/web/modules/webform/css/webform.admin.css b/web/modules/webform/css/webform.admin.css index 82b7176767..b32a6bf89a 100644 --- a/web/modules/webform/css/webform.admin.css +++ b/web/modules/webform/css/webform.admin.css @@ -10,6 +10,14 @@ margin-bottom: 0; } +/** + * Submission. + */ + +.webform-submission-information .button { + font-size: 1em; +} + /** * Webform setting button displays a gear icon. * @see /admin/structure/webform/manage/contact/results/submissions @@ -23,34 +31,13 @@ font-size: 1.2em; } -/** - * Filter webform and submissions. - * @see /admin/structure/webform - * @see /admin/structure/webform/templates - */ -.webform-filter-form .form-submit { - margin-left: 0; - margin-right: 5px; -} - -.webform-filter-form .form-item { - margin-right: 5px; -} - -@media screen and (max-width: 600px) { - .webform-filter-form .form-item { - display: block; - margin-right: 0; - } -} - /** * Results table. * @see /admin/structure/webform/submissions/manage * @see /admin/structure/webform/manage/contact/results/submissions */ -.webform-results__table th, -.webform-results__table td { +.webform-results-table th, +.webform-results-table td { max-width: 400px; overflow: hidden; white-space: nowrap; @@ -58,44 +45,35 @@ } /* Make sure 'Operations' is never cut-off. */ -.webform-results__table td.webform-dropbutton-wrapper { +.webform-results-table td.webform-dropbutton-wrapper { overflow: visible; max-width: inherit; white-space: normal; } -th.webform-results__icon, -td.webform-results__icon { +th.webform-results-table__icon, +td.webform-results-table__icon { padding-left: 0; padding-right: 0; } /* Hide throbber, which breaks icon alignment */ -.webform-results__icon .ajax-progress-throbber, -a.webform-results__custom + .ajax-progress-throbber { +.webform-results-table__icon .ajax-progress-throbber { display: none; } /* Entire row is clickable. @see Drupal.behaviors.webformTableRowHref */ -.webform-results__table tbody tr { +.webform-results-table tbody tr { cursor: pointer; cursor: hand; } /* Limit image height to 60px */ -.webform-results__table td img { +.webform-results-table td img { max-height: 60px; width: auto; } -/** - * Results custom(ize) dialog. - */ -.webform-results__custom .tabledrag-changed-warning, -.webform-results__custom .tabledrag-changed /* Hide table drag warnings. */ { - display: none !important; /* Must use !important because .tabledrag-changed 'display' is set via JavaScript */ -} - /** * Results icons (notes, sticky, and locked) */ @@ -184,15 +162,37 @@ a:focus .webform-icon-locked--off { * Submission view table. * @see /admin/structure/webform/manage/{webform_id}/submission/{webform_submission_id}/table */ -.webform-submission__table th { +.webform-submission-table th { width: 33%; min-width: 100px; } -.webform-submission__table td { +.webform-submission-table td { width: 66%; } .webform-horizontal-rule { margin: .5em 0; } + +/** + * Handlers table. + * @see /admin/structure/webform/manage/{webform_id}/handlers + */ +.webform-handlers-table .ajax-progress-throbber { + display: none; +} + +@media screen and (max-width: 1280px) { + .js-off-canvas-dialog-open .webform-handlers-table th, + .js-off-canvas-dialog-open .webform-handlers-table td { + display: none; + } + + .js-off-canvas-dialog-open .webform-handlers-table th:first-child, + .js-off-canvas-dialog-open .webform-handlers-table th:last-child, + .js-off-canvas-dialog-open .webform-handlers-table td:first-child, + .js-off-canvas-dialog-open .webform-handlers-table td:last-child { + display: table-cell; + } +} diff --git a/web/modules/webform/css/webform.admin.dropbutton.css b/web/modules/webform/css/webform.admin.dropbutton.css index 9f76a15a63..e7eb6cbc9d 100644 --- a/web/modules/webform/css/webform.admin.dropbutton.css +++ b/web/modules/webform/css/webform.admin.dropbutton.css @@ -8,6 +8,19 @@ * @see /admin/structure/webform/manage/{webform}/handlers */ +/** + * Fix webform submission views dropdown display. + */ +.js .webform-submission-views-dropbutton .dropbutton-wrapper { + display: block; + position: relative; + min-height: 2em; +} + +.js .webform-submission-views-dropbutton .dropbutton-widget { + position: absolute; +} + /** * Hide dropbutton to prevent FOUC (flash of unstyled content). */ diff --git a/web/modules/webform/css/webform.admin.settings.css b/web/modules/webform/css/webform.admin.settings.css new file mode 100644 index 0000000000..4af9a04066 --- /dev/null +++ b/web/modules/webform/css/webform.admin.settings.css @@ -0,0 +1,13 @@ +/** + * @file + * Admin settings styles + */ + +/** + * Dialog test button. + * @see /admin/structure/webform/manage/{webform}/settings + */ +.webform-settings-form .webform-dialog.button { + width: 100%; + white-space: nowrap; +} diff --git a/web/modules/webform/css/webform.admin.tabledrag.css b/web/modules/webform/css/webform.admin.tabledrag.css new file mode 100644 index 0000000000..ce21249c76 --- /dev/null +++ b/web/modules/webform/css/webform.admin.tabledrag.css @@ -0,0 +1,29 @@ +/** + * @file + * Tabledrag styles. + */ + +.tabledrag-toggle-weight-wrapper { + position: relative; +} + +.tabledrag-toggle-weight-wrapper button { + position: absolute; + top: -2.5em; + right: 0; +} + +@media screen and (max-width: 960px) { + .tabledrag-toggle-weight-wrapper { + float: right; + } + .tabledrag-toggle-weight-wrapper button { + position: static; + top: inherit; + right: inherit; + } +} + +.webform-tabledrag-hide { + display: none; !important; +} diff --git a/web/modules/webform/css/webform.ajax.css b/web/modules/webform/css/webform.ajax.css index d612690be0..9468e95d41 100644 --- a/web/modules/webform/css/webform.ajax.css +++ b/web/modules/webform/css/webform.ajax.css @@ -46,10 +46,10 @@ * @see core/misc/dialog/dialog.position.js * @see \Drupal\webform\Utility\WebformDialogHelper::getModalDialogAttributes */ -.webform-modal { +.webform-ui-dialog { top: 50px !important; } -.toolbar-tray-open.toolbar-horizontal .webform-modal { +.toolbar-tray-open.toolbar-horizontal .webform-ui-dialog { top: 90px !important; } diff --git a/web/modules/webform/css/webform.composite.css b/web/modules/webform/css/webform.composite.css index e545cb16c2..550c2d5594 100644 --- a/web/modules/webform/css/webform.composite.css +++ b/web/modules/webform/css/webform.composite.css @@ -6,6 +6,19 @@ /** * Remove extra margin are composite element which already contain form elements with margins */ -.form-composite { - margin: 0; +fieldset.webform-composite-hidden-title { + margin-top: 1em; + margin-bottom: 1em; +} + +fieldset.webform-composite-hidden-title .fieldset-wrapper > .form-item:first-child, +fieldset.webform-composite-hidden-title .fieldset-wrapper > .form-wrapper > .form-item:first-child, +fieldset.webform-composite-hidden-title .fieldset-wrapper > .webform-flexbox:first-child { + margin-top: 0; +} + +fieldset.webform-composite-hidden-title .fieldset-wrapper > .form-item:last-child, +fieldset.webform-composite-hidden-title .fieldset-wrapper > .form-wrapper > .form-item:last-child, +fieldset.webform-composite-hidden-title .fieldset-wrapper > .webform-flexbox:last-child { + margin-bottom: 0; } diff --git a/web/modules/webform/css/webform.element.checkbox_value.css b/web/modules/webform/css/webform.element.checkbox_value.css index 2c1edd7723..5067fbddb7 100644 --- a/web/modules/webform/css/webform.element.checkbox_value.css +++ b/web/modules/webform/css/webform.element.checkbox_value.css @@ -6,5 +6,5 @@ */ .form-item.form-type-webform-checkbox-value { - margin: 0 + margin: 0; } diff --git a/web/modules/webform/css/webform.element.composite.css b/web/modules/webform/css/webform.element.composite.css index d80dda63ae..d1147f9f6b 100644 --- a/web/modules/webform/css/webform.element.composite.css +++ b/web/modules/webform/css/webform.element.composite.css @@ -13,7 +13,7 @@ width: 100%; } -.form-type-webform-element-composite table .form-type-checkbox { +.form-type-webform-element-composite table .form-type-checkbox.form-no-label { text-align: center; } diff --git a/web/modules/webform/css/webform.element.computed.css b/web/modules/webform/css/webform.element.computed.css new file mode 100644 index 0000000000..8979b4105f --- /dev/null +++ b/web/modules/webform/css/webform.element.computed.css @@ -0,0 +1,8 @@ +/** + * @file + * Element computed styles. + */ + +.webform-computed-loading { + background-color: #ffffaa; +} diff --git a/web/modules/webform/css/webform.element.counter.css b/web/modules/webform/css/webform.element.counter.css new file mode 100644 index 0000000000..684e64d654 --- /dev/null +++ b/web/modules/webform/css/webform.element.counter.css @@ -0,0 +1,17 @@ +/** + * @file + * Element counter styles. + */ + +input.webform-counter-warning, +input.form-text.webform-counter-warning, +textarea.webform-counter-warning, +textarea.form-textarea.webform-counter-warning { + border-color: #e09600; + background: #fdf8ed; +} + +.text-count-wrapper.webform-counter-warning { + color: #734c00; + font-weight: bold; +} diff --git a/web/modules/webform/css/webform.element.date.css b/web/modules/webform/css/webform.element.date.css new file mode 100644 index 0000000000..f51802f1d2 --- /dev/null +++ b/web/modules/webform/css/webform.element.date.css @@ -0,0 +1,8 @@ +/** + * @file + * Element date styles. + */ + +.ui-datepicker-trigger { + margin: 0 2px; +} diff --git a/web/modules/webform/css/webform.element.datelist.css b/web/modules/webform/css/webform.element.datelist.css new file mode 100644 index 0000000000..8381b151a5 --- /dev/null +++ b/web/modules/webform/css/webform.element.datelist.css @@ -0,0 +1,8 @@ +/** + * @file + * Element datelist styles. + */ + +.form-type-datelist input[type="text"] { + max-width: 5em; +} diff --git a/web/modules/webform/css/webform.element.details.toggle.css b/web/modules/webform/css/webform.element.details.toggle.css index f980233eb8..d7fbccc79c 100644 --- a/web/modules/webform/css/webform.element.details.toggle.css +++ b/web/modules/webform/css/webform.element.details.toggle.css @@ -19,3 +19,34 @@ .webform-details-toggle-state-wrapper + details { margin-top: 0; } + +.webform-details-toggle-state { + background: transparent; + border: 0; + cursor: pointer; + margin-top: 0; + padding: 0; + font-size: 1em; +} + +/* Tweak details toggle state. */ +.webform-details-toggle-state { + color: #337ab7; + text-decoration: none; +} + +.webform-details-toggle-state:hover, +.webform-details-toggle-statelink:focus { + text-decoration: underline; +} + +/* Float toogle to the right of webform tabs */ +.webform-tabs .webform-details-toggle-state-wrapper { + float: right; +} + +@media screen and (max-width: 600px) { + .webform-tabs .webform-details-toggle-state-wrapper { + float: none; + } +} diff --git a/web/modules/webform/css/webform.element.help.css b/web/modules/webform/css/webform.element.help.css index 9bf6d433be..79f01f6e06 100644 --- a/web/modules/webform/css/webform.element.help.css +++ b/web/modules/webform/css/webform.element.help.css @@ -5,11 +5,14 @@ .webform-element-help { display: inline-block; + box-sizing: content-box; border: 2px solid #bbb; background: #bbb; line-height: 14px; + height: 14px; width: 14px; font-size: 12px; + color: #fff; border-radius: 50%; font-weight: bold; text-align: center; @@ -17,22 +20,27 @@ margin: 0 .3em; } -.webform-element-help:link, -.webform-element-help:visited { - color: #fff; - text-decoration: none; -} - .webform-element-help:focus, .webform-element-help:active, .webform-element-help:hover { border: 2px solid #0074bd; background: #0074bd; color: #fff; - text-decoration: none; cursor: help; } +.webform-element-help--title { + font-weight: bold; + font-size: 1.1em; + margin: 0 0 .2em 0; +} + .ui-tooltip.webform-element-help--tooltip { max-width: 400px; } + +@media only screen and (max-width : 400px) { + .ui-tooltip.webform-element-help--tooltip { + max-width: 300px; + } +} diff --git a/web/modules/webform/css/webform.element.image_file.css b/web/modules/webform/css/webform.element.image_file.css index 99bd1abbc1..fcbe0d2f0e 100644 --- a/web/modules/webform/css/webform.element.image_file.css +++ b/web/modules/webform/css/webform.element.image_file.css @@ -9,3 +9,7 @@ img.webform-image-file { max-width: 100%; height: auto; } + +.webform-image-file-modal img { + display: block; +} diff --git a/web/modules/webform/css/webform.element.likert.css b/web/modules/webform/css/webform.element.likert.css index 4043e8047f..b324af7ff1 100644 --- a/web/modules/webform/css/webform.element.likert.css +++ b/web/modules/webform/css/webform.element.likert.css @@ -39,28 +39,18 @@ /** * Basic table formatting. */ -.webform-likert-table th, -.webform-likert-table td { +.webform-likert-table-wrapper th, +.webform-likert-table-wrapper td { text-align: center; } -/** - * Hide radio title and description when Likert is displayed within a - * grid on desktop. - * @see \Drupal\webform\Element\WebformLikert::processWebformLikert - */ -.webform-likert-label, -.webform-likert-description { - display: none; -} - -.webform-likert-table th:first-child, -.webform-likert-table td:first-child { +.webform-likert-table-wrapper th:first-child, +.webform-likert-table-wrapper td:first-child { text-align: inherit; width: 40%; } -.form-type-webform-likert td:first-child label { +.webform-likert-table-wrapper td:first-child label { display: block; } @@ -69,6 +59,10 @@ * grid to inline vertical radios. */ @media (max-width: 768px) { + .form-type-webform-likert table.sticky-header { + display: none; + } + .form-type-webform-likert table { border-collapse: collapse; font-size: inherit; @@ -100,17 +94,31 @@ } /** - * Show radio title and description when Likert is displayed inline on mobile. + * Show visually hidden radio title and description + * when Likert is displayed inline on mobile. * @see \Drupal\webform\Element\WebformLikert::processWebformLikert + * @see core/modules/system/css/components/hidden.module.css */ - .webform-likert-label { + .webform-likert-table .visually-hidden { + position: inherit !important; + clip: inherit; + overflow: inherit; + height: inherit; + width: inherit; + } + + .webform-likert-label.visually-hidden { display: inline; } - .webform-likert-description { + .webform-likert-description.hidden { display: block; } + .webform-likert-help.hidden { + display: inline; + } + .form-type-webform-likert td:last-child { margin-bottom: 1em; } diff --git a/web/modules/webform/css/webform.element.location.css b/web/modules/webform/css/webform.element.location.geocomplete.css similarity index 72% rename from web/modules/webform/css/webform.element.location.css rename to web/modules/webform/css/webform.element.location.geocomplete.css index c13ce73f0f..e60b7efc65 100644 --- a/web/modules/webform/css/webform.element.location.css +++ b/web/modules/webform/css/webform.element.location.geocomplete.css @@ -1,14 +1,14 @@ /** * @file - * Location element styles. + * Location geocomplete element styles. * - * @see /webform/test_element_location + * @see /webform/test_element_loc_geocomplete */ /** * Map styles. */ -.webform-location-map { +.webform-location-geocomplete-map { margin-top: 5px; border: 1px solid #ccc; width: 100%; @@ -17,13 +17,13 @@ position: relative; } -.webform-location-map:after { +.webform-location-geocomplete-map:after { padding-top: 56.25%; /* 16:9 ratio */ display: block; content: ''; } -.webform-location-map--container { +.webform-location-geocomplete-map--container { position: absolute; top: 0; bottom: 0; diff --git a/web/modules/webform/css/webform.element.location.places.css b/web/modules/webform/css/webform.element.location.places.css new file mode 100644 index 0000000000..51510b90b0 --- /dev/null +++ b/web/modules/webform/css/webform.element.location.places.css @@ -0,0 +1,16 @@ +/** + * @file + * Location Algolia places element styles. + * + * @see https://community.algolia.com/places/documentation.html#css-classes + * @see /webform/test_element_loc_places + */ + +.ap-input { + padding-left: 4px; + padding-top: 4px; + padding-bottom: 4px; + line-height: 2em; + height: 2em; + border-radius: unset; +} diff --git a/web/modules/webform/css/webform.element.managed_file.css b/web/modules/webform/css/webform.element.managed_file.css new file mode 100644 index 0000000000..6e1e890deb --- /dev/null +++ b/web/modules/webform/css/webform.element.managed_file.css @@ -0,0 +1,30 @@ +/** + * @file + * Managed file styles. + */ + +.webform-managed-file-preview-wrapper + input[type="submit"], +.webform-managed-file-preview-wrapper + button, +.webform-managed-file-preview + input[type="submit"], +.webform-managed-file-preview + button { + margin: .5em 0; +} + +.webform-managed-file-preview-wrapper, +.webform-managed-file-preview-wrapper.form-item { + display: table; + border: 1px solid #ccc; + padding: .5em; +} + +.webform-managed-file-preview, +label.webform-managed-file-preview, +label.option.webform-managed-file-preview { + display: block; +} + +.webform-managed-file-placeholder, +.webform-managed-file-preview, +.webform-file-button { + margin: .5em 0; +} diff --git a/web/modules/webform/css/webform.element.mapping.css b/web/modules/webform/css/webform.element.mapping.css new file mode 100644 index 0000000000..71f60b6958 --- /dev/null +++ b/web/modules/webform/css/webform.element.mapping.css @@ -0,0 +1,13 @@ +/** + * @file + * Element mapping styles. + */ + +.webform-mapping-table th { + width: 50%; +} + +.webform-mapping-table .form-text, +.webform-mapping-table .form-select { + width: 100%; +} diff --git a/web/modules/webform/css/webform.element.message.css b/web/modules/webform/css/webform.element.message.css index e233c7829d..2a5deb1d48 100644 --- a/web/modules/webform/css/webform.element.message.css +++ b/web/modules/webform/css/webform.element.message.css @@ -14,7 +14,7 @@ background-image: url(../images/icons/info.svg); background-repeat: no-repeat; background-position: 10px 17px; - border-color: #0074bd #0074bd #0074bd transparent; /* LTR */ + border-color: #0074bd #0074bd #0074bd transparent; /* LTR */ box-shadow: -8px 0 0 #0074bd; /* LTR */ } [dir="rtl"] .messages.messages--info { diff --git a/web/modules/webform/css/webform.element.multiple.css b/web/modules/webform/css/webform.element.multiple.css index bb3c45f3be..a41ade1684 100644 --- a/web/modules/webform/css/webform.element.multiple.css +++ b/web/modules/webform/css/webform.element.multiple.css @@ -20,6 +20,11 @@ vertical-align: top; } +.webform-multiple-table td .description, +.webform-multiple-table td .form-item--error-message { + white-space: normal; +} + .webform-multiple-table td.webform-multiple-table--handle { padding: 0 0 0 1em; width: 26px; @@ -72,6 +77,9 @@ width: auto; } +.webform-multiple-table td .form-type-datetime .form-type-textfield input { + width: 12em; +} .webform-multiple-table .webform-multiple-sort-weight { width: 4em; } @@ -86,6 +94,20 @@ width: inherit; } +/** + * Add margin between stacked form items. + */ +.webform-multiple-table .form-item + .form-item { + margin-top: .2em; +} + +/** + * Remove margin from no messages. + */ +.webform-multiple-table--no-items-message .messages__wrapper { + padding: 0; +} + /** * Suppress table drag warnings. */ @@ -97,8 +119,16 @@ /** * Tweak tabledrag toggle weight. */ -.webform-multiple-tabledrag-toggle-weight { +.tabledrag-toggle-weight-wrapper.webform-multiple-tabledrag-toggle-weight { float: right; + display: inherit; + position: inherit; +} + +.tabledrag-toggle-weight-wrapper.webform-multiple-tabledrag-toggle-weight button { + position: inherit; + top: inherit; + right: inherit; } /** diff --git a/web/modules/webform/css/webform.element.range.css b/web/modules/webform/css/webform.element.range.css index 9b3c73511a..2062cd5294 100644 --- a/web/modules/webform/css/webform.element.range.css +++ b/web/modules/webform/css/webform.element.range.css @@ -6,7 +6,7 @@ */ /** - * Verticall center range input. + * Vertical center range input. */ .form-type-range input[type="range"] { display: table-cell; diff --git a/web/modules/webform/css/webform.element.states.css b/web/modules/webform/css/webform.element.states.css index a6c1a54bc1..ea79b60d23 100644 --- a/web/modules/webform/css/webform.element.states.css +++ b/web/modules/webform/css/webform.element.states.css @@ -65,3 +65,14 @@ tr.webform-states-table--state td select { .webform-states-table .tabledrag-changed { display: none !important; /* Must use !important because .tabledrag-changed 'display' is set via JavaScript */ } + +/** + * Limit results in jQuery UI Autocomplete + * + * https://stackoverflow.com/questions/7617373/limit-results-in-jquery-ui-autocomplete + */ +.ui-autocomplete { + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; +} diff --git a/web/modules/webform/css/webform.element.tableselect.css b/web/modules/webform/css/webform.element.tableselect.css index aeda6cc650..b93cdbd752 100644 --- a/web/modules/webform/css/webform.element.tableselect.css +++ b/web/modules/webform/css/webform.element.tableselect.css @@ -8,5 +8,5 @@ /* Make the first column containing a checkbox/radio as small as possible. */ .tableselect th:first-child, .tableselect td:first-child { - width: 1px; + width: 1%; } diff --git a/web/modules/webform/css/webform.element.video_file.css b/web/modules/webform/css/webform.element.video_file.css index 3c59d6b55b..ea34d87d2e 100644 --- a/web/modules/webform/css/webform.element.video_file.css +++ b/web/modules/webform/css/webform.element.video_file.css @@ -5,17 +5,7 @@ * @see /webform/test_element_media_file */ -.webform-video-file { - position: relative; - max-width: 640px; - height: 0; - padding-bottom: 56.25%; -} - .webform-video-file video { - position: absolute; - top: 0; - left: 0; width: 100%; - height: 100%; + height: auto; } diff --git a/web/modules/webform/css/webform.filter.css b/web/modules/webform/css/webform.filter.css new file mode 100644 index 0000000000..7453cac3e9 --- /dev/null +++ b/web/modules/webform/css/webform.filter.css @@ -0,0 +1,34 @@ +/** + * @file + * Filter styles + * @see /admin/structure/webform + * @see /admin/structure/webform/templates + */ + +.webform-filter-form .form-submit { + margin-left: 0; + margin-right: 5px; +} + +.webform-filter-form .form-item { + margin-right: 5px; +} + +.webform-filter-form .form-item input { + max-width: 300px; +} + +.webform-filter-form .form-item select { + max-width: 200px; +} + +@media screen and (max-width: 600px) { + .webform-filter-form .form-item { + display: block; + margin-right: 0; + } + .webform-filter-form .form-item input, + .webform-filter-form .form-item select { + max-width: initial; + } +} diff --git a/web/modules/webform/css/webform.progress.tracker.css b/web/modules/webform/css/webform.progress.tracker.css index b111478aa7..4de370c6b0 100644 --- a/web/modules/webform/css/webform.progress.tracker.css +++ b/web/modules/webform/css/webform.progress.tracker.css @@ -17,11 +17,15 @@ * Update progress text to show completed and active. */ .webform-progress-tracker .progress-step .progress-text { - color: #b6b6b6; + color: #656565; padding-top: 5px; padding-bottom: 0; } +.webform-progress-tracker .progress-step::after { + background-color: #656565; +} + .webform-progress-tracker .progress-step.is-active .progress-text { color: #333; } @@ -37,8 +41,9 @@ /** * Disable hover state because webform wizard progress markers are not clickable. */ +.webform-progress-tracker .progress-step:not(.is-active) .progress-marker, .webform-progress-tracker .progress-step:hover .progress-marker { - background-color: #b6b6b6; + background-color: #656565; } .webform-progress-tracker .progress-step.is-complete:hover .progress-marker { @@ -92,7 +97,7 @@ } [dir="rtl"] .progress-tracker--center .progress-step::after { - right: -50% + right: -50%; } [dir="rtl"] .webform-progress-tracker .progress-step { diff --git a/web/modules/webform/css/webform.promotions.css b/web/modules/webform/css/webform.promotions.css index e610dbd928..8b9bff3463 100644 --- a/web/modules/webform/css/webform.promotions.css +++ b/web/modules/webform/css/webform.promotions.css @@ -6,16 +6,17 @@ /** * Drupal Association. */ -.messages.messages--promotion_drupal_association { +.webform-message .messages.messages--promotion_drupal_association { color: #333; background: #e0e0d8 url(../images/promotions/drupal-association-logo.png) 10px 10px no-repeat; - border-color: #a6a6a6 #a6a6a6 #a6a6a6 transparent; /* LTR */ + background-size: auto; + border-color: #a6a6a6 #a6a6a6 #a6a6a6 transparent; /* LTR */ box-shadow: -8px 0 0 #a6a6a6; /* LTR */ padding-left: 120px; /* LTR */ min-height: 20px; } -[dir="rtl"] .messages.messages--promotion_drupal_association { +[dir="rtl"] .webform-message .messages.messages--promotion_drupal_association { border-color: #333 transparent #333 #333; box-shadow: 8px 0 0 #333; margin-left: 0; @@ -24,7 +25,7 @@ @media screen and (max-width: 600px) { - .messages.messages--promotion_drupal_association .button { + .webform-message .messages.messages--promotion_drupal_association .button { display: block; margin-left: 0; margin-right: 0; diff --git a/web/modules/webform/css/webform.theme.bartik.css b/web/modules/webform/css/webform.theme.bartik.css index 59c4bb898f..16002b6db4 100644 --- a/web/modules/webform/css/webform.theme.bartik.css +++ b/web/modules/webform/css/webform.theme.bartik.css @@ -44,6 +44,22 @@ table { margin: 0; } +/** + * Tweak details toggle state. + */ +.webform-details-toggle-state { + color: #0071b3; + text-decoration: none; + border-bottom: 1px dotted; +} + +.webform-details-toggle-state:hover, +.webform-details-toggle-statelink:focus { + color: #018fe2; + text-decoration: none; + border-bottom-style: solid; +} + /** * Tweak progress tracker. * @@ -66,3 +82,10 @@ summary .webform-element-help { border-color: #999; background: #999; } + +/** + * Tweak Algolia places. + */ +input.ap-input { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; +} diff --git a/web/modules/webform/css/webform.theme.classy.css b/web/modules/webform/css/webform.theme.classy.css new file mode 100644 index 0000000000..fabcfc830d --- /dev/null +++ b/web/modules/webform/css/webform.theme.classy.css @@ -0,0 +1,14 @@ +/** + * @file + * Classy theme styles. + */ + +/** + * Make sure the date picker is in front of the dialog. + * + * @see core/themes/classy/css/components/dialog.css + * @see core/themes/seven/css/components/dialog.css + */ +.ui-datepicker { + z-index: 1261 !important; +} diff --git a/web/modules/webform/css/webform.theme.seven.css b/web/modules/webform/css/webform.theme.seven.css index 6ace9fb5da..d499c211e5 100644 --- a/web/modules/webform/css/webform.theme.seven.css +++ b/web/modules/webform/css/webform.theme.seven.css @@ -8,34 +8,82 @@ table td { vertical-align: top; } +td > .form-item:first-of-type, +tr.odd .form-item:first-of-type, +tr.even .form-item:first-of-type { + margin-top: 0; +} + +td > .form-item:last-of-type, +tr.odd .form-item:last-of-type, +tr.even .form-item:last-of-type { + margin-bottom: 0; +} + /* Add margin around HR tags */ table hr { margin: .5em 0; } /* Add background to nested details */ -details details { +details details /* < 8.5.x */, +details.seven-details details.seven-details /* >= 8.6.x */ { background-color: #f8f8f8; } +details details details { + background-color: #fff; +} + /* Reduce the width of number inputs */ input.form-number { width: 6em; } -details details details { - background-color: #fff; +code { + font-weight: bold; + border: 1px solid #333; + background-color: #f8f8f8; + padding: 2px 4px; + color: #333; } /* Add yellow background to tooltips and tabs */ +/* @see core/assets/vendor/jquery.ui/themes/base/tabs.css */ .ui-tooltip.ui-widget { background: #fe6;; border: 1px solid #ed5; } +.webform-tabs .ui-tabs-nav .ui-tabs-anchor { + text-decoration: inherit; +} + .webform-tabs .ui-tabs-active.ui-state-active { background: #fe6; border: 1px solid #ed5; + text-decoration: none; +} + +.webform-tabs .ui-state-hover { + text-decoration: underline !important; +} + +.webform-tabs .ui-state-active.ui-state-hover { + text-decoration: none !important; +} + +/** Compress tabs for mobile */ +@media screen and (max-width: 600px) { + .webform-tabs.ui-tabs .ui-tabs-nav { + padding: 5px 0 4px 0; + } + .webform-tabs.ui-tabs .ui-tabs-nav li { + padding: 0; + } + .webform-tabs.ui-tabs .ui-tabs-nav .ui-tabs-anchor { + padding: .5em .7em; + } } /* Accordion icon */ @@ -67,6 +115,17 @@ pre.webform-codemirror-runmode { } } +/** + * Form action are moved to a dialogs footer and don't need to be visually + * hidden. + + * @see Drupal.behaviors.dialog.prepareDialogButtons + */ +.webform-ui-dialog .form-actions input[type=submit], +.webform-ui-dialog .form-actions a.button { + display: none !important; +} + /* System tray divider */ .ui-dialog.ui-dialog-off-canvas .ui-resizable-w { background-color: #bfbfba; @@ -75,6 +134,10 @@ pre.webform-codemirror-runmode { } /* System tray title bar */ +.ui-dialog.ui-dialog-off-canvas { + background: #fff; +} + .ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar { border-radius: 0; } @@ -84,6 +147,27 @@ pre.webform-codemirror-runmode { margin: 1em 0; } +/* jQuery UI autocomplete states */ +.ui-autocomplete a /* For filter autocomplete. */, +.ui-autocomplete .ui-menu-item-wrapper /* For #states value autocomplete. */ { + display: block; + border: 1px solid transparent; + color: #0074bd; +} + +.ui-autocomplete a.ui-state-hover, +.ui-autocomplete a.ui-state-active, +.ui-autocomplete a.ui-state-focus, +.ui-autocomplete .ui-menu-item-wrapper.ui-state-hover, +.ui-autocomplete .ui-menu-item-wrapper.ui-state-active, +.ui-autocomplete .ui-menu-item-wrapper.ui-state-focus { + text-decoration: underline; + color: #0074bd; + background: #fe6; + border: solid 1px #ed5; + margin: 0; +} + /* jQuery UI button states */ .webform-buttons .ui-button.ui-state-default { background: #f5f5f2; @@ -96,7 +180,37 @@ pre.webform-codemirror-runmode { border: solid 1px #ed5; } +/* jQuery UI tooltip */ +.ui-tooltip hr { + background: #666; + margin: .3em 0; + height: 1px; +} + +.ui-tooltip code { + font-weight: bold; + white-space: nowrap; + background-color: #f5f5f2; + font-size: .9em; + padding: .2em; +} + /* jQuery UI tabs */ .ui-tabs .ui-tabs-panel { padding: 0; } + +/* Tweak details toggle state. */ +.webform-details-toggle-state { + color: #0074bd; +} + +.webform-details-toggle-state:hover, +.webform-details-toggle-statelink:focus { + text-decoration: underline; +} + +/* Token tree */ +.token-tree ul { + margin: 0; +} diff --git a/web/modules/webform/css/webform.token.css b/web/modules/webform/css/webform.token.css new file mode 100644 index 0000000000..69e2f674cc --- /dev/null +++ b/web/modules/webform/css/webform.token.css @@ -0,0 +1,20 @@ +/** + * @file + * Token (admin) styles + */ + +tr[data-tt-id^="token-webform"] td { + vertical-align: top; +} + +.token-tree .webform-element-more ul { + margin: 0; +} + +tr.token-group .webform-element-more { + display: none; +} + +tr.token-group.expanded .webform-element-more { + display: block; +} diff --git a/web/modules/webform/css/webform.wizard.pages.css b/web/modules/webform/css/webform.wizard.pages.css new file mode 100644 index 0000000000..f36409e076 --- /dev/null +++ b/web/modules/webform/css/webform.wizard.pages.css @@ -0,0 +1,36 @@ +/** + * @file + * Wizard styles. + */ + +.webform-wizard-pages-links { + display: none; +} + +/** + * Progress link styles. + */ +.webform-progress [role="link"] { + cursor: pointer; +} + +.webform-progress .progress-title[role="link"] { + color: #1976d2; +} + +.webform-progress .progress-title[role="link"]:hover, +.webform-progress .progress-title[role="link"]:focus { + text-decoration: underline; + color: #2196f3; +} + +/** + * Preview link styles. + */ +.webform-wizard-page-edit { + display: none; +} + +.webform-wizard-page-edit input { + margin: 0; +} diff --git a/web/modules/webform/docs/DEVELOPMENT-CHEATSHEET.md b/web/modules/webform/docs/DEVELOPMENT-CHEATSHEET.md index 66040d5e84..1cfecd2d81 100644 --- a/web/modules/webform/docs/DEVELOPMENT-CHEATSHEET.md +++ b/web/modules/webform/docs/DEVELOPMENT-CHEATSHEET.md @@ -16,13 +16,17 @@ git diff 8.x-5.x > [project_name]-[issue-description]-[issue-number]-00.patch curl https://www.drupal.org/files/issues/[project_name]-[issue-description]-[issue-number]-00.patch | git apply - # Force apply patch -patch -p1 < [project_name]-[issue-description]-[issue-number]-00.patch +patch -p1 < 3037968-2.patch + +# Remove patch and untracked files +git reset --hard; git clean -f -d # Create interdiff interdiff \ [issue-number]-[old-comment-number].patch \ [issue-number]-[new-comment-number].patch \ > interdiff-[issue-number]-[old-comment-number]-[new-comment-number].txt +cat interdiff-[issue-number]-[old-comment-number]-[new-comment-number].txt # Merge branch with all commits git checkout 8.x-5.x @@ -35,9 +39,12 @@ git merge --squash [issue-number]-[issue-description] git commit -m 'Issue #[issue-number]: [issue-description]' git push -# Delete branch +# Delete local and remote branch git branch -D [issue-number]-[issue-description] git push origin :[issue-number]-[issue-description] + +# Delete remote branch +git push origin --delete [issue-number]-[issue-description] ``` **Generate Drush Make and Composer Files** @@ -50,7 +57,7 @@ drush webform-libraries-composer > composer.json **Manually Execute an Update Hook** ```bash -drush php-eval 'module_load_include('install', 'webform'); webform_update_8032()'; +drush php-eval 'module_load_include('install', 'webform'); webform_update_8144()'; ``` **Import and Export Configuration** @@ -60,24 +67,33 @@ drush php-eval 'module_load_include('install', 'webform'); webform_update_8032() # These files will be ignored. @see .gitignore. echo 'true' > webform.features.yml +echo 'true' > modules/webform_attachment/webform_attachment.features.yml +echo 'true' > modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.features.yml + echo 'true' > modules/webform_examples/webform_examples.features.yml +echo 'true' > modules/webform_examples_accessibility/webform_examples_accessibility.features.yml echo 'true' > modules/webform_example_element/webform_example_element.features.yml echo 'true' > modules/webform_example_composite/webform_example_composite.features.yml +echo 'true' > modules/webform_example_handler/webform_example_handler.features.yml echo 'true' > modules/webform_example_element/webform_example_remote_post.features.yml echo 'true' > modules/webform_templates/webform_templates.features.yml echo 'true' > modules/webform_templates/webform_templates.features.yml echo 'true' > modules/webform_image_select/webform_image_select.features.yml -echo 'true' > modules/webform_image_select/tests/modules/webform_image_select_test.features.yml +echo 'true' > modules/webform_image_select/tests/modules/webform_image_select_test/webform_image_select_test.features.yml echo 'true' > modules/webform_node/webform_node.features.yml echo 'true' > modules/webform_node/tests/modules/webform_node_test_multiple/webform_node_test_multiple.features.yml +echo 'true' > modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.features.yml echo 'true' > modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/webform_scheduled_email_test.features.yml +echo 'true' > modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.features.yml + echo 'true' > modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.features.yml echo 'true' > modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.features.yml +echo 'true' > modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.features.yml echo 'true' > tests/modules/webform_test/webform_test.features.yml echo 'true' > tests/modules/webform_test_ajax/webform_test_ajax.features.yml @@ -88,10 +104,12 @@ echo 'true' > tests/modules/webform_test_block_submission_limit/webform_test_blo echo 'true' > tests/modules/webform_test_config_performance/webform_test_config_performance.features.yml echo 'true' > tests/modules/webform_test_custom_properties/webform_test_custom_properties.features.yml echo 'true' > tests/modules/webform_test_element/webform_test_element.features.yml +echo 'true' > tests/modules/webform_test_entity_reference_views/webform_test_entity_reference_views.features.yml echo 'true' > tests/modules/webform_test_handler/webform_test_handler.features.yml echo 'true' > tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.features.yml echo 'true' > tests/modules/webform_test_options/webform_test_options.features.yml echo 'true' > tests/modules/webform_test_paragraphs/webform_test_paragraphs.features.yml +echo 'true' > tests/modules/webform_test_rest/webform_test_rest.features.yml echo 'true' > tests/modules/webform_test_submissions/webform_test_submissions.features.yml echo 'true' > tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.features.yml echo 'true' > tests/modules/webform_test_translation/webform_test_translation.features.yml @@ -102,106 +120,147 @@ echo 'true' > tests/modules/webform_test_wizard_custom/webform_test_wizard_custo # Make sure all modules that are going to be exported are enabled drush en -y webform\ + webform_attachment\ webform_demo_application_evaluation\ webform_demo_event_registration\ + webform_demo_region_contact\ webform_examples\ - webform_examples\ + webform_examples_accessibility\ webform_example_element\ + webform_example_handler\ webform_example_remote_post\ webform_image_select\ webform_node\ + webform_submission_export_import\ webform_templates\ webform_test\ webform_test_element\ + webform_test_entity_reference_views\ webform_test_handler\ webform_test_handler_remote_post\ webform_test_options\ - webform_test_views\ + webform_test_paragraphs\ + webform_test_rest\ webform_test_submissions\ webform_test_translation\ + webform_test_views\ + webform_attachment_test\ webform_image_select_test\ webform_node_test_multiple\ - webform_scheduled_email_test; + webform_node_test_translation\ + webform_scheduled_email_test\ + webform_submission_export_import_test; # Show the difference between the active config and the default config. drush features-diff webform drush features-diff webform_test -# Export webform configuration from your site. +# Export webform configuration from your site. drush features-export -y webform +drush features-export -y webform_attachment drush features-export -y webform_demo_application_evaluation drush features-export -y webform_demo_event_registration +drush features-export -y webform_demo_region_contact drush features-export -y webform_examples +drush features-export -y webform_examples_accessibility drush features-export -y webform_example_element drush features-export -y webform_example_composite +drush features-export -y webform_example_handler drush features-export -y webform_example_remote_post drush features-export -y webform_node drush features-export -y webform_image_select +drush features-export -y webform_submission_export_import drush features-export -y webform_templates drush features-export -y webform_test drush features-export -y webform_test_block_submission_limit drush features-export -y webform_test_element +drush features-export -y webform_test_entity_reference_views drush features-export -y webform_test_handler drush features-export -y webform_test_handler_remote_post drush features-export -y webform_test_options -drush features-export -y webform_test_views +drush features-export -y webform_test_rest drush features-export -y webform_test_submissions drush features-export -y webform_test_translation +drush features-export -y webform_test_views drush features-export -y webform_test_paragraphs +drush features-export -y webform_attachment_test drush features-export -y webform_image_select_test drush features-export -y webform_node_test_multiple +drush features-export -y webform_node_test_translation drush features-export -y webform_scheduled_email_test +drush features-export -y webform_submission_export_import_test # Revert all feature update to *.info.yml files. git checkout -- *.info.yml -# Tidy webform configuration from your site. +# Tidy webform configuration from your site. drush webform:tidy -y --dependencies webform +drush webform:tidy -y --dependencies webform_attachment drush webform:tidy -y --dependencies webform_demo_application_evaluation drush webform:tidy -y --dependencies webform_demo_event_registration +drush webform:tidy -y --dependencies webform_demo_region_contact drush webform:tidy -y --dependencies webform_examples +drush webform:tidy -y --dependencies webform_examples_accessibility drush webform:tidy -y --dependencies webform_example_element drush webform:tidy -y --dependencies webform_example_composite +drush webform:tidy -y --dependencies webform_example_handler drush webform:tidy -y --dependencies webform_example_remote_post drush webform:tidy -y --dependencies webform_image_select drush webform:tidy -y --dependencies webform_node +drush webform:tidy -y --dependencies webform_submission_export_import drush webform:tidy -y --dependencies webform_templates drush webform:tidy -y --dependencies webform_test drush webform:tidy -y --dependencies webform_test_block_submission_limit drush webform:tidy -y --dependencies webform_test_element +drush webform:tidy -y --dependencies webform_test_entity_reference_views drush webform:tidy -y --dependencies webform_test_handler drush webform:tidy -y --dependencies webform_test_handler_remote_post drush webform:tidy -y --dependencies webform_test_options -drush webform:tidy -y --dependencies webform_test_views +drush webform:tidy -y --dependencies webform_test_paragraphs +drush webform:tidy -y --dependencies webform_test_rest drush webform:tidy -y --dependencies webform_test_submissions drush webform:tidy -y --dependencies webform_test_translation -drush webform:tidy -y --dependencies webform_test_paragraphs +drush webform:tidy -y --dependencies webform_test_views +drush webform:tidy -y --dependencies webform_attachment_test drush webform:tidy -y --dependencies webform_image_select_test drush webform:tidy -y --dependencies webform_node_test_multiple +drush webform:tidy -y --dependencies webform_node_test_translation drush webform:tidy -y --dependencies webform_scheduled_email_test +drush webform:tidy -y --dependencies webform_submission_export_import_test -# Re-import all webform configuration into your site. +# Re-import all webform configuration into your site. drush features-import -y webform +drush features-import -y webform_attachment drush features-import -y webform_demo_application_evaluation drush features-import -y webform_demo_event_registration +drush features-import -y webform_demo_region_contact drush features-import -y webform_examples +drush features-import -y webform_examples_accessibility drush features-import -y webform_example_element drush features-import -y webform_example_composite +drush features-import -y webform_example_handler drush features-import -y webform_example_remote_post drush features-import -y webform_node drush features-import -y webform_image_select +drush features-import -y webform_submission_export_import drush features-import -y webform_templates drush features-import -y webform_test drush features-import -y webform_test_element +drush features-import -y webform_test_entity_reference_views drush features-import -y webform_test_block_submission_limit drush features-import -y webform_test_handler drush features-import -y webform_test_handler_remote_post drush features-import -y webform_test_options -drush features-import -y webform_test_views +drush features-import -y webform_test_paragraphs +drush features-import -y webform_test_rest drush features-import -y webform_test_submissions drush features-import -y webform_test_translation -drush features-import -y webform_test_paragraphs +drush features-import -y webform_test_views +drush features-import -y webform_attachment_test drush features-import -y webform_image_select_test drush features-import -y webform_node_test_multiple +drush features-import -y webform_node_test_translation drush features-import -y webform_scheduled_email_test +drush features-import -y webform_submission_export_import_test + ``` diff --git a/web/modules/webform/docs/DEVELOPMENT-NOTES.md b/web/modules/webform/docs/DEVELOPMENT-NOTES.md index 91a0b43cd3..8c2bdeae2a 100644 --- a/web/modules/webform/docs/DEVELOPMENT-NOTES.md +++ b/web/modules/webform/docs/DEVELOPMENT-NOTES.md @@ -73,7 +73,7 @@ git reset --hard git commit --amendd ../ # Unstage a file about to be committed -git reset HEAD <file>... +git reset HEAD <file>… # Revert (in SVN terms) an uncommitted file to the copy in your latest commit git checkout -- filename diff --git a/web/modules/webform/docs/FEATURES.md b/web/modules/webform/docs/FEATURES.md index ab2bc32d8f..9a4aec0bd9 100644 --- a/web/modules/webform/docs/FEATURES.md +++ b/web/modules/webform/docs/FEATURES.md @@ -1,219 +1,460 @@ Features -------- -## Form Builder -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-builder.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-builder.png" alt="Form Builder" /> -</a> +<blockquote> +The Webform module provides all the features expected from an enterprise +proprietary form builder combined with the flexibility and openness of Drupal +</blockquote> + +The Webform module allows you to build any type of form that can collect any +type of data, which can be submitted to any application or system. +Every single behavior and aspect of your forms and its inputs are customizable. +Whether you need a multi-page form containing a multi-column input layout with +conditional logic or a simple contact form that pushes data to a SalesForce/CRM, + it is all possible using the Webform module for Drupal 8. + +Drupal and the Webform module strives to be fully accessible to all users and +site builders. Assistive technologies, including screen readers and +keyboard access, are fully supported. + +Besides being a feature rich form builder, the Webform module is part of the +Drupal project's ecosystem and community. + +<blockquote> +The <a href="https://www.drupal.org/about">Drupal project</a> is open source software. +Anyone can download, use, work on, and share it with others. +It's built on principles like collaboration, globalism, and innovation. +It's distributed under the terms of the <a href="https://www.gnu.org/copyleft/gpl.html">GNU General Public License</a> (GPL). +There are <a href="https://www.drupal.org/about/licensing">no licensing fees</a>, ever. Drupal (and Webform) will always be free. +</blockquote> + +<div align="center"> +<table class="views-view-grid"> + <tr> +<td><a class="action-button" href="https://youtu.be/VncMRSwjVto">▶ Watch video</a></td> +<td><a class="action-button" href="https://simplytest.me/project/webform/8.x-5.x">Try Webform</a></td> + </tr> +</table> </div> -The Webform module provides an intuitive webform builder based upon Drupal 8's -best practices for user interface and user experience. The webform builder allows non-technical users to easily build and maintain webforms. +<hr/> + +## Form manager + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--form-manager.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--form-manager.png" alt="Form manager" /><br/> + <strong>Form manager</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--form-manager-add.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--form-manager-add.png" alt="Form manager: Add webform" /><br/> + <strong>Add webform</strong> + </a> + </div></td> + </tr> +</table> + +The form manager provides a list of all available webforms. + +Form manager features include: + +- Filtering by keyword, category, and status +- Sorting by total number of submissions +- Archiving of old forms + +## Form builder + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--form-builder.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--form-builder.png" alt="Form builder" /><br/> + <strong>Form builder</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--form-builder.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--form-builder.png" alt="Edit element" /><br/> + <strong>Edit element</strong> + </a> + </div></td> + </tr> +</table> + +The Webform module provides an intuitive form builder based upon Drupal 8's +best practices for user interface design and user experience. +The form builder allows non-technical users to easily build +and maintain webforms. Form builder features include: -- Drag-n-drop webform element management -- Generation of test submissions -- Duplication of existing webforms, templates, and elements +- Drag-n-drop form element management +- Multi-column layout management +- Conditional logic overview +- Element duplication + +## Configuration settings + +Form behaviors, features, submission handling, messaging, and confirmations are completely customizable using global settings and/or form-specific settings. + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-general.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-general.png" alt="Configuration settings: General" /><br/> + <strong>General</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-form.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-form.png" alt="Configuration settings: Form" /><br/> + <strong>Form</strong> + </a> + </div></td> + </tr> +</table> + +### General settings + +Allow a webform's administrative information, paths, behaviors, and third-party settings to be customized. + +General settings include: + +- Categorization +- Customizable paths +- Disable saving of results +- Ajax support + +### Form settings + +Allow a form's status, attributes, behaviors, labels, messages, wizard settings, +and preview to be customized. + +Form settings include: + +- Open and close date/time scheduling +- Login redirection with custom messaging. +- Multiple step wizard forms +- Submission preview +- Input prepopulation using query string parameters. + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-submissions.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-submissions.png" alt="Configuration settings: Submisssions" /><br/> + <strong>Submisssions</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-confirmation.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-confirmation.png" alt="Configuration settings: Confirmation" /><br/> + <strong>Confirmation</strong> + </a> + </div></td> + </tr> +</table> + +### Submissions settings + +Allows a submission's labels, behaviors, limits, and draft settings to be +customized. + +Submission settings include: + +- Saving of drafts +- Automatic purging of submissions +- Submission limits per user and/or per form +- Autofilling form using previously submitted values + +### Confirmation settings + +Allows the form's confirmation type, message, and URL to be customized. + +Confirmation types include: + +- Dedicated page +- Redirect to internal or external URL +- Displaying of a custom status message +- Opening a modal dialog + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-handlers.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-handlers.png" alt="Configuration settings: Handlers" /><br/> + <strong>Handlers</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-handlers-email.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-handlers-email.png" alt="Configuration settings: Email handler" /><br/> + <strong>Email handler</strong> + </a> + </div></td> + </tr> +</table> + + +### Emails / Handlers + +Allows additional actions and behaviors to be processed when a webform or +submission is created, updated, or deleted. Handlers are used to route +submitted data to external applications and send notifications & confirmations. +Email support features include: -## Form Settings - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-settings.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-settings-thumbnail.png" alt="Form Settings" /> -</a> -</div> +- Previewing and resending emails +- Sending HTML emails +- File attachments (requires the Mail System and Swift Mailer module.) +- HTML and plain-text email-friendly Twig templates +- Customizable display formats for individual form elements -Form submission handling, messaging, and confirmations are completely -customizable using global settings and/or form-specific settings. - -Form settings that can be customized include: - -- Messages and button labels -- Confirmation page, messages, and redirects -- Saving drafts -- Previewing submissions -- Confidential submissions -- Prepopulating a webform's elements using query string parameters -- Preventing duplicate submissions -- Disabling back button -- Warning users about unsaved changes -- Disabling client-side validation -- Limiting number of submission per user, per webform, and/or per node -- Look-n-feel of webform, confirmation page, and buttons -- Injection webform specific CSS and JavaScript +Remote post features include: +- Posting selected elements to a remote server +- Adding custom parameters to remote post requests +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-assets.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-assets.png" alt="Configuration settings: CSS/JS assets" /><br/> + <strong>CSS/JS assets</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--settings-access.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--settings-access.png" alt="Configuration settings: Access" /><br/> + <strong>Access</strong> + </a> + </div></td> + </tr> +</table> + +### CSS/JS assets + +The CSS/JS assets page allows site builders to attach custom CSS and JavaScript +to a webform. Custom CSS can be used to make simple layout or design tweaks +to a form. Custom JavaScript allows additional conditional logic and +behaviors to be added to a form. + +### Access settings + +Allows an administrator to determine who can administer a webform and/or create, +update, delete, and purge webform submissions. ## Elements -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-elements.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-elements-thumbnail.png" alt="Elements" /> -</a> -</div> - -The Webform module is built directly on top of Drupal 8's Form API. Every -[form element](https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/8) -available in Drupal 8 is supported by the Webform module. +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--elements.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--elements.png" alt="Elements" /><br/> + <strong>Elements</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--elements-settings.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--elements-settings.png" alt="Element settings" /><br/> + <strong>Element settings</strong> + </a> + </div></td> + </tr> +</table> + +The Webform module is built directly on top of Drupal 8's Form API. +Every form element available in Drupal 8 is supported by the Webform module. Form elements include: -- **HTML:** Textfield, Textareas, Checkboxes, Radios, Select menu, - Password, and more... -- **HTML5:** Email, Url, Number, Telephone, Date, Number, Range, - and more... -- **Drupal specific** File uploads, Entity References, Table select, Date list, - and more... -- **Custom:** [Likert scale](https://en.wikipedia.org/wiki/Likert_scale), - Star rating, Toggle, Buttons, Geolocation, - Select/Checkboxes/Radios with other, and more... -- **Markups** Inline dismissable messages, HTML Markup, Details, and Fieldsets. -- **Composite elements:** Name, Address, and Contact - - -## Element Settings - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-element-settings.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-element-settings-thumbnail.png" alt="Element Settings" /> -</a> -</div> - -All of Drupal 8's default webform element properties and behaviors are supported. -There are also several custom webform element properties and settings -available to enhance a webform element's behavior. - -Standard and custom properties allow for: - -- **Customizable required error messages** -- **Conditional logic** using [FAPI States API](https://api.drupal.org/api/examples/form_example%21form_example_states.inc/function/form_example_states_form/7) -- **Input masks** (using [jquery.inputmask](https://github.com/RobinHerbots/jquery.inputmask)) -- **[Select2](https://select2.github.io/)** replacement of select boxes -- **Word and character counting** for text elements -- **Help popup** (using [jQuery UI Tooltip](https://jqueryui.com/tooltip/)) -- **Regular expression pattern validation** -- **Private** elements, visible only to administrators -- **Unique** values per element +- **Basic HTML**: Textfield, Textareas, Checkboxes, Radios, Select menu, Password, and more... +- **Advanced HTML5**: Email, Url, Number, Telephone, Date, Number, Range, and more... +- **Advanced Drupal**: File uploads, Entity References, Table select, Date list, and more... +- **Widgets**: Likert scale, Star rating, Buttons, Geolocation, Terms of service, Select/Checkboxes/Radios with other, and more... +- **Markup**: Dismissible messages, Basic HTML, Advanced HTML, Details, and Fieldsets. +- **Composites**: Name, Address, Contact, Credit Card, and event custom composites +- **Computed**: Calculated values using Tokens and Twig with Ajax support. +### Element settings -## Viewing Source - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-source.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-source-thumbnail.png" alt="Viewing Source" /> -</a> -</div> - -At the heart of a Webform module's webform elements is a Drupal render array, -which can be edited and managed by developers. The Drupal render array gives developers -complete control over a webform's elements, layout, and look-and-feel by -allowing developers to make bulk updates to a webform's label, descriptions, and -behaviors. +All of Drupal 8's default form element properties and behaviors are supported. +There are also several custom webform element properties and settings available +to enhance a form element's behavior. +Standard and custom properties allow for: -## States/Conditional Logic - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-states.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-states.png" alt="States/Conditional Logic" /> -</a> -</div> - -Drupal's State API can be used by developers to provide conditional logic to -hide and show webform elements. +- Customizable error validation messages +- Conditional logic using [FAPI States API](https://api.drupal.org/api/examples/form_example%21form_example_states.inc/function/form_example_states_form/7) +- Input masks (using [jquery.inputmask](https://github.com/RobinHerbots/jquery.inputmask)) +- [Select2](https://select2.github.io/) or [Chosen](https://harvesthq.github.io/chosen/) replacement of select boxes +- Word and character counting for text elements +- Help tooltips (using [jQuery UI Tooltip](https://jqueryui.com/tooltip/)) +- More information slideouts +- Regular expression pattern validation +- Private elements, visible only to administrators +- Unique values per element + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--elements-conditional.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--elements-conditional.png" alt="Conditional logic" /><br/> + <strong>Conditional logic</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--elements-source.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--elements-source.png" alt="View source" /><br/> + <strong>View source</strong> + </a> + </div></td> + </tr> +</table> + +### States/Conditional logic + +Drupal's State API can be used by developers to provide conditional logic +to hide and show form elements. Drupal's State API supports: - Show/Hide +- Required/Optional - Open/Close - Enable/Disable - -## Multistep Forms - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-wizard.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-wizard.png" alt="Multistep Forms" /> -</a> -</div> - -Forms can be broken up into multiple pages using a progress bar. Authenticated -users can save drafts and/or have their changes automatically saved as they -progress through a long webform. - -Multistep webform features include: +### Viewing source + +At the heart of a Webform module's form elements is a Drupal +[render array](https://www.drupal.org/docs/8/api/render-api/render-arrays), +which can be edited and managed by developers. The Drupal render array +gives developers complete control over a webform's elements, layout, +and look-and-feel by allowing developers to make bulk updates +to a webform's label, descriptions, and behaviors. + +## Forms + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--forms-accessiblity.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--forms-accessiblity.png" alt="Accessibility" /><br/> + <strong>Accessibility</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--forms-wizard.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--forms-wizard.png" alt="Multistep form" /><br/> + <strong>Multistep form</strong> + </a> + </div></td> + </tr> +</table> + +### Accessibility + +The outputted forms and even the Webform module's administrative interface +(i.e. form builder) are accessible using keyboard navigation and screen readers. +The Webform module complies with [WCAG 2.0](http://www.w3.org/TR/WCAG20/#contents) +and [ATAG 2.0](http://www.w3.org/TR/ATAG20/#contents) guidelines. + +### Multistep form + +Forms can be broken up into multiple pages using a progress bar. +Authenticated users can save drafts and/or have their changes automatically +saved as they progress through a long form. + +Multistep form features include: - Customizable progress bar - Customizable previous and next button labels and styles - Saving drafts between steps - -## Email & Remote Post Handlers - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-handlers.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-handlers-thumbnail.png" alt="Email/Handlers" /> -</a> -</div> - -Upon webform submission, customizable email notifications and confirmations can -be sent to users and administrators. - -An extendable plugin that allows developers to push submitted data -to external or internal systems and/or applications is provided. - -Email support features include: - -- Previewing and resending emails -- Sending HTML emails -- File attachments (requires the [Mail System](https://www.drupal.org/project/mailsystem) and [Swift Mailer](https://www.drupal.org/project/swiftmailer) module.) -- HTML and plain-text email-friendly Twig templates -- Customizable display formats for individual webform elements - -Remote post features include: - -- Posting selected elements to remote server -- Adding custom parameters to remote post requests - - -## Results Management - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-results.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-results-thumbnail.png" alt="Results Management" /> -</a> -</div> - -Form submissions can optionally be stored in the database, reviewed, and -downloaded. - -Submissions can also be flagged with administrative notes. +### Drupal integration + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--forms-block.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--forms-block.png" alt="Block" /><br/> + <strong>Block</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--forms-node.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--forms-node.png" alt="Node" /><br/> + <strong>Node</strong> + </a> + </div></td> + </tr> +</table> + +Webforms can be attached to nodes or displayed as blocks. +Webforms can also have dedicated SEO-friendly URLs. +Form elements are render arrays that can easily be altered using +custom hooks and/or plugins. + +## Results management + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--results.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--results.png" alt="Results" /><br/> + <strong>Results</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--results-customize.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--results-customize.png" alt="Customize results" /><br/> + <strong>Customize results </strong> + </a> + </div></td> + </tr> +</table> + +Form submissions can optionally be stored in the database, reviewed, +and downloaded. Submissions can also be flagged with administrative notes. Results management features include: -- Flagging -- Administrative notes +- Submission flagging, locking, and notes - Viewing submissions as HTML, plain text, and YAML - Customizable reports - Downloading results as a CSV to Google Sheets or MS Excel - Saving of download preferences per form -- Automatically purging old submissions based on certain criteria - - -## Access Controls - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-access.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-access-thumbnail.png" alt="Access Controls" /> -</a> -</div> - -The Webform module provides full access controls for managing who can create -forms, post submissions, and access a webform's results. -Access controls can be applied to roles and/or specific users. +- [Drupal Views](https://www.drupal.org/docs/8/core/modules/views) integration +for advanced reporting. + +## Access controls + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--access-permissions.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--access-permissions.png" alt="Permissions" /><br/> + <strong>Permissions</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--access-element.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--access-element.png" alt="Element access" /><br/> + <strong>Element access</strong> + </a> + </div></td> + </tr> +</table> + +The Webform module provides full access controls and permissions for managing +who can create forms, post submissions, and access a webform's results. +Access controls can be applied to roles and/or specific users. +The Webform access submodule allows you to even setup reusable permission +groups which can be applied to multiple instances of the same webform. Access controls allow users to: @@ -226,162 +467,168 @@ Access controls allow users to: - View selected elements - Update selected elements - -## Reusable Templates - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-templates.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-templates-thumbnail.png" alt="Reusable Templates" /> -</a> -</div> - -The Webform module provides a few starter templates and multiple example forms -which webform administrators can update or use to create new reusable templates -for their organization. +## Reusable templates + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--templates.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--templates.png" alt="Templates" /><br/> + <strong>Templates</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--templates-preview.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--templates-preview.png" alt="Templates preview" /><br/> + <strong>Templates preview</strong> + </a> + </div></td> + </tr> +</table> + +The Webform module provides a few starter templates and multiple example +forms which webform administrators can update or use to create new +reusable templates for their organization. Starter templates include: -- Contact Us +- Contact Us - Donation - Employee Evaluation - Issue -- Job Application +- Job Application - Job Seeker Profile - Registration - Session Evaluation - Subscribe - User Profile -Example webforms include: - -- Elements -- Basic layout -- Flexbox layout -- Input masks -- Options -- Wizard - - -## Reusable Options - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-options.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-options-thumbnail.png" alt="Reusable Options" /> -</a> -</div> - -Administrators can define reusable global options for select menus, checkboxes, -and radio buttons. The Webform module includes default options for states, -countries, [likert](https://en.wikipedia.org/wiki/Likert_scale) answers, -and more. +## Reusable options + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--options.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--options.png" alt="Options" /><br/> + <strong>Options</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--options-likert.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--options-likert.png" alt="Likert" /><br/> + <strong>Likert</strong> + </a> + </div></td> + </tr> +</table> + + +Administrators can define reusable global options for select menus, checkboxes, +and radio buttons. The Webform module includes default options for states, +countries, demographics, likert answers, and more. Reusable options include: -- Country codes & names -- Days, Months, Time zones -- Education, Employment status, Ethnicity, Industry, Languages, Marital status, Relationship, Size, and Titles -- Likert agreement, comparison, importance, quality, satisfaction, ten scale, and - would you -- State/province codes & names -- State codes & names +- **Geographic**: Languages, country, and states +- **Date and time**: Days, months, and time zones +- **Demographic**: Education, employment status, ethnicity, Industry, +languages, marital status, relationship, size, and job titles +- **Likert**: Agreement, comparison, importance, quality, satisfaction, +ten scale, and would you ## Internationalization - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-internalization.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-internalization-thumbnail.png" alt="Internationalization" /> -</a> -</div> - -Forms and configuration can be translated into multiple languages using Drupal's -configuration translation system. - - -## Drupal Integration -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-integration.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-integration-thumbnail.png" alt="Drupal Integration" /> -</a> -</div> - -Forms can be attached to nodes or displayed as blocks. Webforms can also have -dedicated SEO-friendly URLs. Webform elements are simply render arrays that can -easily be altered using custom hooks and/or plugins. - - -## Add-ons & Third Party Settings - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-add-ons.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-add-ons-thumbnail.png" alt="Add-ons & Third Party Settings" /> -</a> -</div> - -Includes a list of modules and projects that extend and/or provide additional -functionality to the Webform module and Drupal's Form API. - - -## Extendable Plugins - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-plugin.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-plugin.png" alt="Extendable Plugins" /> -</a> -</div> - -The Webform module provides [plugins](https://www.drupal.org/developing/api/8/plugins) -and hooks that allow contrib and custom modules to extend and enhance webform -elements and submission handling. - -**WebformElement plugin** is used to integrate and enhance webform elements so -that they can be properly integrated into the Webform module. - -**WebformHandler plugin** allows developers to extend a webform's submission -handling. - -**WebformExporter plugin** allows developers to export results using custom -formats and file types. - - -## Help & Video Tutorials - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-help.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-help-thumbnail.png" alt="Help & Video Tutorials" /> -</a> -</div> - -The Webform module provides examples, inline help, and screencast walk throughs. - -Screencasts include: - -- [Welcome to the Webform module](https://youtu.be/sQGsfQ_LZJ4) -- [Installing the Webform module and third party libraries](https://youtu.be/IMfFTrsjg5k) -- [Managing Webforms, Templates, and Examples](https://youtu.be/T5MVGa_3jOQ) -- [Adding Elements, Composites, and Containers](https://youtu.be/LspF9mAvRcY) -- [Configuring Webform Settings and Behaviors](https://youtu.be/UJ0y09ZS9Uc) -- [Controlling Access to Webforms and Elements](https://youtu.be/SFm76DAVjbE) -- [Collecting Submissions, Sending Emails, and Posting Results](https://youtu.be/OdfVm5LMH9A) -- [Placing Webforms in Blocks and Creating Webform Nodes](https://youtu.be/xYBW2g0osd4) -- [Administering and Extending the Webform module](https://youtu.be/bkScAX_Qbt4) -- [Using the Source](https://youtu.be/2pWkJiYeR6E) -- [Getting Help](https://youtu.be/sRXUR2c2brA) - - -## Drush Integration - -<div class="thumbnail"> -<a href="https://www.drupal.org/files/webform-8.x-5.x-drush.png"> -<img src="https://www.drupal.org/files/webform-8.x-5.x-drush.png" alt="Drush Integration" /> -</a> -</div> +Forms and configuration can be translated into multiple languages using +Drupal's [configuration translation system](https://www.drupal.org/docs/8/core/modules/config-translation. + +## Add-ons + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--addons.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--addons.png" alt="Add-ons" /><br/> + <strong>Add-ons</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--addons-webform-analysis.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--addons-webform-analysis.png" alt="Analysis" /><br/> + <strong>Analysis</strong> + </a> + </div></td> + </tr> +</table> + +There are [dozens of add-ons available](https://www.drupal.org/node/2837065) +that extend and/or provide additional functionality to the Webform module +and Drupal's Form API. + +Add-ons include: + +- Analysis for creating graphs and charts +- CRM integration including SalesForce, HubSpot, MyEmma, SugarCRM, more… +- SPAM protection +- Advanced workflows +- Data encryption +- GDPR compliance + +## Development tools + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--devel-test.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--devel-test.png" alt="Test" /><br/> + <strong>Test</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--devel-api.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--devel-api.png" alt="API" /><br/> + <strong>API</strong> + </a> + </div></td> + </tr> +</table> + +Examples and tools are provided to help developers get started customizing +existing features and adding new features to the Webform module. + +Development tools include: + +- Test forms using customizable default values +- Easy to export configuration files +- Debugging tools for all handlers +- Examples for external API integration using remotes posts or custom code +- Example modules for creating custom elements and handlers +- Demos for building event registration and application evaluation system + +## Drush & Composer integration + +<table class="views-view-grid" width="100%"> + <tr> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--drush.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--drush.png" alt="Drush" /><br/> + <strong>Drush</strong> + </a> + </div></td> + <td width="50%"><div class="note"> + <a href="https://www.drupal.org/files/webform-8.x.5.x-features--drush-composer.png"> + <img src="https://www.drupal.org/files/webform-8.x.5.x-features--drush-composer.png" alt="Composer" /><br/> + <strong>Composer</strong> + </a> + </div></td> + </tr> +</table> Drush commands are provided to: -- Generate multiple webform submissions -- Export webform submissions -- Purge webform submissions -- Download and manage third party libraries +- Generate multiple submissions +- Export submissions +- Purge submissions +- Download and manage third-party libraries +- Generate a composer.js file for third-party libraries - Tidy YAML configuration files diff --git a/web/modules/webform/docs/RELEASE-NOTES.md b/web/modules/webform/docs/RELEASE-NOTES.md index 0213d5ec92..eb81bf7d50 100644 --- a/web/modules/webform/docs/RELEASE-NOTES.md +++ b/web/modules/webform/docs/RELEASE-NOTES.md @@ -1,57 +1,131 @@ - Steps for creating a new release -------------------------------- - 1. Cleanup code - 2. Export configuration - 3. Review code + 1. Review code + 2. Deprecated code + 3. Review accessibility 4. Run tests 5. Generate release notes 6. Tag and create a new release - 7. Upload screencast to YouTube - -1. Cleanup code ---------------- -[Convert to short array syntax](https://www.drupal.org/project/short_array_syntax) - drush short-array-syntax webform +1. Review code +-------------- -Tidy YAML files + # Remove files that should never be reviewed. + cd modules/sandbox/webform + rm *.patch interdiff-* + +[PHP](https://www.drupal.org/node/1587138) - @see DEVELOPMENT-CHEATSHEET.md + # Check Drupal PHP coding standards + cd /var/www/sites/d8_webform/web + phpcs --standard=Drupal --extensions=php,module,inc,install,test,profile,theme,css,info modules/sandbox/webform > ~/webform-php-coding-standards.txt + cat ~/webform-php-coding-standards.txt + # Check Drupal PHP best practices + cd /var/www/sites/d8_webform/web + phpcs --standard=DrupalPractice --extensions=php,module,inc,install,test,profile,theme,js,css,info modules/sandbox/webform > ~/webform-php-best-practice.txt + cat ~/webform-php-best-practice.txt -2. Export configuration ------------------------ +[JavaScript](https://www.drupal.org/node/2873849) - @see DEVELOPMENT-CHEATSHEET.md + # Install Eslint. (One-time) + cd /var/www/sites/d8_webform/web/core + yarn install + + # Check Drupal JavaScript (ES5) legacy coding standards. + cd /var/www/sites/d8_webform/web + core/node_modules/.bin/eslint --no-eslintrc -c=core/.eslintrc.legacy.json --ext=.js modules/sandbox/webform > ~/webform-javascript-coding-standards.txt + cat ~/webform-javascript-coding-standards.txt + +[File Permissions](https://www.drupal.org/comment/reply/2690335#comment-form) -3. Review code --------------- + # Files should be 644 or -rw-r--r-- + find * -type d -print0 | xargs -0 chmod 0755 -[Online](http://pareview.sh) + # Directories should be 755 or drwxr-xr-x + find . -type f -print0 | xargs -0 chmod 0644 - http://git.drupal.org/project/webform.git 8.x-5.x +2. Deprecated code +------------------ + +[phpstan-drupal](https://github.com/mglaman/phpstan-drupal) +[phpstan-drupal-deprecations](https://github.com/mglaman/phpstan-drupal-deprecations) + + cd /var/www/sites/d8_webform/ + composer require mglaman/phpstan-drupal + composer require phpstan/phpstan-deprecation-rules + +Create `/var/www/sites/d8_webform/phpstan.neon` + + parameters: + customRulesetUsed: true + reportUnmatchedIgnoredErrors: false + # Ignore phpstan-drupal extension's rules. + ignoreErrors: + - '#\Drupal calls should be avoided in classes, use dependency injection instead#' + - '#Plugin definitions cannot be altered.#' + - '#Missing cache backend declaration for performance.#' + - '#Plugin manager has cache backend specified but does not declare cache tags.#' + includes: + - vendor/mglaman/phpstan-drupal/extension.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + +Run PHPStan with memory limit increased -[Commandline](https://www.drupal.org/node/1587138) + cd /var/www/sites/d8_webform/ + ./vendor/bin/phpstan --memory-limit=1024M analyse web/modules/sandbox/webform > ~/webform-deprecated.txt + cat ~/webform-deprecated.txt - # Check Drupal coding standards - phpcs --standard=Drupal --extensions=php,module,inc,install,test,profile,theme,css,info modules/sandbox/webform - # Check Drupal best practices - phpcs --standard=DrupalPractice --extensions=php,module,inc,install,test,profile,theme,js,css,info modules/sandbox/webform +3. Review accessibility +----------------------- -[File Permissions](https://www.drupal.org/comment/reply/2690335#comment-form) +[Pa11y](http://pa11y.org/) - # Files should be 644 or -rw-r--r-- - find * -type d -print0 | xargs -0 chmod 0755 +Pa11y is your automated accessibility testing pal. - # Directories should be 755 or drwxr-xr-x - find . -type f -print0 | xargs -0 chmod 0644 +Notes +- Requires node 8.x+ + # Enable accessibility examples. + drush en -y webform_examples_accessibility + + # Text. + mkdir -p /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/text + cd /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/text + pa11y http://localhost/wf/webform/example_accessibility_basic > example_accessibility_basic.txt + pa11y http://localhost/wf/webform/example_accessibility_advanced > example_accessibility_advanced.txt + pa11y http://localhost/wf/webform/example_accessibility_containers > example_accessibility_containers.txt + pa11y http://localhost/wf/webform/example_accessibility_wizard > example_accessibility_wizard.txt + pa11y http://localhost/wf/webform/example_accessibility_labels > example_accessibility_labels.txt + + # HTML. + mkdir -p /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/html + cd /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/html + pa11y --reporter html http://localhost/wf/webform/example_accessibility_basic > example_accessibility_basic.html + pa11y --reporter html http://localhost/wf/webform/example_accessibility_advanced > example_accessibility_advanced.html + pa11y --reporter html http://localhost/wf/webform/example_accessibility_containers > example_accessibility_containers.html + pa11y --reporter html http://localhost/wf/webform/example_accessibility_wizard > example_accessibility_wizard.html + pa11y --reporter html http://localhost/wf/webform/example_accessibility_labels > example_accessibility_labels.html + + # Remove localhost from reports. + cd /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity + find . -name '*.html' -exec sed -i '' -e 's|http://localhost/wf/webform/|http://localhost/webform/|g' {} \; + + # PDF. + mkdir -p /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/pdf + cd /var/www/sites/d8_webform/web/modules/sandbox/webform/reports/accessiblity/pdf + wkhtmltopdf --dpi 384 ../html/example_accessibility_basic.html example_accessibility_basic.pdf + wkhtmltopdf --dpi 384 ../html/example_accessibility_advanced.html example_accessibility_advanced.pdf + wkhtmltopdf --dpi 384 ../html/example_accessibility_containers.html example_accessibility_containers.pdf + wkhtmltopdf --dpi 384 ../html/example_accessibility_wizard.html example_accessibility_wizard.pdf + wkhtmltopdf --dpi 384 ../html/example_accessibility_labels.html example_accessibility_labels.pdf + + 4. Run tests ------------ @@ -66,42 +140,43 @@ Tidy YAML files php core/scripts/run-tests.sh --suppress-deprecations --url http://localhost/wf --verbose --class "Drupal\Tests\webform\Functional\WebformListBuilderTest" [PHPUnit](https://www.drupal.org/node/2116263) - + Notes -- Links to PHP Unit HTML responses are not being printed by PHPStrom +- Links to PHP Unit HTML responses are not being printed by PHPStorm -References -- [Issue #2870145: Set printerClass in phpunit.xml.dist](https://www.drupal.org/node/2870145) +References +- [Issue #2870145: Set printerClass in phpunit.xml.dist](https://www.drupal.org/node/2870145) - [Lesson 10.2 - Unit testing](https://docs.acquia.com/article/lesson-102-unit-testing) + # Export database and base URL. + export SIMPLETEST_DB=mysql://drupal_d8_webform:drupal.@dm1n@localhost/drupal_d8_webform; + export SIMPLETEST_BASE_URL='http://localhost/wf'; # Execute all Webform PHPUnit tests. - cd /var/www/sites/d8_webform/core - php ../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" --group webform - - cd /var/www/sites/d8_webform/core + cd /var/www/sites/d8_webform/web/core + php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" --group webform # Execute individual PHPUnit tests. - export SIMPLETEST_DB=mysql://drupal_d8_webform:drupal.@dm1n@localhost/drupal_d8_webform; - export SIMPLETEST_BASE_URL='http://localhost/wf'; + cd /var/www/sites/d8_webform/web/core - # Functional test. + # Functional test. php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Functional/WebformExampleFunctionalTest.php - # Kernal test. + # Kernal test. php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Kernal/Utility/WebformDialogHelperTest.php # Unit test. - php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Unit/Utility/WebformYamlTest.php + php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Unit/Utility/WebformYamlTest.php + + php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Unit/Access/WebformAccessCheckTest - php ../../vendor/phpunit/phpunit/phpunit --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" ../modules/sandbox/webform/tests/src/Unit/Access/WebformAccessCheckTest 5. Generate release notes ------------------------- [Git Release Notes for Drush](https://www.drupal.org/project/grn) - drush release-notes --nouser 8.x-5.0-VERSION 8.x-5.x + drush release-notes --nouser 8.x-5.0-rc1 8.x-5.x 6. Tag and create a new release @@ -114,11 +189,3 @@ References git push origin tag 8.x-5.0-VERSION [Create new release](https://www.drupal.org/node/add/project-release/2640714) - - -7. Upload screencast to YouTube -------------------------------- - -- Title : Webform 8.x-5.x-betaXX -- Tags: Drupal 8,Webform,Form Builder -- Privacy: listed diff --git a/web/modules/webform/docs/UPDATE-LIBRARIES.md b/web/modules/webform/docs/UPDATE-LIBRARIES.md new file mode 100644 index 0000000000..e0816648a4 --- /dev/null +++ b/web/modules/webform/docs/UPDATE-LIBRARIES.md @@ -0,0 +1,85 @@ +Steps for updating libraries +---------------------------- + + 1. Create a ticket in the Webform issue queue + 2. Create a list of all recent releases + 3. Update WebformLibrariesManager + 4. Update webform.libraries.yml + 5. Test changes + 6. Update webform_libraries.module + 7. Update composer.libraries.json + + +1. Create a ticket in the Webform issue queue +---------------------------------------------- + +- https://www.drupal.org/node/add/project-issue/webform + + +2. Create a list of all recent releases +--------------------------------------- + +- Enable all external libraries (admin/structure/webform/config/libraries) + +- Manually check for new releases. Only update to stable releases. + +- Add list of updated external libraries to issue on Drupal.org + + +3. Update WebformLibrariesManager +--------------------------------- + +- \Drupal\webform\WebformLibrariesManager::initLibraries + + +4. Update webform.libraries.yml +--------------------------------- + +- webform.libraries.yml + + +5. Test changes +--------------- + +Check external libraries are loaded from CDN. + + drush webform:libraries:remove + +Check external libraries are download. + + drush webform:libraries:download + + +6. Update webform_libraries.module +---------------------------------- + +Enable and download all libraries + + cd /var/www/sites/d8_webform + drush php-eval "\Drupal::configFactory()->getEditable('webform.settings')->set('libraries.excluded_libraries', [])->save();" + drush en -y webform_image_select + drush webform:libraries:download + +Update libraries.zip + + # Remove libraries.zip. + rm -Rf /var/www/sites/d8_webform/web/modules/sandbox/webform_libraries/libraries.zip + + # Create libraries.zip + cd /var/www/sites/d8_webform/web/ + zip -r libraries.zip libraries + mv libraries.zip /private/var/www/sites/d8_webform/web/modules/sandbox/webform_libraries/libraries.zip + +Commit changes + + # Commit changes. + cd /private/var/www/sites/d8_webform/web/modules/sandbox/webform_libraries/ + git commit -am"Update webform_libraries" + git push + + +7. Update composer.libraries.json +---------------------------------- + + cd /private/var/www/sites/d8_webform/web/modules/sandbox/webform + drush webform:libraries:composer > composer.libraries.json diff --git a/web/modules/webform/drush/webform.drush.inc b/web/modules/webform/drush/webform.drush.inc index b1b698eb5f..9c3525d62b 100644 --- a/web/modules/webform/drush/webform.drush.inc +++ b/web/modules/webform/drush/webform.drush.inc @@ -24,6 +24,7 @@ function webform_drush_command() { 'webform' => 'The webform ID you want to export (required unless --entity-type and --entity-id are specified)', ), 'options' => array( + 'exporter' => 'The type of export. (delimited, table, yaml, or json)', 'delimiter' => 'Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimiter="\t".', 'multiple-delimiter' => 'Delimiter between an element with multiple values (defaults to site-wide setting).', 'file-name' => 'File name used to export submission and uploaded filed. You may use tokens.', @@ -33,6 +34,7 @@ function webform_drush_command() { 'options-multiple-format' => 'Set to "separate" (default) or "compact" to determine how multiple select list values are exported.', 'entity-reference-items' => 'Comma-separated list of entity reference items (id, title, and/or url) to be exported.', 'excluded-columns' => 'Comma-separated list of component IDs or webform keys to exclude.', + 'uuid' => ' Use UUIDs for all entity references. (Only applies to CSV download)', 'entity-type' => 'The entity type to which this submission was submitted from.', 'entity-id' => 'The ID of the entity of which this webform submission was submitted from.', 'range-type' => 'Range of submissions to export: "all", "latest", "serial", "sid", or "date".', @@ -43,12 +45,32 @@ function webform_drush_command() { 'state' => 'Submission state to be included: "completed", "draft" or "all" (default).', 'sticky' => 'Flagged/starred submission status.', 'files' => 'Download files: "1" or "0" (default). If set to 1, the exported CSV file and any submission file uploads will be download in a gzipped tar file.', - 'destination' => 'The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the commandline.', + 'destination' => 'The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the command line.', ), 'aliases' => array( 'wfx', ), ), + 'webform-import' => array( + 'description' => 'Imports webform submissions from a CSV file.', + 'core' => array( + '8+', + ), + 'bootstrap' => 2, + 'arguments' => array( + 'webform' => 'The webform ID you want to import (required unless --entity-type and --entity-id are specified)', + 'import_uri' => 'The path or URI for the CSV file to be imported.', + ), + 'options' => array( + 'skip_validation' => 'Skip form validation.', + 'treat_warnings_as_errors' => 'Treat all warnings as errors.', + 'entity-type' => 'The entity type to which this submission was submitted from.', + 'entity-id' => 'The ID of the entity of which this webform submission was submitted from.', + ), + 'aliases' => array( + 'wfi', + ), + ), 'webform-purge' => array( 'description' => 'Purge webform submissions from the databases', 'core' => array( @@ -177,13 +199,13 @@ function webform_drush_command() { ), ), 'webform-repair' => array( - 'description' => 'Makes sure all Webform admin settings and webforms are up-to-date.', + 'description' => 'Makes sure all Webform admin configuration and webform settings are up-to-date.', 'core' => array( '8+', ), 'bootstrap' => 1, 'examples' => array( - 'webform-repair' => 'Repairs admin settings and webforms are up-to-date.', + 'webform-repair' => 'Repairs admin configuration and webform settings are up-to-date.', ), 'aliases' => array( 'wfr', @@ -252,6 +274,24 @@ function drush_webform_export() { return call_user_func_array([\Drupal::service('webform.cli_service'), 'drush_webform_export'], func_get_args()); } +/******************************************************************************/ +// drush webform-import. DO NOT EDIT. +/******************************************************************************/ + +/** + * Implements drush_hook_COMMAND_validate(). + */ +function drush_webform_import_validate() { + return call_user_func_array([\Drupal::service('webform.cli_service'), 'drush_webform_import_validate'], func_get_args()); +} + +/** + * Implements drush_hook_COMMAND(). + */ +function drush_webform_import() { + return call_user_func_array([\Drupal::service('webform.cli_service'), 'drush_webform_import'], func_get_args()); +} + /******************************************************************************/ // drush webform-purge. DO NOT EDIT. /******************************************************************************/ diff --git a/web/modules/webform/images/elements/date-calendar.png b/web/modules/webform/images/elements/date-calendar.png new file mode 100644 index 0000000000..60c2f3c15b --- /dev/null +++ b/web/modules/webform/images/elements/date-calendar.png @@ -0,0 +1,3 @@ +�PNG + +��� IHDR����������Xo ���tEXtSoftware�Adobe ImageReadyq�e<���PLTE���u��N��/��1�ɴ��qqq������s��-��� tRNS���������SOx���8IDATx�b���lh�$���L �!F��T�����Y�.!V@��~����R�J>w#O����IEND�B`� \ No newline at end of file diff --git a/web/modules/webform/includes/webform.date.inc b/web/modules/webform/includes/webform.date.inc index 4c2f32e6d9..db63b428a9 100644 --- a/web/modules/webform/includes/webform.date.inc +++ b/web/modules/webform/includes/webform.date.inc @@ -7,9 +7,31 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\Core\Datetime\DateHelper; /** - * Callback for custom datetime datepicker. + * Callback for removing abbreviation from datelist. + * + * @param array $element + * The element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param \Drupal\Core\Datetime\DrupalDateTime|null $date + * The date value. + * + * @see \Drupal\Core\Datetime\Element\Datelist::processDatelist + */ +function _webform_datelist_date_date_callback(array &$element, FormStateInterface $form_state, $date) { + $no_abbreviate = (isset($element['#date_abbreviate']) && $element['#date_abbreviate'] === FALSE); + if ($no_abbreviate && isset($element['month']) && isset($element['month']['#options'])) { + // Load translated date part labels from the appropriate calendar plugin. + $date_helper = new DateHelper(); + $element['month']['#options'] = $date_helper->monthNames($element['#required']); + } +} + +/** + * Callback for custom datetime date element. * * @param array $element * The element. @@ -20,19 +42,27 @@ * * @see \Drupal\webform\Plugin\WebformElement\DateTime::prepare */ -function _webform_datetime_datepicker(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) { - // Convert #type from datepicker to textfield. - if (isset($element['#date_date_element']) && $element['#date_date_element'] === 'datepicker') { - $element['date']['#type'] = 'textfield'; +function _webform_datetime_date(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) { + // Make sure the date element is being displayed. + if (!isset($element['date'])) { + return; + } + + $type = (isset($element['#date_date_element'])) ? $element['#date_date_element'] : 'date'; + switch ($type) { + case 'datepicker': + // Convert #type from datepicker to textfield. + $element['date']['#type'] = 'textfield'; - // Must manually set 'data-drupal-date-format' to trigger date picker. - // @see \Drupal\Core\Render\Element\Date::processDate - $element['date']['#attributes']['data-drupal-date-format'] = [$element['date']['#date_date_format']]; + // Must manually set 'data-drupal-date-format' to trigger date picker. + // @see \Drupal\Core\Render\Element\Date::processDate + $element['date']['#attributes']['data-drupal-date-format'] = [$element['date']['#date_date_format']]; + break; } } /** - * Callback for custom datetime timepicker. + * Callback for custom datetime time element. * * @param array $element * The element. @@ -43,16 +73,58 @@ function _webform_datetime_datepicker(array &$element, FormStateInterface $form_ * * @see \Drupal\webform\Plugin\WebformElement\DateTime::prepare */ -function _webform_datetime_timepicker(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) { - // Convert #type from timepicker to textfield. - if (isset($element['#date_time_element']) && $element['#date_time_element'] === 'timepicker') { - $element['time']['#type'] = 'textfield'; +function _webform_datetime_time(array &$element, FormStateInterface $form_state, DrupalDateTime $date = NULL) { + // Make sure the time element is being displayed. + if (!isset($element['time'])) { + return; + } + + // Apply time specific min/max to the element. + foreach (['min', 'max'] as $property) { + if (!empty($element["#date_time_$property"])) { + $value = $element["#date_time_$property"]; + } + elseif (!empty($element["#date_$property"])) { + $value = date('H:i:s', strtotime($element["#date_$property"])); + } + else { + $value = NULL; + } + if ($value) { + $element['time']["#$property"] = $value; + $element['time']['#attributes'][$property] = $value; + } + } + + // Apply time step and format to the element. + if (!empty($element['#date_time_step'])) { + $element['time']['#step'] = $element['#date_time_step']; + $element['time']['#attributes']['step'] = $element['#date_time_step']; + } + if (!empty($element['#date_time_format'])) { + $element['time']['#time_format'] = $element['#date_time_format']; } - // Must manually set 'data-webform-time-format' to trigger time picker. - // @see \Drupal\webform\Element\WebformTime::processWebformTime - $element['time']['#attributes']['data-webform-time-format'] = [$element['#date_time_format']]; + // Remove extra attributes for date element. + unset( + $element['time']['#attributes']['data-min-year'], + $element['time']['#attributes']['data-max-year'] + ); + + $type = (isset($element['#date_time_element'])) ? $element['#date_time_element'] : 'time'; - // Attached custom timepicker library. - $element['time']['#attached']['library'][] = 'webform/webform.element.time'; + switch ($type) { + case 'timepicker': + $element['time']['#type'] = 'webform_time'; + $element['time']['#timepicker'] = TRUE; + break; + + case 'time': + $element['time']['#type'] = 'webform_time'; + break; + + case 'text': + $element['time']['#element_validate'][] = ['\Drupal\webform\Element\WebformTime', 'validateWebformTime']; + break; + } } diff --git a/web/modules/webform/includes/webform.editor.inc b/web/modules/webform/includes/webform.editor.inc new file mode 100644 index 0000000000..e0ed736440 --- /dev/null +++ b/web/modules/webform/includes/webform.editor.inc @@ -0,0 +1,267 @@ +<?php + +/** + * @file + * Webform module editor file upload hooks. + * + * Because Webforms are config entities the editor.module's file uploading + * is not supported. + * + * The below code adds file upload support to Webform config entities and + * 'webform.settings' config. + * + * Below functions are copied from editor.module. + */ + +use Drupal\Core\Config\Config; +use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Serialization\Yaml; +use Drupal\webform\WebformInterface; +use Drupal\Component\Utility\Html; + +/******************************************************************************/ +// Webform entity hooks. +/******************************************************************************/ + +/** + * Implements hook_webform_insert(). + * + * @see editor_entity_insert() + */ +function webform_webform_insert(WebformInterface $webform) { + $uuids = _webform_get_config_entity_file_uuids($webform); + _webform_record_file_usage($uuids, $webform->getEntityTypeId(), $webform->id()); +} + +/** + * Implements hook_webform_update(). + * + * @see editor_entity_update() + */ +function webform_webform_update(WebformInterface $webform) { + $original_uuids = _webform_get_config_entity_file_uuids($webform->original); + $uuids = _webform_get_config_entity_file_uuids($webform); + + // Detect file usages that should be incremented. + $added_files = array_diff($uuids, $original_uuids); + _webform_record_file_usage($added_files, $webform->getEntityTypeId(), $webform->id()); + + // Detect file usages that should be decremented. + $removed_files = array_diff($original_uuids, $uuids); + _webform_delete_file_usage($removed_files, $webform->getEntityTypeId(), $webform->id(), 1); +} + +/** + * Implements hook_webform_delete(). + * + * @see editor_entity_delete() + */ +function webform_webform_delete(WebformInterface $webform) { + $uuids = _webform_get_config_entity_file_uuids($webform); + _webform_delete_file_usage($uuids, $webform->getEntityTypeId(), $webform->id(), 0); +} + +/******************************************************************************/ +// Webform config (settings) hooks. +// @see \Drupal\webform\Form\AdminConfig\WebformAdminConfigBaseForm::loadConfig +// @see \Drupal\webform\Form\AdminConfig\WebformAdminConfigBaseForm::saveConfig +/******************************************************************************/ + +/** + * Update config editor file references. + * + * @param \Drupal\Core\Config\Config $config + * An editable configuration object. + */ +function _webform_config_update(Config $config) { + $original_uuids = _webform_get_array_file_uuids($config->getOriginal()); + $uuids = _webform_get_array_file_uuids($config->getRawData()); + + // Detect file usages that should be incremented. + $added_files = array_diff($uuids, $original_uuids); + _webform_record_file_usage($added_files, 'config', $config->getName()); + + // Detect file usages that should be decremented. + $removed_files = array_diff($original_uuids, $uuids); + _webform_delete_file_usage($removed_files, 'config', $config->getName(), 1); +} + +/** + * Delete config editor file references. + * + * @param \Drupal\Core\Config\Config $config + * An editable configuration object. + * + * @see webform_uninstall() + */ +function _webform_config_delete(Config $config) { + $uuids = _webform_get_array_file_uuids($config->getRawData()); + _webform_delete_file_usage($uuids, 'config', $config->getName(), 0); +} + +/******************************************************************************/ +// Config entity functions. +/******************************************************************************/ + +/** + * Finds all files referenced (data-entity-uuid) by config entity. + * + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity + * An entity whose fields to analyze. + * + * @return array + * An array of file entity UUIDs. + * + * @see _editor_get_file_uuids_by_field() + */ +function _webform_get_config_entity_file_uuids(ConfigEntityInterface $entity) { + return _webform_get_array_file_uuids($entity->toArray()); +} + +/******************************************************************************/ +// Config settings functions. +/******************************************************************************/ + +/** + * Finds all files referenced (data-entity-uuid) in an associatve array. + * + * @param array $data + * An associative array. + * + * @return array + * An array of file entity UUIDs. + * + * @see _editor_get_file_uuids_by_field() + */ +function _webform_get_array_file_uuids(array $data) { + $text = Yaml::encode($data); + return _webform_parse_file_uuids($text); +} + +/******************************************************************************/ +// File usage functions. +/******************************************************************************/ + +/** + * Records file usage of files referenced by formatted text fields. + * + * Every referenced file that does not yet have the FILE_STATUS_PERMANENT state, + * will be given that state. + * + * @param array $uuids + * An array of file entity UUIDs. + * @param string $type + * The type of the object that contains the referenced file. + * @param string $id + * The unique ID of the object containing the referenced file. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * + * @see _editor_record_file_usage() + */ +function _webform_record_file_usage(array $uuids, $type, $id) { + if (empty($uuids) || !\Drupal::moduleHandler()->moduleExists('file')) { + return; + } + + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + + foreach ($uuids as $uuid) { + if ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid)) { + if ($file->status !== FILE_STATUS_PERMANENT) { + $file->status = FILE_STATUS_PERMANENT; + $file->save(); + } + $file_usage->add($file, 'editor', $type, $id); + } + } +} + +/** + * Deletes file usage of files referenced by formatted text fields. + * + * @param array $uuids + * An array of file entity UUIDs. + * @param string $type + * The type of the object that contains the referenced file. + * @param string $id + * The unique ID of the object containing the referenced file. + * @param int $count + * The number of references to delete. Should be 1 when deleting a single + * revision and 0 when deleting an entity entirely. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * + * @see \Drupal\file\FileUsage\FileUsageInterface::delete() + * @see _editor_delete_file_usage() + */ +function _webform_delete_file_usage(array $uuids, $type, $id, $count) { + if (empty($uuids) || !\Drupal::moduleHandler()->moduleExists('file')) { + return; + } + + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + + $make_unused_managed_files_temporary = \Drupal::config('webform.settings')->get('html_editor.make_unused_managed_files_temporary'); + foreach ($uuids as $uuid) { + if ($file = \Drupal::entityManager()->loadEntityByUuid('file', $uuid)) { + $file_usage->delete($file, 'editor', $type, $id, $count); + // Make unused files temporary. + if ($make_unused_managed_files_temporary && empty($file_usage->listUsage($file)) && !$file->isTemporary()) { + $file->setTemporary(); + $file->save(); + } + } + } +} + +/******************************************************************************/ +// File parsing functions. +/******************************************************************************/ + +/** + * Parse an HTML snippet for any linked file with data-entity-uuid attributes. + * + * @param string $text + * The partial (X)HTML snippet to load. Invalid markup will be corrected on + * import. + * + * @return array + * An array of all found UUIDs. + * + * @see _editor_parse_file_uuids() + */ +function _webform_parse_file_uuids($text) { + if (strpos($text, 'data-entity-uuid') === FALSE) { + return []; + } + + $uuids = []; + // Get all images using a regex. + if (preg_match_all('/<img[^>]+>/', $text, $matches)) { + foreach ($matches[0] as $img) { + // Cleanup quotes escaped via YAML. + // Please note, calling stripslashes() twice because elements are + // double escaped. + $img = stripslashes(stripslashes($img)); + + // Create a DomElement so that we can parse the image's attributes. + $dom_node = Html::load($img)->getElementsByTagName('img')->item(0); + + // Get the entity type and uuid. + $type = $dom_node->getAttribute('data-entity-type'); + $uuid = $dom_node->getAttribute('data-entity-uuid'); + + // Check the image is a file entity with a uuid. + if ($type === 'file' && $uuid) { + $uuids[] = $uuid; + } + } + } + + // Use array_unique() to collect one uuid per uploaded file. + // This prevents cut-n-pasted uploaded files from having multiple usages. + return array_unique($uuids); +} diff --git a/web/modules/webform/includes/webform.install.inc b/web/modules/webform/includes/webform.install.inc index 75a774d139..abc6503440 100644 --- a/web/modules/webform/includes/webform.install.inc +++ b/web/modules/webform/includes/webform.install.inc @@ -18,7 +18,7 @@ * done via an update hook. * * @param bool $reset - * If set TRUE old admin settings will be completly deleted. + * If set TRUE old admin settings will be completely deleted. * * @see drush_webform_repair() */ @@ -68,7 +68,7 @@ function _webform_update_admin_settings($reset = FALSE) { } /** - * Update webform setting to reflect changes in the default settings. + * Update webform settings to reflect changes in the default settings. * * This function can be used to apply new webform settings to all existing * webforms. @@ -76,14 +76,35 @@ function _webform_update_admin_settings($reset = FALSE) { * @see \Drupal\webform\Entity\Webform::setSettings */ function _webform_update_webform_settings() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $data = $webform_config->getRawData(); + $data = _webform_update_webform_setting($data); + $webform_config->setData($data)->save(); + } +} + +/** + * Update webform setting to reflect changes in the default settings. + * + * @param array $data + * A webform's raw configuration data from webform.webform.*.yml. + * + * @return array + * Updated raw configuration data. + */ +function _webform_update_webform_setting(array $data) { $default_properties = [ 'langcode' => 'en', 'status' => WebformInterface::STATUS_OPEN, 'dependencies' => [], 'open' => NULL, 'close' => NULL, + 'weight' => 0, 'uid' => '', 'template' => FALSE, + 'archive' => FALSE, 'id' => '', 'title' => '', 'description' => '', @@ -97,79 +118,82 @@ function _webform_update_webform_settings() { ]; $default_settings = Webform::getDefaultSettings(); - $config_factory = \Drupal::configFactory(); - // Update 'webform.webform.*' configuration. - foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { - $webform_config = $config_factory->getEditable($webform_config_name); - // Get data. - $data = $webform_config->getRawData(); - - // Always apply the default properties. - $properties = $default_properties; - // Now apply defined properties. - foreach ($data as $name => $value) { - $properties[$name] = $value; - } - // Set properties. - $data = $properties; + // Always apply the default properties. + $properties = $default_properties; + // Now apply defined properties. + foreach ($data as $name => $value) { + $properties[$name] = $value; + } + // Set properties. + $data = $properties; - // Always apply the default settings. - $settings = $default_settings; - // Now apply custom settings. - foreach ($data['settings'] as $name => $value) { - $settings[$name] = $value; - } - // Set settings. - $data['settings'] = $settings; + // Always apply the default settings. + $settings = $default_settings; + // Now apply custom settings. + foreach ($data['settings'] as $name => $value) { + $settings[$name] = $value; + } + // Set settings. + $data['settings'] = $settings; - // Set access. - $data['access'] += Webform::getDefaultAccessRules(); + // Set access. + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + $data['access'] += $access_rules_manager->getDefaultAccessRules(); - // Save data. - $webform_config->setData($data)->save(); - } + return $data; } /** - * Update webform handler configuration and settings to reflect changes in a handler's default configuration. - * - * Must be called by passing in the handler's class using ::class. - * - * Example usage: - * @code - * _webform_update_webform_handler_configuration(); - * _webform_update_webform_handler_configuration(EmailWebformHandler::class); - * @endcode - * - * @param string $handler_class - * A handler class instance. If NULL all handlers will be updated. - * Defaults to NULL. + * Update webform handler settings to reflect changes in a handler's default configuration. * * @see \Drupal\webform\Plugin\WebformHandlerInterface */ -function _webform_update_webform_handler_configuration($handler_class = NULL) { - /** @var \Drupal\webform\WebformInterface[] $webforms */ - $webforms = Webform::loadMultiple(); - foreach ($webforms as $webform) { +function _webform_update_webform_handler_settings() { + // Issue #2863986: Allow updating modules with new service dependencies. + \Drupal::service('kernel')->rebuildContainer(); + + // Get the default configuration (aka settings) for all handlers provided + // by the Webform module. + /** @var \Drupal\webform\Plugin\WebformHandlerManagerInterface $handler_manager */ + $handler_manager = \Drupal::service('plugin.manager.webform.handler'); + $definitions = $handler_manager->getDefinitions(); + $default_handler_settings = []; + foreach ($definitions as $plugin_id => $definition) { + if (strpos($definition['provider'], 'webform_test_') === 0 || in_array($definition['provider'], ['webform', 'webform_scheduled_email'])) { + $default_handler_settings[$plugin_id] = $handler_manager->createInstance($plugin_id)->defaultConfiguration(); + } + } + + $config_factory = \Drupal::configFactory(); + // Update 'webform.webform.*' configuration. + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + + // Get data. + $data = $webform_config->getRawData(); + + // Apply the default handler settings. $has_handler = FALSE; - $handlers = $webform->getHandlers(); - foreach ($handlers as $handler) { - $is_handler_class = ($handler_class === NULL || $handler instanceof $handler_class); - if ($is_handler_class) { - $has_handler = TRUE; - $configuration = $handler->getConfiguration(); + foreach ($data['handlers'] as &$handler) { + if (!isset($default_handler_settings[$handler['id']])) { + continue; + } - $settings = $handler->defaultConfiguration(); - foreach ($configuration['settings'] as $settings_key => $setting_value) { - $settings[$settings_key] = $setting_value; - } - $configuration['settings'] = $settings; - $handler->setConfiguration($configuration); + $settings = $default_handler_settings[$handler['id']]; + foreach ($handler['settings'] as $settings_key => $setting_value) { + $settings[$settings_key] = $setting_value; + } + + if ($handler['settings'] != $settings) { + $has_handler = TRUE; + $handler['settings'] = $settings; } } + if ($has_handler) { - $webform->save(); + $webform_config->setData($data)->save(); } } } @@ -261,6 +285,38 @@ function _webform_update_webform_submission_storage_schema() { $manager->updateEntityType($manager->getEntityType('webform_submission')); } +/** + * Replace string in webform.settings.yml and webform.webform.*.yml. + * + * @param string $search + * String to be search for. + * @param string $replace + * String to be replace with. + */ +function _webform_update_string_replace($search, $replace) { + $config_factory = \Drupal::configFactory(); + + // Update 'webform.settings' configuration. + $settings_config = \Drupal::configFactory()->getEditable('webform.settings'); + $yaml = Yaml::encode($settings_config->getRawData()); + if (strpos($yaml, $search) !== FALSE) { + $yaml = str_replace($search, $replace, $yaml); + $settings_config->setData(Yaml::decode($yaml)); + $settings_config->save(); + } + + // Update 'webform.webform.*' configuration. + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $yaml = Yaml::encode($webform_config->getRawData()); + if (strpos($yaml, $search) !== FALSE) { + $yaml = str_replace($search, $replace, $yaml); + $webform_config->setData(Yaml::decode($yaml)); + $webform_config->save(); + } + } +} + /** * Clear/remove selected webform element properties. * @@ -308,4 +364,3 @@ function _webform_update_elements_clear_properties_recursive(array &$element, ar } } } - diff --git a/web/modules/webform/includes/webform.install.requirements.inc b/web/modules/webform/includes/webform.install.requirements.inc index d7ba827d8e..c8ce139862 100644 --- a/web/modules/webform/includes/webform.install.requirements.inc +++ b/web/modules/webform/includes/webform.install.requirements.inc @@ -23,7 +23,7 @@ function webform_requirements($phase) { // Check HTML email handling. /****************************************************************************/ - /** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */ + /** @var \Drupal\webform\WebformEProviderInterface $email_provider */ $email_provider = \Drupal::service('webform.email_provider'); $mail_module_name = $email_provider->getModuleName(); $mail_plugin_id = $email_provider->getMailPluginId(); diff --git a/web/modules/webform/includes/webform.install.update.inc b/web/modules/webform/includes/webform.install.update.inc index 00942f68b0..9df1f4d580 100644 --- a/web/modules/webform/includes/webform.install.update.inc +++ b/web/modules/webform/includes/webform.install.update.inc @@ -5,22 +5,38 @@ * Archived Webform update hooks. */ +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\Component\Uuid\Php as Uuid; use Drupal\Core\Cache\Cache; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Database\Database; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Render\Element; +use Drupal\Core\Url; +use Drupal\views\Entity\View; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformOptions; -use Drupal\webform\Plugin\WebformHandler\ActionWebformHandler; use Drupal\webform\Plugin\WebformHandler\EmailWebformHandler; use Drupal\webform\WebformInterface; use Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformFormHelper; use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\Utility\WebformReflectionHelper; use Drupal\webform\Utility\WebformYaml; +/** + * Implements hook_update_dependencies(). + */ +function webform_update_dependencies() { + // Ensure that system_update_8501() runs before the webform update, so that + // the new revision_default field is installed in the correct table. + // @see https://www.drupal.org/project/webform/issues/2958102 + $dependencies['webform'][8099]['system'] = 8501; + + return $dependencies; +} + /******************************************************************************/ // Webform-8.x-5.0-beta1 - December 7, 2016 (No update required). /******************************************************************************/ @@ -62,23 +78,7 @@ function webform_update_8001() { * Issue #2834572: Refactor and improve token management. */ function webform_update_8002() { - $config_factory = \Drupal::configFactory(); - - // Update 'webform.settings' configuration. - $settings_config = \Drupal::configFactory()->getEditable('webform.settings'); - $yaml = Yaml::encode($settings_config->getRawData()); - $yaml = str_replace('[webform_submission:', '[webform_submission:', $yaml); - $settings_config->setData(Yaml::decode($yaml)); - $settings_config->save(); - - // Update 'webform.webform.*' configuration. - foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { - $webform_config = $config_factory->getEditable($webform_config_name); - $yaml = Yaml::encode($webform_config->getRawData()); - $yaml = str_replace('[webform_submission:', '[webform_submission:', $yaml); - $webform_config->setData(Yaml::decode($yaml)); - $webform_config->save(); - } + _webform_update_string_replace('[webform-submission:', '[webform_submission:'); } /** @@ -244,6 +244,9 @@ function webform_update_8011() { * A form element. */ function _webform_update_8011(array &$element) { + // Issue #2863986: Allow updating modules with new service dependencies. + \Drupal::service('kernel')->rebuildContainer(); + if (isset($element['#format'])) { /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ $element_manager = \Drupal::service('plugin.manager.webform.element'); @@ -313,6 +316,9 @@ function webform_update_8014() { * A form element. */ function _webform_update_8014(array &$element) { + // Issue #2863986: Allow updating modules with new service dependencies. + \Drupal::service('kernel')->rebuildContainer(); + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ $element_manager = \Drupal::service('plugin.manager.webform.element'); @@ -490,6 +496,9 @@ function webform_update_8024() { * Issue #2857417: Add support for open and close date/time to Webform nodes. Update database scheme. */ function webform_update_8025() { + // Issue #2863986: Allow updating modules with new service dependencies. + \Drupal::service('kernel')->rebuildContainer(); + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); @@ -628,7 +637,7 @@ function webform_update_8030() { * Issue #2854021: Send email based on element options selection. */ function webform_update_8031() { - _webform_update_webform_handler_configuration(EmailWebformHandler::class); + _webform_update_webform_handler_settings(); } /** @@ -733,7 +742,7 @@ function webform_update_8034() { /******************************************************************************/ /******************************************************************************/ -// Webform-8.x-5.0-beta12 - April 21, 2017. +// Webform-8.x-5.0-beta12 - April 21, 2017. /******************************************************************************/ /** @@ -1012,7 +1021,7 @@ function webform_update_8046() { * Webform config data. * * @return array - * Webform config data with 'webfor_actions' element. + * Webform config data with 'webform_actions' element. */ function _webform_update_8046_convert_data(array $data) { $button_names = [ @@ -1196,7 +1205,7 @@ function webform_update_8057() { } /** - * Issue #2888076: Redirect users to login page when trying to access the a protected webform file. + * Issue #2888076: Redirect users to login page when trying to access a protected webform file. */ function webform_update_8058() { _webform_update_admin_settings(); @@ -1583,7 +1592,7 @@ function webform_update_8077() { */ function webform_update_8078() { _webform_update_admin_settings(); - _webform_update_webform_handler_configuration(EmailWebformHandler::class); + _webform_update_webform_handler_settings(); } /** @@ -1613,7 +1622,7 @@ function webform_update_8080() { * Issue #2913215: Remote Post handler add GET method support. */ function webform_update_8081() { - _webform_update_webform_handler_configuration(RemotePostWebformHandler::class); + _webform_update_webform_handler_settings(); } /** @@ -1826,7 +1835,7 @@ function webform_update_8096() { * Issue #2932607: Add Twig support to email body. */ function webform_update_8097() { - _webform_update_webform_handler_configuration(EmailWebformHandler::class); + _webform_update_webform_handler_settings(); } /******************************************************************************/ @@ -1891,7 +1900,7 @@ function webform_update_8099() { */ function webform_update_8100() { // Add locked to action handler. - _webform_update_webform_handler_configuration(ActionWebformHandler::class); + _webform_update_webform_handler_settings(); // Add locked to remote post handler's excluded data. /** @var \Drupal\webform\WebformInterface[] $webforms */ @@ -1973,7 +1982,7 @@ function webform_update_8105() { } /******************************************************************************/ -// Webform-8.x-5.0-rc4 +// Webform-8.x-5.0-rc4 - March 13, 2018. /******************************************************************************/ /** @@ -2048,7 +2057,6 @@ function webform_update_8110() { $excluded_elements['password_confirm'] = 'password_confirm'; $admin_config->set('element.excluded_elements', $excluded_elements); $admin_config->save(); - } } @@ -2087,7 +2095,6 @@ function webform_update_8113() { $config->save(); } - /** * Issue #2951921: The contribute module is missing from the file system. */ @@ -2122,6 +2129,18 @@ function webform_update_8114() { ->execute(); } +/******************************************************************************/ +// Webform-8.x-5.0-rc5 - March 14, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc6 - March 16, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc7 - March 23, 2018. +/******************************************************************************/ + /** * Issue #2955218: Allow query and token to be removed from confirmation URL. */ @@ -2129,8 +2148,12 @@ function webform_update_8115() { _webform_update_webform_settings(); } +/******************************************************************************/ +// Webform-8.x-5.0-rc8 - April 2, 2018. +/******************************************************************************/ + /** - * #multiple__label and #multiple__labels are being saved with every element. + * Stop #multiple__label and #multiple__labels from being saved with every element. */ function webform_update_8116() { _webform_update_elements_clear_properties([ @@ -2140,50 +2163,43 @@ function webform_update_8116() { } /** - * Issue #2957192: Add postal_code to test data + * Issue #2957192: Add postal_code to test data. */ function webform_update_8117() { $config = \Drupal::configFactory()->getEditable('webform.settings'); $names = Yaml::decode($config->get('test.names')); $names += [ - 'postal_code' => ['11111', '12345', '12345-6789'] + 'postal_code' => ['11111', '12345', '12345-6789'], ]; - $config->set('test.names', WebformYaml::tidy(Yaml::encode($names))); + $config->set('test.names', WebformYaml::encode($names)); $config->save(); } +/******************************************************************************/ +// Webform-8.x-5.0-rc9 - April 7, 2018. +/******************************************************************************/ + /** * Issue #2957074: Invalid Tokens in Email Handler. */ function webform_update_8118() { - $config_factory = \Drupal::configFactory(); - - // Update 'webform.settings' configuration. - $settings_config = \Drupal::configFactory()->getEditable('webform.settings'); - $yaml = Yaml::encode($settings_config->getRawData()); - $yaml = str_replace('[webform-submission:', '[webform_submission:', $yaml); - $settings_config->setData(Yaml::decode($yaml)); - $settings_config->save(); - - // Update 'webform.webform.*' configuration. - foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { - $webform_config = $config_factory->getEditable($webform_config_name); - $yaml = Yaml::encode($webform_config->getRawData()); - $yaml = str_replace('[webform-submission:', '[webform_submission:', $yaml); - $webform_config->setData(Yaml::decode($yaml)); - $webform_config->save(); - } + _webform_update_string_replace('[webform-submission:', '[webform_submission:'); } - /** * Issue #2957002: Same webform multiple times on the same page. */ function webform_update_8119() { + // Issue #2863986: Allow updating modules with new service dependencies. + \Drupal::service('kernel')->rebuildContainer(); + + // Issue #2982273: Route "webform.config.libraries" does not exist. + \Drupal::service('router.builder')->rebuild(); + /** @var \Drupal\webform\WebformHelpManagerInterface $help_manager */ $help_manager = \Drupal::service('webform.help_manager'); $help_manager->addNotification('webform_update_8119', - t('<strong>ATTENTION DEVELOPERS!!!</strong> The webform submission form\'s BASE_FORM_ID and FORM_ID have changed.') . + t("<strong>ATTENTION DEVELOPERS!!!</strong> The webform submission form's BASE_FORM_ID and FORM_ID have changed.") . '<br/>' . t('Please make sure to update all webform related <code>hook_form_BASE_FORM_ID_alter()</code> and <code>hook_form_FORM_ID_alter()</code> hooks.') . ' ' . t('<a href=":href">Learn more</a>', [':href' => 'https://www.drupal.org/node/2959264']), 'warning' @@ -2194,19 +2210,871 @@ function webform_update_8119() { * Issue #2953929: Remote handler does not display messages when HTTP status is different than 200. */ function webform_update_8120() { - _webform_update_webform_handler_configuration(RemotePostWebformHandler::class); + _webform_update_webform_handler_settings(); } /** * Issue #2953929: Remote handler does not display messages when HTTP status is different than 200. */ function webform_update_8121() { - _webform_update_webform_handler_configuration(RemotePostWebformHandler::class); + _webform_update_webform_handler_settings(); } +/******************************************************************************/ +// Webform-8.x-5.0-rc10 - April 9, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc11 - April 20, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc12 - April 25 2018. +/******************************************************************************/ + /** - * Issue #2952419: Attached files are deleted without usage checking + * Issue #2952419: Attached files are deleted without usage checking. */ function webform_update_8122() { _webform_update_admin_settings(); } + +/******************************************************************************/ +// Webform-8.x-5.0-rc13 - June 4, 2018. +/******************************************************************************/ + +/** + * Issue #2962442: Remove [webform-authenticated-user] token and use [current-user] token with clear value option. + */ +function webform_update_8123() { + _webform_update_string_replace('[webform-authenticated-user:', '[current-user:'); +} + +/** + * Issue #2971207: Hidden Field updated values not being captured on Submit. + */ +function webform_update_8124() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $data = $webform_config->getRawData(); + if (strpos($data['elements'], "'#type': hidden") !== FALSE) { + $elements = Yaml::decode($data['elements']); + _webform_update_8124($elements); + $data['elements'] = Yaml::encode($elements); + + $webform_config->setData($data); + $webform_config->save(); + } + } +} + +/** + * Recursively convert hidden elements #value to #default_value. + * + * @param array $element + * An element. + */ +function _webform_update_8124(array &$element) { + if (isset($element['#type']) && $element['#type'] === 'hidden') { + if (isset($element['#value']) && !isset($element['#default_value'])) { + $element['#default_value'] = $element['#value']; + unset($element['#value']); + } + } + foreach (Element::children($element) as $key) { + if (is_array($element[$key])) { + _webform_update_8124($element[$key]); + } + } +} + +/** + * Issue #2966507: Start-to-finish documentation for showing Webforms in modals. + */ +function webform_update_8125() { + _webform_update_admin_settings(); +} + +/** + * Issue #2973377: Make the previously saved messages customizable. + */ +function webform_update_8126() { + _webform_update_admin_settings(); + _webform_update_webform_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc14 - June 6, 2018. +/******************************************************************************/ + +/** + * Issue #2974597: Enable default publishing status of new webforms. + */ +function webform_update_8127() { + _webform_update_admin_settings(); +} + +/** + * Issue #2932893: Filter out closed forms in webform field. + */ +function webform_update_8128() { + _webform_update_webform_settings(); +} + +/** + * Issue #2977378: Add 'exclude unselected checkboxes' from email notification and preview. + */ +function webform_update_8129() { + _webform_update_admin_settings(); + _webform_update_webform_settings(); + _webform_update_webform_handler_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc15 - June 12, 2018. +/******************************************************************************/ + +/** + * Issue #2974153: Non admin duplicate form. + */ +function webform_update_8130() { + _webform_update_webform_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc16 - June 29, 2018. +/******************************************************************************/ + +/** + * Issue #2980470: Convert email handler "default" settings to use "_default_" to prevent localization issues. + */ +function webform_update_8131() { + // Get all webform handlers that are instances of the email handler. + // This allows any custom handler that extends the EmailWebformHandler to be + // updated. + /** @var \Drupal\webform\Plugin\WebformHandlerManagerInterface $handler_manager */ + $handler_manager = \Drupal::service('plugin.manager.webform.handler'); + $definitions = $handler_manager->getDefinitions(); + $email_handler_ids = []; + foreach ($definitions as $plugin_id => $definition) { + if ($handler_manager->createInstance($plugin_id) instanceof EmailWebformHandler) { + $email_handler_ids[$plugin_id] = $plugin_id; + } + } + + $default_settings = [ + 'to_mail', + 'from_mail', + 'from_name', + 'subject', + 'body', + ]; + + $config_factory = \Drupal::configFactory(); + // Update 'webform.webform.*' configuration. + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + + // Get data. + $data = $webform_config->getRawData(); + + // Change 'default' to '_default' is email handler settings. + $save_handler = FALSE; + foreach ($data['handlers'] as &$handler) { + if (!isset($email_handler_ids[$handler['id']])) { + continue; + } + + foreach ($handler['settings'] as $settings_key => $setting_value) { + if ($setting_value === 'default' && in_array($settings_key, $default_settings)) { + $handler['settings'][$settings_key] = EmailWebformHandler::DEFAULT_VALUE; + $save_handler = TRUE; + } + } + } + + if ($save_handler) { + $webform_config->setData($data)->save(); + } + } +} + +/** + * Issue #2980276: Webform assumes the /tmp directory is always the same, but if there are multiple servers, each may have its own /tmp directory. + */ +function webform_update_8132() { + _webform_update_admin_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc17 - August 6, 2018. +/******************************************************************************/ + +/** + * Issue #2890861: Webform toggle element is not accessible. + */ +function webform_update_8133() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $data = $webform_config->getRawData(); + $yaml = Yaml::encode($data); + if (strpos($yaml, 'webform_toggle')) { + return; + } + } + + $admin_config = \Drupal::configFactory()->getEditable('webform.settings'); + $settings = $admin_config->getRawData(); + $settings['libraries']['excluded_libraries'][] = 'jquery.toggles'; + $admin_config->setData($settings)->save(); +} + +/** + * Issue #2984348: Link to relevant wizard page on preview. + */ +function webform_update_8134() { + _webform_update_admin_settings(); +} + +/** + * Issue #2984868: Allow to specify the text format for emails. + */ +function webform_update_8135() { + $admin_config = \Drupal::configFactory()->getEditable('webform.settings'); + $settings = $admin_config->getRawData(); + $settings['html_editor']['element_format'] = $settings['html_editor']['format']; + $settings['html_editor']['mail_format'] = $settings['html_editor']['format']; + unset($settings['html_editor']['format']); + $admin_config->setData($settings)->save(); +} + +/** + * Issue #2987174: Replace and improve word/character counter. + */ +function webform_update_8136() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + + $data = $webform_config->getRawData(); + if (strpos($data['elements'], "'#counter_message'") === FALSE) { + continue; + } + + $elements = Yaml::decode($data['elements']); + _webform_update_8136($elements); + + $data['elements'] = Yaml::encode($elements); + $webform_config->setData($data); + $webform_config->save(); + } +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc18 - August 7, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc19 - September 6, 2018. +/******************************************************************************/ + +/** + * Update #counter_* attributes. + * + * Replace 'X' in counter message with '%d' and + * move '#counter_message' to '#counter_maximum_message'. + * + * @param array $element + * A form element. + */ +function _webform_update_8136(array &$element) { + if (isset($element['#counter_message'])) { + if (strpos($element['#counter_message'], 'X') === FALSE) { + $element['#counter_message'] = '%d ' . $element['#counter_message']; + } + else { + $element['#counter_message'] = str_replace('X', '%d', $element['#counter_message']); + } + $element['#counter_maximum_message'] = $element['#counter_message']; + unset($element['#counter_message']); + } + + foreach (Element::children($element) as $key) { + if (is_array($element[$key])) { + _webform_update_8136($element[$key]); + } + } +} + +/** + * Issue #2983137: WYSIWYG editor not saving inline images for advanced html/text editor. + */ +function webform_update_8137() { + _webform_update_admin_settings(); +} + +/** + * Issue #2947303: Location element's geocomplete library is not supported. + */ +function webform_update_8138() { + _webform_update_admin_settings(); + + // Track if webform location geocomplete is being used. + $has_geocomplete_element = FALSE; + + // Convert 'webform_location' to 'webform_location_geocomplete'. + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $config_name) { + $config = $config_factory->getEditable($config_name); + $elements = $config->get('elements'); + if (strpos($elements, "'#type': webform_location") !== FALSE) { + $elements = str_replace("'#type': webform_location", "'#type': webform_location_geocomplete", $elements); + $config->set('elements', $elements); + $config->save(TRUE); + $has_geocomplete_element = TRUE; + } + } + + // Update exclude elements. + $admin_config = \Drupal::configFactory()->getEditable('webform.settings'); + $excluded_elements = $admin_config->get('element.excluded_elements') ?: []; + if (isset($excluded_elements['webform_location'])) { + // Convert 'webform_location' to 'webform_location_geocomplete'. + unset($excluded_elements['webform_location']); + $excluded_elements['webform_location_geocomplete'] = 'webform_location_geocomplete'; + } + elseif (!$has_geocomplete_element) { + // If location geocomplete element is not being used then excluded it. + $excluded_elements['webform_location_geocomplete'] = 'webform_location_geocomplete'; + } + $admin_config->set('element.excluded_elements', $excluded_elements); + $admin_config->save(); +} + +/** + * Issue #2918669: Elements separated by conditional inside multistep wizard are both saved to submission. + */ +function webform_update_8139() { + $build = [ + 'list' => [ + '#theme' => 'item_list', + '#title' => t('IMPORTANT! Elements, containers, and pages that are hidden using conditional logic will now have their submission data cleared when a webform is submitted.'), + '#items' => [ + t('Please make sure to test any webform that contains conditionally hidden elements, containers, or pages.'), + t('Any element that is conditionally hidden will have its submission data cleared.'), + t("Existing submissions that have conditionally hidden elements will have the element's submission data cleared when the submission is updated."), + ], + ], + 'link' => [ + '#type' => 'link', + '#title' => t('Learn more'), + '#url' => Url::fromUri('https://www.drupal.org/node/2956589'), + ], + ]; + + /** @var \Drupal\webform\WebformHelpManagerInterface $help_manager */ + $help_manager = \Drupal::service('webform.help_manager'); + $help_manager->addNotification(__FUNCTION__, $build, 'warning'); +} + +/** + * Issue #2994411: Allow help to be hidden. + */ +function webform_update_8140() { + _webform_update_admin_settings(); +} + +/** + * Issue #2993327: Provide Default View to Override Submission Results Tab. + */ +function webform_update_8141() { + _webform_update_admin_settings(); + _webform_update_webform_settings(); + + // Copied from: redirect_update_8103() + $message = NULL; + if (\Drupal::moduleHandler()->moduleExists('views') && !View::load('webform_submissions')) { + $config_path = drupal_get_path('module', 'webform') . '/config/optional/views.view.webform_submissions.yml'; + $data = ['uuid' => (new Uuid())->generate()] + Yaml::decode(file_get_contents($config_path)); + \Drupal::configFactory()->getEditable('views.view.webform_submissions')->setData($data)->save(TRUE); + $message = 'The new webform submissions view has been created.'; + } + else { + $message = 'Not creating a webform submissions view since it already exists.'; + } + return $message; +} + +/** + * Issue #2995413: Allow email handler theme to be customized. + */ +function webform_update_8142() { + _webform_update_webform_handler_settings(); +} + +/** + * Issue #2943879: How to display alternate text when the user is not allowed to create a Webform? + */ +function webform_update_8143() { + $admin_config = \Drupal::configFactory()->getEditable('webform.settings'); + // Convert form login message to access denied message. + if ($admin_config->get('settings.default_form_login_message') !== NULL) { + $admin_config->set('settings.default_form_access_denied_message', $admin_config->get('settings.default_form_login_message')); + $admin_config->clear('settings.default_form_login_message'); + } + $admin_config->save(); + _webform_update_admin_settings(); + + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $config_name) { + $config = $config_factory->getEditable($config_name); + // Convert form login to access denied. + if ($config->get('settings.form_login') !== NULL) { + $config->set('settings.form_access_denied', $config->get('settings.form_login') ? WebformInterface::ACCESS_DENIED_LOGIN : WebformInterface::ACCESS_DENIED_DEFAULT); + $config->clear('settings.form_login'); + } + // Convert form login message to access denied message. + if ($config->get('settings.form_login_message') !== NULL) { + $config->set('settings.form_access_denied_message', $config->get('settings.form_login_message')); + $config->clear('settings.form_login_message'); + } + + // Convert submission login to access denied. + if ($config->get('settings.submission_login') !== NULL) { + $config->set('settings.submission_access_denied', $config->get('settings.submission_login') ? WebformInterface::ACCESS_DENIED_LOGIN : WebformInterface::ACCESS_DENIED_DEFAULT); + $config->clear('settings.submission_login'); + } + // Convert submission login message to access denied message. + if ($config->get('settings.submission_login_message') !== NULL) { + $config->set('settings.submission_access_denied_message', $config->get('settings.submission_login_message')); + $config->clear('settings.submission_login_message'); + } + + $config->save(); + } + _webform_update_webform_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc20 - September 7, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc21 - September 8, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc22. +/******************************************************************************/ + +/** + * Issue #2998239: Swift Mailer no longer working after custom email handler theme option was added. + */ +function webform_update_8144() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + + $data = $webform_config->getRawData(); + $has_email_handler = FALSE; + foreach ($data['handlers'] as &$handler) { + if (in_array($handler['id'], ['email', 'scheduled_email'])) { + // Change 'theme' setting to 'theme_name' to prevent conflicts with + // Swift Mailer. + if (isset($handler['settings']['theme'])) { + $handler['settings']['theme_name'] = $handler['settings']['theme']; + unset($handler['settings']['theme']); + $has_email_handler = TRUE; + } + } + } + if ($has_email_handler) { + $webform_config->setData($data); + $webform_config->save(); + } + } +} + +/** + * Issue #3002183: Upload Total Limit instead of Upload File limit. + */ +function webform_update_8145() { + _webform_update_admin_settings(); + _webform_update_webform_settings(); +} + +/** + * Issue #3000542: Remove webform_devel.module logger debugging. + */ +function webform_update_8146() { + if ($config = \Drupal::configFactory()->getEditable('webform_devel.settings')) { + $config->delete(); + } +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc23 - October 20, 2018. +/******************************************************************************/ + +/** + * Issue #3006468: Hide empty fields on on submission page. + */ +function webform_update_8147() { + _webform_update_webform_settings(); +} + +/** + * Issue #3007215: Option to retain webform title when source entity is provided. + */ +function webform_update_8148() { + _webform_update_webform_settings(); +} + +/** + * Issue #3007247: Custom composite fields appear as block elements. + */ +function webform_update_8149() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $elements = $webform_config->get('elements'); + if (strpos($elements, "'add_more'") !== FALSE) { + $elements = str_replace("'#add_more'", "'#add_more_items'", $elements); + $elements = str_replace("'#multiple__add_more'", "'#multiple__add_more_items'", $elements); + $webform_config->set('elements', $elements); + $webform_config->save(TRUE); + } + } +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc24 - October 22, 2018. +/******************************************************************************/ + +/** + * Issue #2980032: SUBMISSION BEHAVIORS: Allow edit the previous submission. + */ +function webform_update_8150() { + _webform_update_webform_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc25 - November 5, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc26 - November 5, 2018 (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc27 - November 28, 2018. +/******************************************************************************/ + +/** + * Issue #3013767: Computed twig element is not working on multi-step form. + */ +function webform_update_8151() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $elements = $webform_config->get('elements'); + + // Try to decode elements. + try { + $elements = Yaml::decode($elements); + } + catch (\Exception $exception) { + continue; + } + + // Make sure elements is an array. + if (!is_array($elements)) { + continue; + } + + $has_computed_element = FALSE; + $flattened_elements =& WebformFormHelper::flattenElements($elements); + foreach ($flattened_elements as &$element) { + // Convert #value property to #template property. + // @see \Drupal\webform\Entity\Webform::initElementsRecursive + if (isset($element['#type']) && strpos($element['#type'], 'webform_computed_') === 0) { + $has_computed_element = TRUE; + if (isset($element['#value']) && !isset($element['#template'])) { + $element['#template'] = $element['#value']; + unset($element['#value']); + } + } + } + if ($has_computed_element) { + $webform_config->set('elements', Yaml::encode($elements)); + $webform_config->save(TRUE); + } + } +} + +/** + * Issue #3014933: Webform paths not being removed when a webform is deleted. + */ +function webform_update_8153() { + // Load all webforms to improve performance. + $webform = Webform::loadMultiple(); + + $database = \Drupal::database(); + $select = $database->select('url_alias', 'u'); + $select->fields('u', ['pid', 'source', 'alias', 'langcode']); + $select->condition('source', '/webform/%', 'LIKE'); + $result = $select->execute(); + while ($record = $result->fetchAssoc()) { + if (preg_match('#^/webform/([^/]+)/(?:drafts|submissions)$#', $record['source'], $match)) { + // Check if the webform still exists. + $webform_id = $match[1]; + if (!isset($webform[$webform_id])) { + // Delete the broken URL alias. + $database->delete('url_alias') + ->condition('pid', $record['pid']) + ->execute(); + } + } + } +} + +/** + * Issue #3015990: Option not to store the IP address for logged-in users. + */ +function webform_update_8154() { + _webform_update_webform_settings(); +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc28 - December 7, 2018 +/******************************************************************************/ + +/** + * Issue #3017679: 2 different validation range modes for date/time field. + */ +function webform_update_8155() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('webform.webform.') as $webform_config_name) { + $webform_config = $config_factory->getEditable($webform_config_name); + $elements = $webform_config->get('elements'); + + // Try to decode elements. + try { + $elements = Yaml::decode($elements); + } + catch (\Exception $exception) { + continue; + } + + // Make sure elements is an array. + if (!is_array($elements)) { + continue; + } + + $has_date_element = FALSE; + $flattened_elements =& WebformFormHelper::flattenElements($elements); + foreach ($flattened_elements as &$element) { + // Convert #min/max property to #date_date_(min|max) property. + // @see \Drupal\webform\Entity\Webform::initElementsRecursive + if (isset($element['#type']) && in_array($element['#type'], ['date', 'datetime', 'datelist'])) { + $has_date_element = TRUE; + if (isset($element['#min']) && !isset($element['#date_date_min'])) { + $element['#date_date_min'] = $element['#min']; + unset($element['#min']); + } + if (isset($element['#max']) && !isset($element['#date_date_max'])) { + $element['#date_date_max'] = $element['#max']; + unset($element['#max']); + } + } + } + if ($has_date_element) { + $webform_config->set('elements', Yaml::encode($elements)); + $webform_config->save(TRUE); + } + } +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc29 - December 7, 2018. (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0-rc30 - December 16, 2018. +/******************************************************************************/ + +/** + * Issue #3015180: Add 'webform_submission_log' submodule. + */ +function webform_update_8156() { + $enable = \Drupal::config('webform.settings')->get('settings.default_submission_log'); + if (!$enable) { + $query = \Drupal::entityQuery('webform') + ->condition('settings.submission_log', TRUE) + ->count(); + $enable = $query->execute() > 0; + } + + if ($enable) { + try { + \Drupal::service('module_installer')->install(['webform_submission_log']); + } + catch (\Drupal\Core\Database\SchemaObjectExistsException $exception) { + // This is actually expected. The table {webform_submission_log} would exist + // from webform submission entity schema. + } + + // Because MySQL does not allow default value on blob/text column types and + // we want to add a not null blob column to a table that is likely to have + // existing rows, we are doing it in such a 3-step fashion. + \Drupal::database()->schema()->addField('webform_submission_log', 'variables', [ + 'type' => 'blob', + 'size' => 'big', + 'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.', + ]); + + \Drupal::database()->update('webform_submission_log')->fields([ + 'variables' => serialize([]), + ])->execute(); + + \Drupal::database()->schema()->changeField('webform_submission_log', 'variables', 'variables', [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.', + ]); + } +} + +/******************************************************************************/ +// Webform-8.x-5.0-rc31 - December 31, 2018. (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.0 - December 24, 2018. (No update required). +/******************************************************************************/ + +/******************************************************************************/ +// Webform-8.x-5.1 - January 2, 2019. +/******************************************************************************/ + +/** + * Issue #3022398: Possible modification to update hook and/or documentation. + */ +function webform_update_8157() { + _webform_update_webform_submission_storage_schema(); +} + +/******************************************************************************/ +// Webform-8.x-5.2 - TDB. +/******************************************************************************/ + +/** + * Issue #3023863: Typo in State/Province codes options. + */ +function webform_update_8158() { + /** \Drupal\webform\WebformOptionsInterface */ + $webform_options = WebformOptions::load('state_province_codes'); + if (!$webform_options) { + return; + } + $options = $webform_options->getOptions(); + if (isset($options['AE']) && $options['AE'] === 'Armed Forces (Canada, Europe, Africa, or Middle East') { + $options['AE'] = 'Armed Forces (Canada, Europe, Africa, or Middle East)'; + $webform_options->setOptions($options)->save(); + } +} + +/** + * Issue #3023223: Remove unused fields settings. + */ +function webform_update_8159() { + /** @var \Drupal\field\FieldStorageConfigInterface[] $field_storage_configs */ + $field_storage_configs = FieldStorageConfig::loadMultiple(); + foreach ($field_storage_configs as $field_storage_config) { + if ($field_storage_config->getType() !== 'webform') { + continue; + } + + list($entity_type, $field_name) = explode('.', $field_storage_config->getConfigTarget()); + $bundles = $field_storage_config->getBundles(); + foreach ($bundles as $bundle) { + $config = \Drupal::configFactory() + ->getEditable("field.field.$entity_type.$bundle.$field_name"); + $data = $config->getRawData(); + if (isset($data['settings']['default_data'])) { + unset( + $data['settings']['default_data'], + $data['settings']['status'], + $data['settings']['open'], + $data['settings']['close'] + ); + $config->setData($data)->save(); + } + } + } +} + +/** + * Issue #3019987: Fatal error: Allowed memory size after updating to rc29. + */ +function webform_update_8160() { + $config = \Drupal::configFactory() + ->getEditable('webform.webform.example_computed_elements'); + if (!$config) { + return; + } + + // Remove nested calculation elements. + $elements = WebformYaml::decode($config->get('elements')); + if (isset($elements['calculation']) && isset($elements['calculation']['calculation'])) { + $path = drupal_get_path('module', 'webform') . '/modules/webform_examples/config/install/webform.webform.example_computed_elements.yml'; + $data = Yaml::decode(file_get_contents($path)); + $config->set('elements', $data['elements'])->save(); + } +} + +/** + * Issue #3026068: Ensure the webform submission has the langcode entity key set. + */ +function webform_update_8161() { + // Installations that were updated from 8.x-5.0-beta10 or older might have a + // unclean langcode definition, update it. First make sure that the langcode + // entity key is set, if not, set it and update the field definition. + $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); + $entity_type = $entity_definition_update_manager->getEntityType('webform_submission'); + if (!$entity_type->getKey('langcode')) { + $keys = $entity_type->getKeys(); + $keys['langcode'] = 'langcode'; + $entity_type->set('entity_keys', $keys); + $entity_definition_update_manager->updateEntityType($entity_type); + + $langcode_field = BaseFieldDefinition::create('language') + ->setName('langcode') + ->setTargetEntityTypeId('webform_submission') + ->setLabel(t('Language')) + ->setDescription(t('The submission language code.')); + \Drupal::entityDefinitionUpdateManager()->updateFieldStorageDefinition($langcode_field); + } +} + +/** + * Issue #3035054: Captcha challenge still visible after 'close time'. + */ +function webform_update_8162() { + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('third_party_settings.captcha', [ + 'replace_administration_mode' => TRUE, + ]) + ->save(); +} + +/** + * Issue #2902977: Provide tool for importing submissions from a CSV document. + */ +function webform_update_8163() { + _webform_update_admin_settings(); +} diff --git a/web/modules/webform/includes/webform.libraries.inc b/web/modules/webform/includes/webform.libraries.inc index e9e2b6e7d8..60b034bec6 100644 --- a/web/modules/webform/includes/webform.libraries.inc +++ b/web/modules/webform/includes/webform.libraries.inc @@ -9,15 +9,41 @@ * Implements hook_library_info_alter(). */ function webform_library_info_alter(&$libraries, $extension) { - $webform_libraries_modules = \Drupal::moduleHandler()->getImplementations('webform_libraries_info'); - $webform_libraries_modules[] = 'webform'; - // Only alter modules that declare webform libraries. // @see hook_webform_libraries_info() + $webform_libraries_modules = \Drupal::moduleHandler()->getImplementations('webform_libraries_info'); + $webform_libraries_modules[] = 'webform'; if (!in_array($extension, $webform_libraries_modules)) { return; } + // If chosen_lib.module is installed, then update the dependency. + if (\Drupal::moduleHandler()->moduleExists('chosen_lib')) { + if (isset($libraries['webform.element.chosen'])) { + $dependencies =& $libraries['webform.element.chosen']['dependencies']; + foreach ($dependencies as $index => $dependency) { + if ($dependency === 'webform/libraries.jquery.chosen') { + $dependencies[$index] = 'chosen_lib/chosen'; + $dependencies[] = 'chosen_lib/chosen.css'; + break; + } + } + } + } + + // If select2.module is installed, then update the dependency. + if (\Drupal::moduleHandler()->moduleExists('select2')) { + if (isset($libraries['webform.element.select2'])) { + $dependencies =& $libraries['webform.element.select2']['dependencies']; + foreach ($dependencies as $index => $dependency) { + if ($dependency === 'webform/libraries.jquery.select2') { + $dependencies[$index] = 'select2/select2'; + break; + } + } + } + } + /** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */ $libraries_manager = \Drupal::service('webform.libraries_manager'); @@ -30,6 +56,11 @@ function webform_library_info_alter(&$libraries, $extension) { continue; } + // Skip libraries installed by other modules. + if (isset($library['module'])) { + continue; + } + if (!empty($library['dependencies'])) { // Remove excluded libraries from dependencies. foreach ($library['dependencies'] as $dependency_index => $dependency_name) { diff --git a/web/modules/webform/includes/webform.options.inc b/web/modules/webform/includes/webform.options.inc index dd327eac86..f5b1f1da86 100644 --- a/web/modules/webform/includes/webform.options.inc +++ b/web/modules/webform/includes/webform.options.inc @@ -73,3 +73,16 @@ function webform_webform_options_languages_alter(array &$options, array $element } } } + +/** + * Implements hook_webform_options_WEBFORM_OPTIONS_ID_alter(). + */ +function webform_webform_options_translations_alter(array &$options, array $element = []) { + if (empty($options)) { + $languages = \Drupal::languageManager()->getLanguages(); + $options = []; + foreach ($languages as $language) { + $options[$language->getId()] = $language->getName(); + } + } +} diff --git a/web/modules/webform/includes/webform.theme.inc b/web/modules/webform/includes/webform.theme.inc index 0d84821cfd..8a65c7f5d6 100644 --- a/web/modules/webform/includes/webform.theme.inc +++ b/web/modules/webform/includes/webform.theme.inc @@ -2,12 +2,15 @@ /** * @file - * Theme hooks, preprocessor, and suggesttions. + * Theme hooks, preprocessor, and suggestions. */ use Drupal\file\Entity\File; use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Template\Attribute; +use Drupal\webform\Utility\WebformAccessibilityHelper; +use Drupal\webform\Utility\WebformElementHelper; /******************************************************************************/ // Theme hooks. @@ -67,6 +70,10 @@ function webform_theme() { 'render element' => 'elements', ], + 'webform_submission_form' => [ + 'render element' => 'form', + ], + 'webform_submission_navigation' => [ 'variables' => ['webform_submission' => NULL], ], @@ -89,7 +96,7 @@ function webform_theme() { ], 'webform_element_help' => [ - 'variables' => ['help' => NULL], + 'variables' => ['help' => NULL, 'help_title' => '', 'attributes' => []], ], 'webform_element_more' => [ @@ -112,6 +119,9 @@ function webform_theme() { 'variables' => ['element' => [], 'value' => NULL, 'webform_submission' => NULL, 'options' => [], 'file' => NULL], ], + 'webform_email_html' => [ + 'variables' => ['subject' => '', 'body' => '', 'webform_submission' => NULL, 'handler' => NULL], + ], 'webform_email_message_html' => [ 'variables' => ['message' => '', 'webform_submission' => NULL, 'handler' => NULL], ], @@ -119,6 +129,9 @@ function webform_theme() { 'variables' => ['message' => '', 'webform_submission' => NULL, 'handler' => NULL], ], + 'webform_html_editor_markup' => [ + 'variables' => ['markup' => NULL, 'allowed_tags' => []], + ], 'webform_horizontal_rule' => [ 'render element' => 'element', ], @@ -244,6 +257,10 @@ function webform_preprocess_menu_local_action(&$variables) { * @see \Drupal\webform\Plugin\WebformElement\OptionsBase */ function webform_preprocess_checkboxes(&$variables) { + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } + _webform_preprocess_options($variables); } @@ -253,41 +270,100 @@ function webform_preprocess_checkboxes(&$variables) { * @see \Drupal\webform\Plugin\WebformElement\OptionsBase */ function webform_preprocess_radios(&$variables) { + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } + _webform_preprocess_options($variables); } +/******************************************************************************/ +// Preprocess tables. +/******************************************************************************/ + /** - * Prepares variables for checkboxes and radios options templates. - * - * Below code must be called by template_preprocess_(radios|checkboxes) which - * reset the element's 'attributes'; + * Prepares variables for table templates. */ -function _webform_preprocess_options(&$variables) { - $element = $variables['element']; +function webform_preprocess_table(&$variables) { + // Add links to 'Translate' webform tab. + if (\Drupal::routeMatch()->getRouteName() === 'entity.webform.config_translation_overview') { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = \Drupal::routeMatch()->getParameter('webform'); + foreach ($variables['rows'] as &$row) { + // Check first cell. + if (!isset($row['cells'][0]['content']) + || !is_array($row['cells'][0]['content']) + || !isset($row['cells'][0]['content']['#markup'])) { + continue; + } - $variables['attributes']['class'][] = Html::getClass('js-webform-' . $element['#type']); + // Check last cell edit link. + if (!isset($row['cells'][1]['content']) + || !is_array($row['cells'][1]['content']) + || !isset($row['cells'][1]['content']['#links']) + || !is_array($row['cells'][1]['content']['#links']) + || !isset($row['cells'][1]['content']['#links']['edit'])) { + continue; + } - if (!empty($element['#options_display'])) { - $variables['attributes']['class'][] = Html::getClass('webform-options-display-' . $element['#options_display']); - $variables['#attached']['library'][] = 'webform/webform.element.options'; + // Get language from edit link. + $route_parameters = $row['cells'][1]['content']['#links']['edit']['url']->getRouteParameters(); + $langcode = (isset($route_parameters['langcode'])) ? $route_parameters['langcode'] : NULL; + $language = \Drupal::languageManager()->getLanguage($langcode); + + // Convert first to link. + $row['cells'][0]['content'] = [ + '#type' => 'link', + '#url' => $webform->toUrl('canonical', ['language' => $language]), + '#title' => $row['cells'][0]['content'], + ]; + } } } +/******************************************************************************/ +// Preprocess containers. +/******************************************************************************/ + +/** + * Prepares variables for datetime form element templates. + */ +function webform_preprocess_datetime_form(&$variables) { + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } + + // Add .container-inline to datetime form wrapper which is missing from the + // stable base theme. + // @see core/themes/classy/templates/form/datetime-form.html.twig + // @see core/themes/stable/templates/form/datetime-form.html.twig + $variables['attributes']['class'][] = 'container-inline'; +} + /** * Prepares variables for details element templates. */ function webform_preprocess_details(&$variables) { - // Move #description to #help for webform admin routes. - _webform_preprocess_description_help($variables); + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } - // Add (read) more to #description. - _webform_preprocess_details_description_more($variables); + // Setup description, help, and more. + _webform_preprocess_element($variables); $element = &$variables['element']; - // Restructure the details's title to include #help. - if (isset($variables['title'])) { - _webform_preprocess_help_title($variables['title'], $element); + // Hide details title. + if (isset($element['#title_display']) && $element['#title_display'] === 'invisible') { + $variables['title'] = WebformAccessibilityHelper::buildVisuallyHidden($variables['title']); + } + + // Remove invalid 'required' and 'aria-required' attributes from details. + if (isset($element['#webform_key'])) { + unset( + $variables['attributes']['required'], + $variables['attributes']['aria-required'] + ); } } @@ -295,227 +371,99 @@ function webform_preprocess_details(&$variables) { * Prepares variables for fieldset element templates. */ function webform_preprocess_fieldset(&$variables) { - // Move #description to #help for webform admin routes. - _webform_preprocess_description_help($variables); + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } - // Add (read) more to #description. - _webform_preprocess_form_element_description_more($variables); + // Setup description, help, and more. + _webform_preprocess_element($variables, ['legend', 'title']); $element = &$variables['element']; + // If the description is displayed 'before' we need to move it to the + // fieldset's prefix. + // @see fieldset.html.twig + if (isset($element['#description_display']) && $element['#description_display'] === 'before' && !empty($variables['description']['content'])) { + if (isset($variables['prefix'])) { + if (is_array($variables['prefix'])) { + $variables['prefix'] = ['prefix' => $variables['prefix']]; + } + else { + $variables['prefix'] = ['prefix' => ['#markup' => $variables['prefix']]]; + } + } + else { + $variables['prefix'] = []; + } + $variables['prefix']['description'] = $variables['description']['content']; + unset($variables['description']['content']); + } + // Apply inline title defined by radios, checkboxes, and buttons. // @see \Drupal\webform\Plugin\WebformElement\OptionsBase::prepare if (isset($element['#_title_display'])) { $variables['attributes']['class'][] = 'webform-fieldset--title-inline'; } - // Restructure the fieldset's legend title to include #help. - if (isset($variables['legend']['title'])) { - _webform_preprocess_help_title($variables['legend']['title'], $element); - } - - // Add .js-webform-form-composite class to be used #states API. + // Add .js-webform-form-type-* class to be used JavaScript and #states API. + // @see js/webform.element.location.geocomplete.js // @see js/webform.states.js - if (isset($element['#type']) && in_array($element['#type'], ['checkboxes', 'radios'])) { - $variables['attributes']['class'][] = 'js-webform-type-' . $element['#type']; - $variables['attributes']['class'][] = 'webform-type-' . $element['#type']; + if (isset($element['#type'])) { + $variables['attributes']['class'][] = 'js-webform-type-' . Html::getClass($element['#type']); + $variables['attributes']['class'][] = 'webform-type-' . Html::getClass($element['#type']); } -} - -/** - * Prepares variables for webform section element templates. - */ -function webform_preprocess_webform_section(&$variables) { - // Move #description to #help for webform admin routes. - _webform_preprocess_description_help($variables); - $element = &$variables['element']; - - // Restructure the section's title to include #help. - if (isset($variables['title'])) { - _webform_preprocess_help_title($variables['title'], $element); - } -} - -/** - * Prepares variables for datetime form wrapper templates. - */ -function webform_preprocess_datetime_wrapper(&$variables) { - // Move #description to #help for webform admin routes. - _webform_preprocess_description_help($variables); - - // Add (read) more to #description. - _webform_preprocess_form_element_description_more($variables); - - $element = &$variables['element']; - - // Restructure the title to include #help. - if (isset($variables['title'])) { - _webform_preprocess_help_title($variables['title'], $element); + // Remove invalid 'required' and 'aria-required' attributes from fieldset. + if (isset($element['#webform_key'])) { + unset( + $variables['attributes']['required'], + $variables['attributes']['aria-required'] + ); } } -/** - * Prepares variables for form label templates. - */ -function webform_preprocess_form_element_label(&$variables) { - $element = &$variables['element']; - - // Unset 'for' attribute. - // @see \Drupal\webform\Element\WebformOtherBase::processWebformOther - if (isset($element['#attributes']['for']) && $element['#attributes']['for'] === FALSE) { - unset($variables['attributes']['for']); - } - - // Restructure the label's title to include #help. - if (!empty($element['#help'])) { - _webform_preprocess_help_title($variables['title'], $element); - } -} +/******************************************************************************/ +// Preprocess form element. +/******************************************************************************/ /** * Prepares variables for form element templates. */ function webform_preprocess_form_element(&$variables) { - $element = &$variables['element']; - - // Move #description to #help for webform admin routes. - _webform_preprocess_description_help($variables); - - // Add (read) more to #description. - _webform_preprocess_form_element_description_more($variables); - - // Add #help and #_title_display to label. - // Note: #_title_display is used to track inline titles. - // @see \Drupal\webform\Plugin\WebformElementBase::prepare - $variables['label'] += array_intersect_key($element, array_flip(['#help', '#_title_display'])); -} - -/** - * Prepares #description and #help properties for form element templates. - */ -function _webform_preprocess_description_help(&$variables) { - $element = &$variables['element']; - // Move #description to #help for webform admin routes. - if (\Drupal::config('webform.settings')->get('ui.description_help') - && \Drupal::service('webform.request')->isWebformAdminRoute() - && \Drupal::routeMatch()->getRouteName() != 'webform.contribute.settings' - && isset($element['#description']) - && !isset($element['#help']) - && !empty($element['#title']) - && !(isset($element['#title_display']) && in_array($element['#title_display'], ['attribute', 'invisible'])) - ) { - $element['#help'] = $element['#description']; - unset($variables['description']); - if (is_array($element['#help'])) { - $element['#help'] = \Drupal::service('renderer')->render($element['#help']); - } - } -} - -/** - * Append #help to title variable. - */ -function _webform_preprocess_help_title(&$variables, array &$element) { - if (empty($element['#help'])) { + if (!WebformElementHelper::isWebformElement($variables['element'])) { return; } - $variables = [ - 'title' => (is_array($variables)) ? $variables : ['#markup' => $variables], - 'help' => [ - '#type' => 'webform_help', - ] + array_intersect_key($element, array_flip(['#help'])), - ]; + // Setup description, help, and more. + _webform_preprocess_element($variables); - // Get #title_display and move help before title for 'inline' titles. - if (isset($element['#_title_display'])) { - // #_title_display is set - // via \Drupal\webform\Plugin\WebformElementBase::prepare. - $title_display = $element['#_title_display']; - } - elseif (isset($element['#title_display'])) { - $title_display = $element['#title_display']; - } - else { - $title_display = NULL; - } + $element = &$variables['element']; - if ($title_display == 'inline') { - $variables['title']['#weight'] = 0; - $variables['help']['#weight'] = -1; - } + // Add #help, #help_attributes, and #_title_display to label. + // Note: #_title_display is used to track inline titles. + // @see \Drupal\webform\Plugin\WebformElementBase::prepare + $variables['label'] += array_intersect_key($element, array_flip(['#help', '#help_title', '#help_attributes', '#_title_display'])); } /** - * Prepares #more property for form element template. - * - * @see template_preprocess_form_element() - * @see form-element.html.twig - * @see template_preprocess_datetime_wrapper() - * @see datetime-wrapper.html.twig + * Prepares variables for form label templates. */ -function _webform_preprocess_form_element_description_more(&$variables) { +function webform_preprocess_form_element_label(&$variables) { $element = &$variables['element']; - if (empty($element['#more'])) { - return; - } - - // Make sure $variables['description']['content'] is a render array. - if (!isset($variables['description'])) { - $variables['description_display'] = $element['#description_display']; - $description_attributes = []; - if (!empty($element['#id'])) { - $description_attributes['id'] = $element['#id'] . '--description'; - } - $variables['description']['attributes'] = new Attribute($description_attributes); - $variables['description']['content'] = []; - } - // Move content to content.description and convert description to - // render array. - $variables['description']['content'] = [ - 'description' => is_array($variables['description']['content']) ? $variables['description']['content'] : ['#markup' => $variables['description']['content']], - ]; + // Restructure the label's title to include #help. + _webform_preprocess_help_title($variables['title'], $element); - // If description display is invisible hie the description. - if (isset($variables['description_display']) && $variables['description_display'] == 'invisible') { - $variables['description']['content']['description']['#prefix'] = '<div class="visually-hidden">'; - $variables['description']['content']['description']['#suffix'] = '</div>'; - $variables['description_display'] = 'after'; + // Remove label 'for' attribute. + if (!empty($element['#attributes']['webform-remove-for-attribute'])) { + unset($variables['attributes']['webform-remove-for-attribute']); + unset($variables['attributes']['for']); } - - // Add more element. - $variables['description']['content']['more'] = [ - '#type' => 'webform_more', - '#attributes' => (!empty($element['#id'])) ? ['id' => $element['#id'] . '--more'] : [], - ] + array_intersect_key($element, array_flip(['#more', '#more_title'])); } -/** - * Prepares #more property for details template. - * - * @see template_preprocess_details() - * @see details.html.twig - */ -function _webform_preprocess_details_description_more(&$variables) { - $element = &$variables['element']; - if (empty($element['#more'])) { - return; - } - - // Convert description to render array. - $variables['description'] = [ - 'description' => is_array($variables['description']) ? $variables['description'] : ['#markup' => $variables['description']], - ]; - - // Add more element. - $variables['description']['more'] = [ - '#type' => 'webform_more', - '#attributes' => (!empty($element['#id'])) ? ['id' => $element['#id'] . '--more'] : [], - ] + array_intersect_key($element, array_flip(['#more', '#more_title'])); - -} +/******************************************************************************/ +// Preprocess file/image elements. +/******************************************************************************/ /** * Prepares variables for file managed file templates. @@ -524,6 +472,10 @@ function _webform_preprocess_details_description_more(&$variables) { * @see template_preprocess_file_managed_file() */ function webform_preprocess_file_managed_file(&$variables) { + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } + $element = &$variables['element']; if (empty($element['#button'])) { return; @@ -570,6 +522,16 @@ function webform_preprocess_file_managed_file(&$variables) { $element['#attached']['library'][] = 'webform/webform.element.file.button'; } +/** + * Prepares variables for file upload help text templates. + */ +function webform_preprocess_file_upload_help(&$variables) { + $upload_validators = $variables['upload_validators']; + if (isset($upload_validators['webform_file_limit']) && $upload_validators['webform_file_limit'][0]) { + $variables['descriptions'][] = t('@size limit per form.', ['@size' => format_size($upload_validators['webform_file_limit'][0])]); + } +} + /** * Prepares variables for file link templates. * @@ -598,6 +560,226 @@ function webform_preprocess_image(&$variables) { } } +/******************************************************************************/ +// Preprocess webform specific elements. +/******************************************************************************/ + +/** + * Prepares variables for webform section element templates. + */ +function webform_preprocess_webform_section(&$variables) { + // Setup description, help, and more. + _webform_preprocess_element($variables); +} + +/******************************************************************************/ +// Preprocess helpers. +/******************************************************************************/ + +/** + * Prepares variables for checkboxes and radios options templates. + * + * Below code must be called by template_preprocess_(radios|checkboxes) which + * reset the element's 'attributes'; + */ +function _webform_preprocess_options(array &$variables) { + $element =& $variables['element']; + + $variables['attributes']['class'][] = Html::getClass('js-webform-' . $element['#type']); + + if (!empty($element['#options_display'])) { + $variables['attributes']['class'][] = Html::getClass('webform-options-display-' . $element['#options_display']); + $variables['#attached']['library'][] = 'webform/webform.element.options'; + } +} + +/** + * Prepares webform element description, help, and more templates. + * + * @see template_preprocess_form_element() + * @see core/modules/system/templates/form-element.html.twig + * @see template_preprocess_details() + * @see /core/modules/system/templates/details.html.twig + * @see template_preprocess_fieldset() + * @see /core/modules/system/templates/fieldset.html.twig + * @see template_preprocess_webform_section() + * @see /webform/templates/webform-section.html.twig + */ +function _webform_preprocess_element(array &$variables, $title_parents = ['title']) { + $element =& $variables['element']; + $type = $element['#type']; + + // Fix details 'description' property which does not have description.content. + // @see template_preprocess_details + // @see Issue #2896169: Details elements have incorrect aria-describedby attributes + if (!empty($element['#description'])) { + // Convert the current description to simple #markup. + if (is_array($element['#description'])) { + // Make a copy of the element's #description so that it can be rendered + // without affecting the element's #description. + $element_description = $element['#description']; + $description = ['#markup' => \Drupal::service('renderer')->render($element_description)]; + } + else { + $description = ['#markup' => $element['#description']]; + } + + if ($type === 'details') { + $description_attributes = []; + if (!empty($element['#id'])) { + $description_attributes['id'] = $element['#id'] . '--description'; + } + $variables['description'] = []; + $variables['description']['content'] = [ + '#type' => 'container', + '#attributes' => new Attribute($description_attributes), + ] + $description; + } + else { + // Wrap description in a container. + $variables['description']['content'] = [ + '#type' => 'container', + '#attributes' => $variables['description']['attributes'], + ] + $description; + $variables['description']['attributes'] = new Attribute(); + } + + $variables['description']['content']['#attributes']->addClass('webform-element-description'); + + // Handle invisible descriptions. + if (isset($element['#description_display']) && $element['#description_display'] === 'invisible') { + $variables['description']['content']['#attributes']->addClass('visually-hidden'); + $variables['description_display'] = 'after'; + } + + // Nest description content so that we can a more link + // below the description. + $variables['description']['content'] = [ + 'description' => $variables['description']['content'], + ]; + } + elseif (isset($variables['description']) && empty($variables['description'])) { + // Unset $variable['description'] which can be set to NULL or empty string. + // This allows $variable['description'] to be converted to render array. + // @see template_preprocess_details() + // @see template_preprocess_form_element() + unset($variables['description']); + } + + $title =& NestedArray::getValue($variables, $title_parents); + + // Move #description to #help for webform admin routes. + _webform_preprocess_description_help($variables); + + // Add (read) more to #description. + _webform_preprocess_form_element_description_more($variables); + + // Add help to title (aka label). + _webform_preprocess_help_title($title, $element); +} + +/** + * Prepares #description and #help properties for form element templates. + */ +function _webform_preprocess_description_help(array &$variables) { + $element = &$variables['element']; + // Move #description to #help for webform admin routes. + if (\Drupal::config('webform.settings')->get('ui.description_help') + && \Drupal::service('webform.request')->isWebformAdminRoute() + && \Drupal::routeMatch()->getRouteName() != 'webform.contribute.settings' + && !isset($element['#help']) + && !empty($element['#title']) && (empty($element['#title_display']) || !in_array($element['#title_display'], ['attribute', 'invisible'])) + && !empty($element['#description']) && (empty($element['#description_display']) || !in_array($element['#description_display'], ['invisible'])) + ) { + // Render the description. + $description = (is_array($element['#description'])) ? \Drupal::service('renderer')->render($element['#description']) : $element['#description']; + // Replace breaks in admin tooltips with horizontal rules. + $description = str_replace('<br /><br />', '<hr />', $description); + $element['#help'] = ['#markup' => $description]; + + // We must still render the description as visually hidden because the input + // has an 'aria-describedby' attribute pointing to the description's id. + $variables['description_display'] = 'after'; + $variables['description']['content']['description']['#attributes']->addClass('visually-hidden'); + + // Remove all links from the #description since it will be .visually-hidden + // and unreachable via tabbing. + if (isset($variables['description']['content']['description']['#markup'])) { + $variables['description']['content']['description']['#markup'] = strip_tags($variables['description']['content']['description']['#markup']); + } + } +} + +/** + * Append #help to title variable. + */ +function _webform_preprocess_help_title(&$variables, array &$element) { + if (empty($variables) || empty($element['#help'])) { + return; + } + + // Default #help_title to element's #title. + if (empty($element['#help_title']) && !empty($element['#title'])) { + $element['#help_title'] = $element['#title']; + } + + $variables = [ + 'title' => (is_array($variables)) ? $variables : ['#markup' => $variables], + 'help' => [ + '#type' => 'webform_help', + ] + array_intersect_key($element, array_flip(['#help', '#help_title'])), + ]; + + // Add help attributes. + if (isset($element['#help_attributes'])) { + $variables['help']['#attributes'] = $element['#help_attributes']; + } + + // Get #title_display and move help before title for 'inline' titles. + if (isset($element['#_title_display'])) { + // #_title_display is set via WebformElementBase::prepare. + // @see \Drupal\webform\Plugin\WebformElementBase::prepare. + $title_display = $element['#_title_display']; + } + elseif (isset($element['#title_display'])) { + $title_display = $element['#title_display']; + } + else { + $title_display = NULL; + } + + if ($title_display === 'inline') { + $variables['title']['#weight'] = 0; + $variables['help']['#weight'] = -1; + } +} + +/** + * Prepares #more property for form element template. + * + * @see template_preprocess_form_element() + * @see form-element.html.twig + * @see template_preprocess_datetime_wrapper() + * @see datetime-wrapper.html.twig + */ +function _webform_preprocess_form_element_description_more(array &$variables) { + $element = &$variables['element']; + if (empty($element['#more'])) { + return; + } + + // Make sure description is displayed. + if (!isset($variables['description_display'])) { + $variables['description_display'] = 'after'; + } + + // Add more element. + $variables['description']['content']['more'] = [ + '#type' => 'webform_more', + '#attributes' => (!empty($element['#id'])) ? ['id' => $element['#id'] . '--more'] : [], + ] + array_intersect_key($element, array_flip(['#more', '#more_title'])); +} + /******************************************************************************/ // Theme suggestions. /******************************************************************************/ @@ -619,6 +801,9 @@ function _webform_theme_suggestions(array $variables, $hook) { if ($hook == 'webform' && isset($variables['element']) && isset($variables['element']['#webform_id'])) { $suggestions[] = $hook . '__' . $variables['element']['#webform_id']; } + elseif ($hook == 'webform_submission_form' && isset($variables['form']) && isset($variables['form']['#webform_id'])) { + $suggestions[] = $hook . '__' . $variables['form']['#webform_id']; + } elseif (strpos($hook, 'webform_element_base_') === 0 || strpos($hook, 'webform_container_base_') === 0) { $element = $variables['element']; @@ -634,7 +819,7 @@ function _webform_theme_suggestions(array $variables, $hook) { $webform = NULL; $webform_submission = NULL; $sanitized_view_mode = NULL; - if (isset($variables['elements']) || isset($variables['elements']['#webform_submission'])) { + if (isset($variables['elements']) && isset($variables['elements']['#webform_submission'])) { /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $variables['elements']['#webform_submission']; $webform = $webform_submission->getWebform(); @@ -743,6 +928,13 @@ function webform_theme_suggestions_webform_submission(array $variables) { return _webform_theme_suggestions($variables, 'webform_submission'); } +/** + * Implements hook_theme_suggestions_HOOK(). + */ +function webform_theme_suggestions_webform_submission_form(array $variables) { + return _webform_theme_suggestions($variables, 'webform_submission_form'); +} + /** * Implements hook_theme_suggestions_HOOK(). */ @@ -778,6 +970,13 @@ function webform_theme_suggestions_webform_container_base_text(array $variables) return _webform_theme_suggestions($variables, 'webform_container_base_text'); } +/** + * Implements hook_theme_suggestions_HOOK(). + */ +function webform_theme_suggestions_webform_email_html(array $variables) { + return _webform_theme_suggestions($variables, 'webform_email_html'); +} + /** * Implements hook_theme_suggestions_HOOK(). */ diff --git a/web/modules/webform/includes/webform.theme.template.inc b/web/modules/webform/includes/webform.theme.template.inc index 9f22e4d6c2..b94eb8ed1d 100644 --- a/web/modules/webform/includes/webform.theme.template.inc +++ b/web/modules/webform/includes/webform.theme.template.inc @@ -5,6 +5,7 @@ * Preprocessors and helper functions to make theming easier. */ +use Drupal\Component\Utility\Html; use Drupal\Core\Link; use Drupal\Core\Render\Element; use Drupal\Core\Render\Markup; @@ -15,6 +16,7 @@ use Drupal\webform\Element\WebformCodeMirror; use Drupal\webform\Element\WebformHtmlEditor; use Drupal\webform\WebformMessageManagerInterface; +use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\Utility\WebformElementHelper; @@ -169,7 +171,7 @@ function template_preprocess_webform_confirmation(array &$variables) { } // Set message. - $variables['message'] = $message_manager->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION); + $variables['message'] = $message_manager->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); // Set attributes. $variables['attributes'] = new Attribute($settings['confirmation_attributes']); @@ -232,6 +234,11 @@ function template_preprocess_webform_submission_navigation(array &$variables) { /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $variables['webform_submission']; + $webform = $webform_submission->getWebform(); + + // Webform id and title for context. + $variables['webform_id'] = $webform->id(); + $variables['webform_title'] = $webform->label(); // Get the route name, parameters, and source entity for the current page. // This ensures that the user stays within their current context as they are @@ -276,9 +283,15 @@ function template_preprocess_webform_submission_navigation(array &$variables) { */ function template_preprocess_webform_submission(array &$variables) { $variables['view_mode'] = $variables['elements']['#view_mode']; - $variables['webform_submission'] = $variables['elements']['#webform_submission']; - $variables['webform'] = $variables['elements']['#webform_submission']->getWebform(); + $variables['navigation'] = $variables['elements']['navigation']; + $variables['information'] = $variables['elements']['information']; + $variables['submission'] = $variables['elements']['submission']; + + $variables['webform_submission'] = $variables['elements']['#webform_submission']; + if ($variables['webform_submission'] instanceof WebformSubmissionInterface) { + $variables['webform'] = $variables['webform_submission']->getWebform(); + } } /** @@ -295,7 +308,6 @@ function template_preprocess_webform_submission_information(array &$variables) { $webform_submission = $variables['webform_submission']; $webform = $webform_submission->getWebform(); - $variables['webform_id'] = $webform->id(); $variables['serial'] = $webform_submission->serial(); $variables['sid'] = $webform_submission->id(); $variables['uuid'] = $webform_submission->uuid(); @@ -317,7 +329,7 @@ function template_preprocess_webform_submission_information(array &$variables) { $variables['language'] = isset($languages[$langcode]) ? $languages[$langcode]->getName() : $langcode; if ($source_url = $webform_submission->getSourceUrl()) { - $variables['uri'] = Link::fromTextAndUrl($source_url->setAbsolute(FALSE)->toString(), $source_url);; + $variables['uri'] = Link::fromTextAndUrl($source_url->setAbsolute(FALSE)->toString(), $source_url); } if ($webform->getSetting('token_update')) { @@ -345,6 +357,22 @@ function template_preprocess_webform_submission_information(array &$variables) { $variables['submissions_view'] = TRUE; } } + + if ($webform_submission->access('delete')) { + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + $base_route_name = (strpos(\Drupal::routeMatch()->getRouteName(), 'webform.user.submission') !== FALSE) ? 'webform.user.submission.delete' : 'webform_submission.delete_form'; + $url = $request_handler->getUrl($webform_submission, $source_entity, $base_route_name); + + $variables['delete'] = [ + '#type' => 'link', + '#title' => t('Delete submission'), + '#url' => $url, + '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['button', 'button--danger']), + ]; + + WebformDialogHelper::attachLibraries($variables['delete']); + } } /** @@ -390,19 +418,21 @@ function template_preprocess_webform_element_base_html(array &$variables) { if (empty($variables['options']['email'])) { $type = $element['#type']; + $attributes = (isset($element['#format_attributes'])) ? $element['#format_attributes'] : []; + $attributes += ['class' => []]; + // Use wrapper attributes for the id instead of #id, + // this stops the <label> from having a 'for' attribute. + $attributes += [ + 'id' => $element['#webform_id'], + ]; + $attributes['class'][] = 'webform-element'; + $attributes['class'][] = 'webform-element-type-' . str_replace('_', '-', $type); + $variables['item'] = [ '#type' => 'item', '#title' => $variables['title'], '#name' => $element['#webform_key'], - // Use wrapper attributes for the id instead of #id, - // this stops the <label> from having a 'for' attribute. - '#wrapper_attributes' => [ - 'id' => $element['#webform_id'], - 'class' => [ - 'webform-element', - 'webform-element-type-' . str_replace('_', '-', $type), - ], - ], + '#wrapper_attributes' => $attributes, ]; if (is_array($variables['value'])) { $variables['item']['value'] = $variables['value']; @@ -473,8 +503,8 @@ function _template_progress_webform_set_title(array &$variables, $strip_tags = F * - current_page: The current wizard page. */ function template_preprocess_webform_progress(array &$variables) { - /** @var \Drupal\webform\WebformLibrariesManagerInterface $libaries_manager */ - $libaries_manager = \Drupal::service('webform.libraries_manager'); + /** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */ + $libraries_manager = \Drupal::service('webform.libraries_manager'); /** @var \Drupal\webform\WebformInterface $webform */ $webform = $variables['webform']; @@ -491,7 +521,7 @@ function template_preprocess_webform_progress(array &$variables) { if ($webform->getSetting('wizard_progress_bar')) { $variables['bar'] = [ - '#theme' => ($libaries_manager->isIncluded('progress-tracker')) ? 'webform_progress_tracker' : 'webform_progress_bar', + '#theme' => ($libraries_manager->isIncluded('progress-tracker')) ? 'webform_progress_tracker' : 'webform_progress_bar', '#webform' => $webform, '#current_page' => $current_page, '#operation' => $operation, @@ -604,6 +634,23 @@ function template_preprocess_webform_message(array &$variables) { } } +/** + * Prepares variables for Webform HTML Editor markup templates. + * + * Default template: webform-html-editor-markup.html.twig. + * + * @param array $variables + * An associative array containing: + * - markup: HTML markup. + * - allowed_tags: Allowed tags. + */ +function template_preprocess_webform_html_editor_markup(array &$variables) { + $variables['content'] = [ + '#markup' => $variables['markup'], + '#allowed_tags' => $variables['allowed_tags'], + ]; +} + /** * Prepares variables for Webform horizontal rule templates. * @@ -789,19 +836,24 @@ function template_preprocess_webform_composite_telephone(array &$variables) { * An associative array containing the following key: * - element: The webform element. * - help: The help content. + * - attributes: The help attributes. */ function template_preprocess_webform_element_help(array &$variables) { - $variables['help_icon'] = [ - '#type' => 'link', - '#title' => '?', - '#url' => Url::fromRoute('<none>', [], ['fragment' => 'help']), - '#attributes' => [ - 'title' => $variables['help'], - // Add 'data-webform-help' attribute which support HTML markup. - 'data-webform-help' => WebformHtmlEditor::stripTags($variables['help']), - 'class' => ['webform-element-help'], - ], - ]; + $attributes = (isset($variables['attributes'])) ? $variables['attributes'] : []; + $attributes['class'][] = 'webform-element-help'; + $attributes['role'] = 'tooltip'; + $attributes['tabindex'] = '0'; + + $content = (is_array($variables['help'])) ? \Drupal::service('renderer')->render($variables['help']) : $variables['help']; + + $help = ''; + if (!empty($variables['help_title'])) { + $help .= '<div class="webform-element-help--title">' . WebformHtmlEditor::stripTags($variables['help_title']) . '</div>'; + } + $help .= '<div class="webform-element-help--content">' . WebformHtmlEditor::stripTags($content) . '</div>'; + $attributes['data-webform-help'] = $help; + + $variables['attributes'] = new Attribute($attributes); } /** @@ -826,6 +878,16 @@ function template_preprocess_webform_element_more(array &$variables) { } $variables['attributes'] = new Attribute($variables['attributes']); + + // Make sure there is a unique id. + if (empty($variables['id'])) { + $variables['id'] = Html::getUniqueId('webform-element-more'); + } + + // Make sure attributes id is set. + if (!isset($variables['attributes']['id'])) { + $variables['attributes']['id'] = $variables['id']; + } } /** @@ -909,8 +971,11 @@ function template_preprocess_webform_element_image_file(array &$variables) { $uri = $file->getFileUri(); $url = Url::fromUri(file_create_url($uri)); + $extension = pathinfo($uri, PATHINFO_EXTENSION); + $is_image = in_array($extension, ['gif', 'png', 'jpg', 'jpeg']); + // Build image. - if (\Drupal::moduleHandler()->moduleExists('image') && $style_name && ImageStyle::load($style_name)) { + if ($is_image && \Drupal::moduleHandler()->moduleExists('image') && $style_name && ImageStyle::load($style_name)) { $variables['image'] = [ '#theme' => 'image_style', '#style_name' => $variables['style_name'], diff --git a/web/modules/webform/includes/webform.translation.inc b/web/modules/webform/includes/webform.translation.inc index 4adcebaaf5..03894924ac 100644 --- a/web/modules/webform/includes/webform.translation.inc +++ b/web/modules/webform/includes/webform.translation.inc @@ -3,6 +3,8 @@ /** * @file * Webform module translation hooks. + * + * @see webform_preprocess_table() */ use Drupal\Core\Config\Entity\ConfigEntityInterface; @@ -13,13 +15,12 @@ use Drupal\Core\Serialization\Yaml; use Drupal\webform\Utility\WebformElementHelper; - /** * Implements hook_form_FORM_ID_alter(). */ function webform_form_locale_translate_edit_form_alter(&$form, FormStateInterface $form_state) { // Don't allow YAML to be validated using locale string translation. - foreach (\Drupal\Core\Render\Element::children($form['strings']) as $key) { + foreach (Element::children($form['strings']) as $key) { $element =& $form['strings'][$key]; if ($element['original'] && !empty($element['original']['#plain_text']) @@ -58,11 +59,11 @@ function webform_form_config_translation_add_form_alter(&$form, FormStateInterfa /** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */ $translation_manager = \Drupal::service('webform.translation_manager'); - $translation_langcode = $form_state->get('config_translation_language')->getId();; + $translation_langcode = $form_state->get('config_translation_language')->getId(); $source_elements = $translation_manager->getSourceElements($webform); $translation_elements = $translation_manager->getTranslationElements($webform, $translation_langcode); - $source_value = trim(Yaml::encode($source_elements)); - $translation_value = trim(Yaml::encode($translation_elements)); + $source_value = WebformYaml::encode($source_elements); + $translation_value = WebformYaml::encode($translation_elements); _webform_form_config_translate_add_form_alter_yaml_element($config_element['elements'], $source_value, $translation_value); @@ -80,6 +81,10 @@ function webform_form_config_translation_add_form_alter(&$form, FormStateInterfa * Validate callback; Validates and cleanups webform elements. */ function _webform_form_config_translate_add_form_validate(&$form, FormStateInterface $form_state) { + if ($form_state::hasAnyErrors()) { + return; + } + $values = $form_state->getValues(); $config_name = $form_state->get('webform_config_name'); @@ -172,26 +177,32 @@ function _webform_form_config_translate_add_form_alter_yaml_element(array &$elem */ function webform_lingotek_config_entity_document_upload(array &$source_data, ConfigEntityInterface &$entity, &$url) { switch ($entity->getEntityTypeId()) { + case 'field_config': + // Convert webform default data YAML string to an associative array. + /** @var \Drupal\field\Entity\FieldConfig $entity */ + if ($entity->getFieldStorageDefinition()->getType() === 'webform') { + foreach ($source_data as &$field_settings) { + foreach ($field_settings as $setting_name => $setting_value) { + if (preg_match('/\.default_data$/', $setting_name)) { + $field_settings[$setting_name] = Yaml::decode($field_settings[$setting_name]); + } + } + } + _webform_lingotek_encode_tokens($field_settings); + } + break; + case 'webform'; /** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */ $translation_manager = \Drupal::service('webform.translation_manager'); // Replace elements with just the translatable properties - // (i.e. #title, #description, #options, etc...) so that Lingotek's + // (i.e. #title, #description, #options, etc…) so that Lingotek's // translation services can correctly translate each element. $translation_elements = $translation_manager->getTranslationElements($entity, $entity->language()->getId()); $source_data['elements'] = $translation_elements; - // Encode all [tokens]. - $yaml = Yaml::encode($source_data); - $yaml = preg_replace_callback( - '/\[([a-z][^]]+)\]/', - function ($matches) { - return '[***' . base64_encode($matches[1]) . '***]'; - }, - $yaml - ); - $source_data = Yaml::decode($yaml); + _webform_lingotek_encode_tokens($source_data); break; case 'webform_options'; @@ -206,17 +217,23 @@ function ($matches) { */ function webform_lingotek_config_entity_translation_presave(ConfigEntityInterface &$translation, $langcode, &$data) { switch ($translation->getEntityTypeId()) { + case 'field_config': + // Convert webform default data associative array back to YAML string. + /** @var \Drupal\field\Entity\FieldConfig $translation */ + if ($translation->getFieldStorageDefinition()->getType() === 'webform') { + foreach ($data as &$field_settings) { + _webform_lingotek_encode_tokens($field_settings); + foreach ($field_settings as $setting_name => $setting_value) { + if (preg_match('/\.default_data$/', $setting_name)) { + $field_settings[$setting_name] = $field_settings[$setting_name] ? Yaml::encode($field_settings[$setting_name]) : ''; + } + } + } + } + break; + case 'webform'; - // Decode all [tokens]. - $yaml = Yaml::encode($data); - $yaml = preg_replace_callback( - '/\[\*\*\*([^]]+)\*\*\*\]/', - function ($matches) { - return '[' . base64_decode($matches[1]) . ']'; - }, - $yaml - ); - $data = Yaml::decode($yaml); + _webform_lingotek_decode_tokens($data); /** @var \Drupal\webform\WebformInterface $translation */ $translation->setElements($data['elements']); @@ -233,17 +250,75 @@ function ($matches) { } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_lingotek_config_object_document_upload(). */ -function webform_form_lingotek_config_management_alter(&$form, FormStateInterface $form_state, $form_id) { - $entity_type = $form['filters']['wrapper']['bundle']['#default_value']; - if (!in_array($entity_type, ['webform', 'webform_options'])) { +function webform_lingotek_config_object_document_upload(array &$data, $config_name) { + if ($config_name !== 'webform.settings') { return; } - $entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple(array_keys($form['table']['#options'])); - foreach ($form['table']['#options'] as $entity_id => &$option) { - $entity = $entities[$entity_id]; - $option['title'] = $entity->toLink($entity->label() . ' ' . $entity_type); + $data['webform.settings']['test.types'] = Yaml::decode($data['webform.settings']['test.types']); + $data['webform.settings']['test.names'] = Yaml::decode($data['webform.settings']['test.names']); + + _webform_lingotek_encode_tokens($data); +} + +/** + * Implements hook_lingotek_config_object_translation_presave(). + */ +function webform_lingotek_config_object_translation_presave(array &$data, $config_name) { + if ($config_name !== 'webform.settings') { + return; } + + _webform_lingotek_decode_tokens($data); + + $data['webform.settings']['test.types'] = Yaml::encode($data['webform.settings']['test.types']); + $data['webform.settings']['test.names'] = Yaml::encode($data['webform.settings']['test.names']); +} + +/******************************************************************************/ +// Lingotek decode/encode token functions. +/******************************************************************************/ + +/** + * Encode all tokens so that they won't be translated. + * + * @param array $data + * An array of data. + */ +function _webform_lingotek_encode_tokens(array &$data) { + $yaml = Yaml::encode($data); + $yaml = preg_replace_callback( + '/\[([a-z][^]]+)\]/', + function ($matches) { + // Encode all token characters to HTML entities. + // @see https://stackoverflow.com/questions/6720826/php-convert-all-characters-to-html-entities. + $replacement = mb_encode_numericentity($matches[1], [0x000000, 0x10ffff, 0, 0xffffff], 'UTF-8'); + return "[$replacement]"; + }, + $yaml + ); + $data = Yaml::decode($yaml); +} + +/** + * Decode all tokens after string have been translated. + * + * @param array $data + * An array of data. + */ +function _webform_lingotek_decode_tokens(array &$data) { + $yaml = Yaml::encode($data); + $yaml = preg_replace_callback( + '/\[([^]]+?)\]/', + function ($matches) { + // Decode token HTML entities to characters. + // @see https://stackoverflow.com/questions/6720826/php-convert-all-characters-to-html-entities. + $token = mb_decode_numericentity($matches[1], [0x000000, 0x10ffff, 0, 0xffffff], 'UTF-8'); + return "[$token]"; + }, + $yaml + ); + $data = Yaml::decode($yaml); } diff --git a/web/modules/webform/js/webform.admin.js b/web/modules/webform/js/webform.admin.js index 9dc070bd6a..48408cbf0c 100644 --- a/web/modules/webform/js/webform.admin.js +++ b/web/modules/webform/js/webform.admin.js @@ -3,7 +3,7 @@ * JavaScript behaviors for admin pages. */ -(function ($, Drupal) { +(function ($, Drupal, debounce) { 'use strict'; @@ -42,7 +42,7 @@ attach: function (context) { // Only attach the click event handler to the entire table and determine // which row triggers the event. - $('.webform-results__table', context).once('webform-results-table').click(function (event) { + $('.webform-results-table', context).once('webform-results-table').click(function (event) { if (event.target.tagName === 'A' || event.target.tagName === 'BUTTON') { return true; } @@ -62,4 +62,4 @@ } }; -})(jQuery, Drupal); +})(jQuery, Drupal, Drupal.debounce); diff --git a/web/modules/webform/js/webform.admin.tabledrag.js b/web/modules/webform/js/webform.admin.tabledrag.js new file mode 100644 index 0000000000..25573770ae --- /dev/null +++ b/web/modules/webform/js/webform.admin.tabledrag.js @@ -0,0 +1,19 @@ +/** + * @file + * Dropbutton feature. + */ + +(function ($, Drupal) { + + 'use strict'; + + // Make sure that dropButton behavior exists. + if (!Drupal.behaviors.tableDrag) { + return; + } + + $(function () { + $('head').append('<style type="text/css">.webform-tabledrag-hide {display: table-cell;}</style>'); + }); + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.ajax.js b/web/modules/webform/js/webform.ajax.js index 1b8b25c9ed..da3ac6b13e 100644 --- a/web/modules/webform/js/webform.ajax.js +++ b/web/modules/webform/js/webform.ajax.js @@ -26,7 +26,7 @@ */ Drupal.behaviors.webformAjaxLink = { attach: function (context) { - $('.webform-ajax-link').once('webform-ajax-link').each(function () { + $('.webform-ajax-link', context).once('webform-ajax-link').each(function () { var element_settings = {}; element_settings.progress = {type: 'fullscreen'}; @@ -44,21 +44,34 @@ element_settings.element = this; Drupal.ajax(element_settings); - // For anchor tags with 'data-hash' attribute, add the hash to current - // pages location. - // @see \Drupal\webform_ui\WebformUiEntityElementsForm::getElementRow - // @see Drupal.behaviors.webformFormTabs - var hash = $(this).data('hash'); - if (hash) { - $(this).on('click', function() { - location.hash = $(this).data('hash'); + // Close all open modal dialogs when opening off-canvas dialog. + if (element_settings.dialogRenderer === 'off_canvas') { + $(this).on('click', function () { + $('.ui-dialog.webform-ui-dialog:visible').find('.ui-dialog-content').dialog('close'); }); } + }); + } + }; - // Close all open modal dialogs when opening off-canvas dialog. - if (element_settings.dialogRenderer === 'off_canvas') { - $(this).on('click', function() { - $(".ui-dialog.webform-modal:visible").find('.ui-dialog-content').dialog('close'); + /** + * Adds a hash (#) to current pages location for links and buttons + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior to a[data-hash] or :button[data-hash]. + * + * @see \Drupal\webform_ui\WebformUiEntityElementsForm::getElementRow + * @see Drupal.behaviors.webformFormTabs + */ + Drupal.behaviors.webformAjaxHash = { + attach: function (context) { + $('[data-hash]', context).once('webform-ajax-hash').each(function () { + var hash = $(this).data('hash'); + if (hash) { + $(this).on('click', function () { + location.hash = $(this).data('hash'); }); } }); @@ -77,7 +90,7 @@ attach: function (context) { $('.js-webform-confirmation-back-link-ajax', context) .once('webform-confirmation-back-ajax') - .click(function(event) { + .click(function (event) { var $form = $(this).parents('form'); // Trigger the Ajax call back for the hidden submit button. @@ -98,15 +111,20 @@ } }; - /****************************************************************************/ + /** ********************************************************************** **/ // Ajax commands. - /****************************************************************************/ + /** ********************************************************************** **/ /** * Track the updated table row key. */ var updateKey; + /** + * Track the add element key. + */ + var addElement; + /** * Command to insert new content into the DOM. * @@ -129,37 +147,58 @@ // Insert the HTML. this.insert(ajax, response, status); - // Scroll to and highlight the updated table row. - if (updateKey) { + // Add element. + if (addElement) { + var addSelector = (addElement === '_root_') + ? '#webform-ui-add-element' + : '[data-drupal-selector="edit-webform-ui-elements-' + addElement + '-add"]'; + $(addSelector).click(); + } + + // If not add element, then scroll to and highlight the updated table row. + if (!addElement && updateKey) { var $element = $('tr[data-webform-key="' + updateKey + '"]'); // Highlight the updated element's row. $element.addClass('color-success'); - setTimeout(function() {$element.removeClass('color-success')}, 3000); + setTimeout(function () {$element.removeClass('color-success');}, 3000); + + // Focus first tabbable item for the updated elements and handlers. + $element.find(':tabbable:not(.tabledrag-handle)').eq(0).focus(); // Scroll to elements that are not visible. if (!isScrolledIntoView($element)) { $('html, body').animate({scrollTop: $element.offset().top - Drupal.webform.ajax.scrollTopOffset}, 500); } } - updateKey = null; // Reset element update. + else { + // Focus main content. + $('#main-content').focus(); + } // Display main page's status message in a floating container. var $wrapper = $(response.selector); if ($wrapper.parents('.ui-dialog').length === 0) { var $messages = $wrapper.find('.messages'); - if ($messages.length) { + // If 'add element' don't show any messages. + if (addElement) { + $messages.remove(); + } + else if ($messages.length) { var $floatingMessage = $('#webform-ajax-messages'); if ($floatingMessage.length === 0) { $floatingMessage = $('<div id="webform-ajax-messages" class="webform-ajax-messages"></div>'); $('body').append($floatingMessage); } - if ($floatingMessage.is(":animated")) { + if ($floatingMessage.is(':animated')) { $floatingMessage.stop(true, true); } $floatingMessage.html($messages).show().delay(3000).fadeOut(1000); } } + + updateKey = null; // Reset element update. + addElement = null; // Reset add element. }; /** @@ -188,7 +227,7 @@ scrollTarget = $(scrollTarget).parent(); } - if (response.target == 'page' && $(scrollTarget).length && $(scrollTarget)[0].tagName === 'HTML') { + if (response.target === 'page' && $(scrollTarget).length && $(scrollTarget)[0].tagName === 'HTML') { // Scroll to top when scroll target is the entire page. // @see https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport var rect = $(scrollTarget)[0].getBoundingClientRect(); @@ -202,6 +241,14 @@ $(scrollTarget).animate({scrollTop: (offset.top - Drupal.webform.ajax.scrollTopOffset)}, 500); } } + + // Focus on the form wrapper content bookmark if + // .js-webform-autofocus is not enabled. + // @see \Drupal\webform\Form\WebformAjaxFormTrait::buildAjaxForm + var $form = $(response.selector + '-content').find('form'); + if (!$form.hasClass('js-webform-autofocus')) { + $(response.selector + '-content').focus(); + } }; /** @@ -221,25 +268,39 @@ // @see https://stackoverflow.com/questions/6944744/javascript-get-portion-of-url-path var a = document.createElement('a'); a.href = response.url; - if (a.pathname == window.location.pathname && $('.webform-ajax-refresh').length) { - updateKey = (response.url.match(/[\?|&]update=(.*)($|&)/)) ? RegExp.$1 : null; + if (a.pathname === window.location.pathname && $('.webform-ajax-refresh').length) { + updateKey = (response.url.match(/[?|&]update=([^&]+)($|&)/)) ? RegExp.$1 : null; + addElement = (response.url.match(/[?|&]add_element=([^&]+)($|&)/)) ? RegExp.$1 : null; $('.webform-ajax-refresh').click(); } else { + // Clear unsaved information flag so that the current webform page + // can be redirected. + // @see Drupal.behaviors.webformUnsaved.clear + if (Drupal.behaviors.webformUnsaved) { + Drupal.behaviors.webformUnsaved.clear(); + } + + this.redirect(ajax, response, status); } }; /** - * Command to close a dialog. + * Command to close a off-canvas and modal dialog. * * If no selector is given, it defaults to trying to close the modal. * * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. * @param {object} response + * The response from the Ajax request. * @param {string} response.selector + * Selector to use. * @param {bool} response.persist + * Whether to persist the dialog element or not. * @param {number} [status] + * The HTTP status code. */ Drupal.AjaxCommands.prototype.webformCloseDialog = function (ajax, response, status) { if ($('#drupal-off-canvas').length) { @@ -254,26 +315,48 @@ var edge = document.documentElement.dir === 'rtl' ? 'left' : 'right'; var $mainCanvasWrapper = $('[data-off-canvas-main-canvas]'); $mainCanvasWrapper.css('padding-' + edge, 0); + + // Resize tabs when closing off-canvas system tray. + $(window).trigger('resize.tabs'); } - else { - // https://stackoverflow.com/questions/15763909/jquery-ui-dialog-check-if-exists-by-instance-method - if ($(response.selector).hasClass('ui-dialog-content')) { - this.closeDialog(ajax, response, status); - } + + // https://stackoverflow.com/questions/15763909/jquery-ui-dialog-check-if-exists-by-instance-method + if ($(response.selector).hasClass('ui-dialog-content')) { + this.closeDialog(ajax, response, status); } }; - /****************************************************************************/ + /** + * Triggers audio UAs to read the supplied text. + * + * @param {Drupal.Ajax} [ajax] + * A {@link Drupal.ajax} object. + * @param {object} response + * Ajax response. + * @param {string} response.text + * A string to be read by the UA. + * @param {string} [response.priority='polite'] + * A string to indicate the priority of the message. Can be either + * 'polite' or 'assertive'. + * + * @see Drupal.announce + */ + Drupal.AjaxCommands.prototype.webformAnnounce = function (ajax, response) { + // Delay the announcement. + setTimeout(function () {Drupal.announce(response.text, response.priority);}, 200); + }; + + /** ********************************************************************** **/ // Helper functions. - /****************************************************************************/ + /** ********************************************************************** **/ /** * Determine if element is visible in the viewport. * - * @param element + * @param {Element} element * An element. * - * @returns {boolean} + * @return {boolean} * TRUE if element is visible in the viewport. * * @see https://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling diff --git a/web/modules/webform/js/webform.announce.js b/web/modules/webform/js/webform.announce.js new file mode 100644 index 0000000000..f519988687 --- /dev/null +++ b/web/modules/webform/js/webform.announce.js @@ -0,0 +1,33 @@ +/** + * @file + * JavaScript behaviors for announcing changes. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Provide Webform announce attribute behavior. + * + * Announces changes using [data-webform-announce] attribute. + * + * The announce attributes allows FAPI Ajax callbacks to easily + * trigger announcements. + * + * @see \Drupal\webform\Element\WebformComputedBase::ajaxWebformComputedCallback + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior to [data-webform-announce] attribute. + */ + Drupal.behaviors.webformAnnounce = { + attach: function (context) { + $('[data-webform-announce]', context).once('data-webform-announce').each(function () { + Drupal.announce($(this).data('webform-announce')); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.confirmation.modal.js b/web/modules/webform/js/webform.confirmation.modal.js index d72264c270..8e09d84c66 100644 --- a/web/modules/webform/js/webform.confirmation.modal.js +++ b/web/modules/webform/js/webform.confirmation.modal.js @@ -19,7 +19,7 @@ */ Drupal.behaviors.webformConfirmationModal = { attach: function (context) { - $('.js-webform-confirmation-modal', context).once('webform-confirmation-modal').each(function() { + $('.js-webform-confirmation-modal', context).once('webform-confirmation-modal').each(function () { var $element = $(this); var $dialog = $element.find('.webform-confirmation-modal--content'); @@ -30,6 +30,8 @@ resizable: false, title: $element.find('.webform-confirmation-modal--title').text(), close: function (event) { + Drupal.dialog(event.target).close(); + Drupal.detachBehaviors(event.target, null, 'unload'); $(event.target).remove(); } }; @@ -40,7 +42,15 @@ // Use setTimeout to prevent dialog.position.js // Uncaught TypeError: Cannot read property 'settings' of undefined - setTimeout(function() {dialog.showModal()}, 1); + setTimeout(function () { + dialog.showModal(); + + // Close any open webform submission modals. + var $modal = $('#drupal-modal'); + if ($modal.find('.webform-submission-form').length) { + Drupal.dialog($modal .get(0)).close(); + } + }, 1); }); } }; diff --git a/web/modules/webform/js/webform.contextual.js b/web/modules/webform/js/webform.contextual.js index 157207ec1f..596c4cd243 100644 --- a/web/modules/webform/js/webform.contextual.js +++ b/web/modules/webform/js/webform.contextual.js @@ -11,15 +11,15 @@ // dynamically inserted via Ajax. // @see webform_contextual_links_view_alter() // @see Drupal.behaviors.contextual - $(document).on('click', '.contextual', function() { - $(this).find('a.webform-contextual').once('webform-contextual').each(function() { + $(document).on('click', '.contextual', function () { + $(this).find('a.webform-contextual').once('webform-contextual').each(function () { this.href = this.href.split('?')[0]; // Add ?_webform_test={webform} to the current page's URL. - if (/webform\/([^\/]+)\/test/.test(this.href)) { + if (/webform\/([^/]+)\/test/.test(this.href)) { this.href = window.location.pathname + '?_webform_test=' + RegExp.$1; } }); }); - + })(jQuery); diff --git a/web/modules/webform/js/webform.dialog.js b/web/modules/webform/js/webform.dialog.js index d28c2b8168..99135a692f 100644 --- a/web/modules/webform/js/webform.dialog.js +++ b/web/modules/webform/js/webform.dialog.js @@ -1,19 +1,75 @@ /** * @file - * JavaScript behaviors to fix dialogs. + * JavaScript behaviors for webform dialogs. */ -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { 'use strict'; - // @see http://stackoverflow.com/questions/20533487/how-to-ensure-that-ckeditor-has-focus-when-displayed-inside-of-jquery-ui-dialog - var _allowInteraction = $.ui.dialog.prototype._allowInteraction; - $.ui.dialog.prototype._allowInteraction = function (event) { - if ($(event.target).closest('.cke_dialog').length) { - return true; + // @see http://api.jqueryui.com/dialog/ + Drupal.webform = Drupal.webform || {}; + Drupal.webform.dialog = Drupal.webform.dialog || {}; + Drupal.webform.dialog.options = Drupal.webform.dialog.options || {}; + + /** + * Open webform dialog using preset options. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformDialog = { + attach: function (context) { + $('a.webform-dialog', context).once('webform-dialog').each(function () { + var $a = $(this); + + // Get default options. + var options = $.extend({}, Drupal.webform.dialog.options); + + // Get preset dialog options. + if ($a.attr('class').match(/webform-dialog-([a-z0-9_]+)/)) { + var dialogOptionsName = RegExp.$1; + if (drupalSettings.webform.dialog.options[dialogOptionsName]) { + options = drupalSettings.webform.dialog.options[dialogOptionsName]; + + // Unset title. + delete options.title; + } + } + + // Get custom dialog options. + if ($(this).data('dialog-options')) { + $.extend(options, $(this).data('dialog-options')); + } + + var href = $a.attr('href'); + + // Replace ENTITY_TYPE and ENTITY_ID placeholders and update the href. + // @see webform_page_attachments() + if (href.indexOf('?source_entity_type=ENTITY_TYPE&source_entity_id=ENTITY_ID') !== -1) { + if (drupalSettings.webform.dialog.entity_type && drupalSettings.webform.dialog.entity_id) { + href = href.replace('ENTITY_TYPE', encodeURIComponent(drupalSettings.webform.dialog.entity_type)); + href = href.replace('ENTITY_ID', encodeURIComponent(drupalSettings.webform.dialog.entity_id)); + } + else { + href = href.replace('?source_entity_type=ENTITY_TYPE&source_entity_id=ENTITY_ID', ''); + } + $a.attr('href', href); + } + + // Append _webform_dialog=1 to href to trigger Ajax support. + // @see \Drupal\webform\WebformSubmissionForm::setEntity + href += (href.indexOf('?') === -1 ? '?' : '&') + '_webform_dialog=1'; + + var element_settings = {}; + element_settings.progress = {type: 'fullscreen'}; + element_settings.url = href; + element_settings.event = 'click'; + element_settings.dialogType = $a.data('dialog-type') || 'modal'; + element_settings.dialog = options; + element_settings.element = this; + Drupal.ajax(element_settings); + }); } - return _allowInteraction.apply(this, arguments); }; -})(jQuery, Drupal); +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/webform/js/webform.element.buttons.buttonset.js b/web/modules/webform/js/webform.element.buttons.buttonset.js deleted file mode 100644 index 52fe14c94e..0000000000 --- a/web/modules/webform/js/webform.element.buttons.buttonset.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file - * JavaScript behaviors for jQuery UI buttons (buttonset) element integration. - */ - -(function ($, Drupal) { - - 'use strict'; - - /** - * Create jQuery UI buttons (buttonset) element. - * - * @type {Drupal~behavior} - */ - Drupal.behaviors.webformButtonsButtonSet = { - attach: function (context) { - $(context).find('.js-webform-buttons .form-radios, .js-webform-buttons.form-radios, .js-webform-buttons .js-webform-radios').once('webform-buttons').each(function () { - var $input = $(this); - - // Remove all div and classes around radios and labels. - $input.html($input.find('input[type="radio"], label').removeClass()); - - // Create buttonset. - $input.buttonset(); - - // Disable buttonset. - $input.buttonset('option', 'disabled', $input.find('input[type="radio"]:disabled').length); - - // Turn buttonset off/on when the input is disabled/enabled. - // @see webform.states.js - $input.on('webform:disabled', function () { - $input.buttonset('option', 'disabled', $input.find('input[type="radio"]:disabled').length); - }); - }); - } - }; - -})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.buttons.checkboxradio.js b/web/modules/webform/js/webform.element.buttons.js similarity index 78% rename from web/modules/webform/js/webform.element.buttons.checkboxradio.js rename to web/modules/webform/js/webform.element.buttons.js index 0c493e4973..1f7be05bc2 100644 --- a/web/modules/webform/js/webform.element.buttons.checkboxradio.js +++ b/web/modules/webform/js/webform.element.buttons.js @@ -17,17 +17,17 @@ ].join(','); /** - * Create jQuery UI buttons (checkboxradio) element. + * Create jQuery UI buttons element. * * @type {Drupal~behavior} */ - Drupal.behaviors.webformButtonsCheckboxRadio = { + Drupal.behaviors.webformButtons = { attach: function (context) { $(context).find(Drupal.webform.buttons.selector).once('webform-buttons').each(function () { var $buttons = $(this); // Remove classes around radios and labels and move to main element. - $buttons.find('input[type="radio"], label').each(function() { + $buttons.find('input[type="radio"], label').each(function () { $buttons.append($(this).removeAttr('class')); }); @@ -41,7 +41,7 @@ var $input = $buttons.find('input[type="radio"]'); // Create checkboxradio. - $input.checkboxradio({'icon': false}); + $input.checkboxradio({icon: false}); // Disable checkboxradio. $input.checkboxradio('option', 'disabled', $input.is(':disabled')); @@ -51,6 +51,15 @@ $input.on('webform:disabled', function () { $input.checkboxradio('option', 'disabled', $input.is(':disabled')); }); + + // Refresh checkboxradio when input is changed via webform.states.js. + // @see webform.states.js ::triggerEventHandlers(). + $input.on('change', function (event, param1) { + if (param1 === 'webform.states') { + $input.checkboxradio('refresh'); + } + }); + }); } }; diff --git a/web/modules/webform/js/webform.element.chosen.js b/web/modules/webform/js/webform.element.chosen.js index 25d5d873ef..3c4c303ac8 100644 --- a/web/modules/webform/js/webform.element.chosen.js +++ b/web/modules/webform/js/webform.element.chosen.js @@ -23,12 +23,53 @@ return; } - var options = $.extend({width: '100%'}, Drupal.webform.chosen.options); + // Add HTML5 required attribute support. + // Checking for $oldChosen to prevent duplicate workarounds from + // being applied. + // @see https://github.com/harvesthq/chosen/issues/515 + if (!$.fn.oldChosen) { + $.fn.oldChosen = $.fn.chosen; + $.fn.chosen = function (options) { + var select = $(this); + var is_creating_chosen = !!options; + if (is_creating_chosen && select.css('position') === 'absolute') { + select.removeAttr('style'); + } + var ret = select.oldChosen(options); + if (is_creating_chosen && select.css('display') === 'none') { + select.attr('style', 'display:visible; position:absolute; width:0px; height: 0px; clip:rect(0,0,0,0)'); + select.attr('tabindex', -1); + } + return ret; + }; + } $(context) .find('select.js-webform-chosen, .js-webform-chosen select') .once('webform-chosen') - .chosen(options); + .each(function () { + var $select = $(this); + // Check for .chosen-enable to prevent the chosen.module and + // webform.module from both initializing the chosen select element. + if ($select.hasClass('chosen-enable')) { + return; + } + + var options = $.extend({width: '100%'}, Drupal.webform.chosen.options); + if ($select.data('placeholder')) { + if ($select.prop('multiple')) { + options.placeholder_text_multiple = $select.data('placeholder'); + } + else { + // Clear option value so that placeholder is displayed. + $select.find('option[value=""]').html(''); + // Allow single option to be deselected. + options.allow_single_deselect = true; + } + } + + $select.chosen(options); + }); } }; diff --git a/web/modules/webform/js/webform.element.codemirror.js b/web/modules/webform/js/webform.element.codemirror.js index d711f92bf0..56d0ff7cc5 100644 --- a/web/modules/webform/js/webform.element.codemirror.js +++ b/web/modules/webform/js/webform.element.codemirror.js @@ -38,14 +38,30 @@ var options = $.extend({ mode: $(this).attr('data-webform-codemirror-mode'), lineNumbers: true, + lineWrapping: ($(this).attr('wrap') === 'off') ? false : true, viewportMargin: Infinity, readOnly: ($(this).prop('readonly') || $(this).prop('disabled')) ? true : false, - // Setting for using spaces instead of tabs - https://github.com/codemirror/CodeMirror/issues/988 extraKeys: { + // Setting for using spaces instead of tabs - https://github.com/codemirror/CodeMirror/issues/988 Tab: function (cm) { var spaces = Array(cm.getOption('indentUnit') + 1).join(' '); cm.replaceSelection(spaces, 'end', '+element'); + }, + // On 'Escape' move to the next tabbable input. + // @see http://bgrins.github.io/codemirror-accessible/ + Esc: function (cm) { + // Must show and then textarea so that we can determine + // its tabindex. + var textarea = $(cm.getTextArea()); + $(textarea).show().addClass('visually-hidden'); + var $tabbable = $(':tabbable'); + var tabindex = $tabbable.index(textarea); + $(textarea).hide().removeClass('visually-hidden'); + + // Tabindex + 2 accounts for the CodeMirror's iframe. + $tabbable.eq(tabindex + 2).focus(); } + } }, Drupal.webform.codeMirror.options); @@ -72,6 +88,27 @@ editor.setOption('readOnly', $input.is(':disabled')); }); + // Delay refreshing CodeMirror for 10 millisecond while the dialog is + // still being rendered. + // @see http://stackoverflow.com/questions/8349571/codemirror-editor-is-not-loading-content-until-clicked + setTimeout(function () { + // Show tab panel and open details. + var $tabPanel = $input.parents('.ui-tabs-panel:hidden'); + var $details = $input.parents('details:not([open])'); + + if (!$tabPanel.length && $details.length) { + return; + } + + $tabPanel.show(); + $details.attr('open', 'open'); + + editor.refresh(); + + // Hide tab panel and close details. + $tabPanel.hide(); + $details.removeAttr('open'); + }, 10); }); // Webform CodeMirror syntax coloring. @@ -79,55 +116,7 @@ // Mode Runner - http://codemirror.net/demo/runmode.html CodeMirror.runMode($(this).addClass('cm-s-default').text(), $(this).attr('data-webform-codemirror-mode'), this); }); - } }; - /****************************************************************************/ - // Refresh functions. - /****************************************************************************/ - - /** - * Refresh codemirror element to make sure it renders correctly. - * - * @param element - * An element containing a CodeMirror editor. - */ - function refresh(element) { - // Show tab panel and open details. - var $tabPanel = $(element).parents('.ui-tabs-panel:hidden'); - $tabPanel.show(); - var $details = $(element).parents('details:not([open])'); - $details.attr('open', 'open'); - - element.CodeMirror.refresh(); - - // Hide tab panel and close details. - $tabPanel.hide(); - $details.removeAttr('open'); - } - - // Workaround: When a dialog opens we need to reference all CodeMirror - // editors to make sure they are properly initialized and sized. - $(window).on('dialog:aftercreate', function (dialog, $element, settings) { - // Delay refreshing CodeMirror for 10 millisecond while the dialog is - // still being rendered. - // @see http://stackoverflow.com/questions/8349571/codemirror-editor-is-not-loading-content-until-clicked - setTimeout(function () { - $('.CodeMirror').each(function (index, element) { - refresh(element); - }); - }, 10); - }); - - // On state:visible refresh CodeMirror elements. - $(document).on('state:visible state:visible-slide', function (event) { - var $element = $(event.target).parent().find('.js-webform-codemirror'); - $element.parent().find('.CodeMirror').each(function (index, element) { - setTimeout(function () { - refresh(element); - }, 1); - }); - }); - })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.color.js b/web/modules/webform/js/webform.element.color.js index 3cf9cb3965..f5ff08948f 100644 --- a/web/modules/webform/js/webform.element.color.js +++ b/web/modules/webform/js/webform.element.color.js @@ -24,14 +24,21 @@ .removeClass('form-color-large'); } else { - // Display color input's output to the end user. - var $output = $('<input class="form-color-output ' + $element.attr('class') + ' js-webform-input-mask" data-inputmask-mask="\\#######" />'); + // Display color input's output w/ visually-hidden label to + // the end user. + var $output = $('<input class="form-color-output ' + $element.attr('class') + ' js-webform-input-mask" data-inputmask-mask="\\#######" />').uniqueId(); + var $label = $element.parent('.js-form-type-color').find('label').clone(); + $label.attr({ + for: $output.attr('id'), + class: 'visually-hidden' + }); if ($.fn.inputmask) { $output.inputmask(); } $output[0].value = $element[0].value; $element .after($output) + .after($label) .css({float: 'left'}); // Sync $element and $output. diff --git a/web/modules/webform/js/webform.element.composite.js b/web/modules/webform/js/webform.element.composite.js index 58d5a1a28f..ac0184db1a 100644 --- a/web/modules/webform/js/webform.element.composite.js +++ b/web/modules/webform/js/webform.element.composite.js @@ -14,14 +14,14 @@ */ Drupal.behaviors.webformElementComposite = { attach: function (context) { - $('[data-composite-types]').once('webform-composite-types').each(function() { + $('[data-composite-types]').once('webform-composite-types').each(function () { var $element = $(this); var $type = $element.closest('tr').find('.js-webform-composite-type'); var types = $element.attr('data-composite-types').split(','); var required = $element.attr('data-composite-required'); - $type.on('change', function() { + $type.on('change', function () { if ($.inArray($(this).val(), types) === -1) { $element.hide(); if (required) { @@ -31,11 +31,11 @@ else { $element.show(); if (required) { - $element.attr({ 'required': 'required', 'aria-required': 'true' }) + $element.attr({'required': 'required', 'aria-required': 'true'}); } } }).change(); - }) + }); } }; diff --git a/web/modules/webform/js/webform.element.computed.js b/web/modules/webform/js/webform.element.computed.js new file mode 100644 index 0000000000..1853c1e734 --- /dev/null +++ b/web/modules/webform/js/webform.element.computed.js @@ -0,0 +1,74 @@ +/** + * @file + * JavaScript behaviors for computed elements. + */ + +(function ($, Drupal, debounce) { + + 'use strict'; + + Drupal.webform = Drupal.webform || {}; + Drupal.webform.computed = Drupal.webform.computed || {}; + Drupal.webform.computed.delay = Drupal.webform.computed.delay || 500; + + /** + * Initialize computed elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformComputed = { + attach: function (context) { + $(context).find('.js-webform-computed').once('webform-computed').each(function () { + // Get computed element and parent form. + var $element = $(this); + var $form = $element.closest('form'); + + // Get elements that are used by the computed element. + var elementKeys = $(this).data('webform-element-keys').split(','); + if (!elementKeys) { + return; + } + + // Get computed element triggers. + var inputs = []; + $.each(elementKeys, function (i, key) { + // Exact input match. + inputs.push(':input[name="' + key + '"]'); + // Sub inputs. (aka #tree) + inputs.push(':input[name^="' + key + '["]'); + }); + var $triggers = $form.find(inputs.join(',')); + + // Add event handler to computed element triggers. + $triggers.on('keyup change', + debounce(triggerUpdate, Drupal.webform.computed.delay)); + + // Initialize computed element update which refreshes the displayed + // value and accounts for any changes to the #default_value for a + // computed element. + triggerUpdate(true); + + function triggerUpdate(initialize) { + // Prevent duplicate computations. + // @see Drupal.behaviors.formSingleSubmit + if (initialize !== true) { + var formValues = $triggers.serialize(); + var previousValues = $element.attr('data-webform-computed-last'); + if (previousValues === formValues) { + return; + } + $element.attr('data-webform-computed-last', formValues); + } + + // Add loading class to computed wrapper. + $element.find('.js-webform-computed-wrapper') + .addClass('webform-computed-loading'); + + // Trigger computation. + $element.find('.js-form-submit').mousedown(); + } + }); + } + }; + +})(jQuery, Drupal, Drupal.debounce); diff --git a/web/modules/webform/js/webform.element.counter.js b/web/modules/webform/js/webform.element.counter.js index 7011639436..903ba2369c 100644 --- a/web/modules/webform/js/webform.element.counter.js +++ b/web/modules/webform/js/webform.element.counter.js @@ -1,13 +1,13 @@ /** * @file - * JavaScript behaviors for jQuery Word and Counter Counter integration. + * JavaScript behaviors for jQuery Text Counter integration. */ (function ($, Drupal) { 'use strict'; - // @see http://qwertypants.github.io/jQuery-Word-and-Character-Counter-Plugin/ + // @see https://github.com/ractoon/jQuery-Text-Counter#options Drupal.webform = Drupal.webform || {}; Drupal.webform.counter = Drupal.webform.counter || {}; Drupal.webform.counter.options = Drupal.webform.counter.options || {}; @@ -19,29 +19,38 @@ */ Drupal.behaviors.webformCounter = { attach: function (context) { - if (!$.fn.counter) { + if (!$.fn.textcounter) { return; } $(context).find('.js-webform-counter').once('webform-counter').each(function () { var options = { - goal: $(this).attr('data-counter-limit'), - msg: $(this).attr('data-counter-message') + type: $(this).data('counter-type'), + max: $(this).data('counter-maximum'), + min: $(this).data('counter-minimum') || 0, + counterText: $(this).data('counter-minimum-message'), + countDownText: $(this).data('counter-maximum-message'), + inputErrorClass: 'webform-counter-warning', + counterErrorClass: 'webform-counter-warning', + countSpaces: true, + stopInputAtMaximum: false, + // Don't display min/max message since server-side validation will + // display these messages. + minimumErrorText: '', + maximumErrorText: '' }; - // Only word type can be defined, otherwise the counter defaults to - // character counting. - if ($(this).attr('data-counter-type') === 'word') { - options.type = 'word'; + options.countDown = (options.max) ? true : false; + if (!options.counterText) { + options.counterText = (options.type === 'word') ? Drupal.t('%d word(s) entered') : Drupal.t('%d characters(s) entered'); + } + if (!options.countDownText) { + options.countDownText = (options.type === 'word') ? Drupal.t('%d word(s) remaining') : Drupal.t('%d characters(s) remaining'); } options = $.extend(options, Drupal.webform.counter.options); - // Set the target to a div that is appended to end of the input's parent container. - options.target = $('<div class="webform-counter-message"></div>'); - $(this).parent().append(options.target); - - $(this).counter(options); + $(this).textcounter(options); }); } diff --git a/web/modules/webform/js/webform.element.date.js b/web/modules/webform/js/webform.element.date.js index 0af0042fb9..b091a80f2d 100644 --- a/web/modules/webform/js/webform.element.date.js +++ b/web/modules/webform/js/webform.element.date.js @@ -32,7 +32,7 @@ // Skip if date inputs are supported by the browser and input is not a text field. // @see \Drupal\webform\Element\WebformDatetime - if (window.Modernizr && Modernizr.inputtypes.date === true && $input.attr('type') !== 'text') { + if (window.Modernizr && Modernizr.inputtypes && Modernizr.inputtypes.date === true && $input.attr('type') !== 'text') { return; } @@ -41,6 +41,16 @@ changeYear: true }, Drupal.webform.datePicker.options); + // Add datepicker button. + if ($input.hasData('datepicker-button')) { + options = $.extend({ + showOn: 'both', + buttonImage: settings.webform.datePicker.buttonImage, + buttonImageOnly: true, + buttonText: Drupal.t('Select date') + }, Drupal.webform.datePicker.options); + } + var dateFormat = $input.data('drupalDateFormat'); // The date format is saved in PHP style, we need to convert to jQuery @@ -48,7 +58,7 @@ // @see http://stackoverflow.com/questions/16702398/convert-a-php-date-format-to-a-jqueryui-datepicker-date-format // @see http://php.net/manual/en/function.date.php options.dateFormat = dateFormat - // Year. + // Year. .replace('Y', 'yy') // A full numeric representation of a year, 4 digits (1999 or 2003) .replace('y', 'y') // A two digit representation of a year (99 or 03) // Month. @@ -72,20 +82,30 @@ // Add min/max year to data range. if (!options.yearRange && $input.data('min-year') && $input.data('max-year')) { - options.yearRange = $input.data('min-year') + ':' + $input.attr('data-max-year') + options.yearRange = $input.data('min-year') + ':' + $input.attr('data-max-year'); } // First day of the week. options.firstDay = settings.webform.dateFirstDay; + // Disable autocomplete. + // @see https://gist.github.com/niksumeiko/360164708c3b326bd1c8 + var isChrome = /Chrome/.test(window.navigator.userAgent) && /Google Inc/.test(window.navigator.vendor); + $input.attr('autocomplete', (isChrome) ? 'off' : 'false'); + $input.datepicker(options); }); + } + // Issue #2983363: Datepicker is being detached when multiple files are + // uploaded. + /* }, detach: function (context, settings, trigger) { if (trigger === 'unload') { $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy'); } } + */ }; })(jQuery, Modernizr, Drupal); diff --git a/web/modules/webform/js/webform.element.details.save.js b/web/modules/webform/js/webform.element.details.save.js index c2e005c84e..3fb4a91d6b 100644 --- a/web/modules/webform/js/webform.element.details.save.js +++ b/web/modules/webform/js/webform.element.details.save.js @@ -68,7 +68,7 @@ * @param {jQuery} $details * A details element. * - * @return string + * @return {string} * The name used to store the state of details element. */ Drupal.webformDetailsSaveGetName = function ($details) { @@ -103,6 +103,6 @@ formId = formId.replace(/--.+?$/, '').replace(/-/g, '_'); detailsId = detailsId.replace(/--.+?$/, '').replace(/-/g, '_'); return 'Drupal.webform.' + formId + '.' + detailsId; - } + }; })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.details.toggle.js b/web/modules/webform/js/webform.element.details.toggle.js index 0041e641fd..550f32a5f3 100644 --- a/web/modules/webform/js/webform.element.details.toggle.js +++ b/web/modules/webform/js/webform.element.details.toggle.js @@ -20,15 +20,12 @@ attach: function (context) { $('.js-webform-details-toggle', context).once('webform-details-toggle').each(function () { var $form = $(this); + var $tabs = $form.find('.webform-tabs'); - var options = $.extend({ - 'button': '<button type="button" class="link webform-details-toggle-state"></button>' - }, Drupal.webform.detailsToggle.options); - - // Get only the main details elements and ingnore all nested details. - var $details = $form.find('details').filter(function() { - // @todo Figure out how to optimize the below code. - var $parents = $(this).parentsUntil('.js-webform-details-toggle'); + // Get only the main details elements and ignore all nested details. + var selector = ($tabs.length) ? '.webform-tab' : '.js-webform-details-toggle'; + var $details = $form.find('details').filter(function () { + var $parents = $(this).parentsUntil(selector); return ($parents.find('details').length === 0); }); @@ -37,8 +34,12 @@ return; } - // Add toggle state link to first details element. - $details.first().before($(options.button) + var options = $.extend({ + button: '<button type="button" class="webform-details-toggle-state"></button>' + }, Drupal.webform.detailsToggle.options); + + // Create toggle buttons. + var $toggle = $(options.button) .attr('title', Drupal.t('Toggle details widget state.')) .on('click', function (e) { var open; @@ -64,8 +65,16 @@ } }) .wrap('<div class="webform-details-toggle-state-wrapper"></div>') - .parent() - ); + .parent(); + + if ($tabs.length) { + // Add toggle state before the tabs. + $tabs.find('.item-list:first-child').eq(0).before($toggle); + } + else { + // Add toggle state link to first details element. + $details.eq(0).before($toggle); + } setDetailsToggleLabel($form); }); @@ -92,8 +101,13 @@ * A webform. */ function setDetailsToggleLabel($form) { - var label = (isFormDetailsOpen($form)) ? Drupal.t('Collapse all') : Drupal.t('Expand all'); + var isOpen = isFormDetailsOpen($form); + + var label = (isOpen) ? Drupal.t('Collapse all') : Drupal.t('Expand all'); $form.find('.webform-details-toggle-state').html(label); + + var text = (isOpen) ? Drupal.t('All details have been expanded.') : Drupal.t('All details have been collapsed.'); + Drupal.announce(text); } })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.help.js b/web/modules/webform/js/webform.element.help.js index b8337c9c83..fc43577a59 100644 --- a/web/modules/webform/js/webform.element.help.js +++ b/web/modules/webform/js/webform.element.help.js @@ -11,19 +11,19 @@ Drupal.webform = Drupal.webform || {}; Drupal.webform.elementHelpIcon = Drupal.webform.elementHelpIcon || {}; Drupal.webform.elementHelpIcon.options = Drupal.webform.elementHelpIcon.options || { - position: { my: "left+5 top+5", at: "left bottom", collision: "flipfit" }, + position: {my: 'left+5 top+5', at: 'left bottom', collision: 'flipfit'}, tooltipClass: 'webform-element-help--tooltip', // @see https://stackoverflow.com/questions/18231315/jquery-ui-tooltip-html-with-links - show: null, + show: {delay: 100}, close: function (event, ui) { ui.tooltip.hover( function () { $(this).stop(true).fadeTo(400, 1); }, function () { - $(this).fadeOut("400", function () { + $(this).fadeOut('400', function () { $(this).remove(); - }) + }); }); } }; @@ -38,12 +38,39 @@ $(context).find('.webform-element-help').once('webform-element-help').each(function () { var $link = $(this); - var options = $.extend({}, Drupal.webform.elementHelpIcon.options); - // Use 'data-webform-help' attribute which can include HTML markup. - options.content = $(this).attr('data-webform-help'); - $link.tooltip(options).on('click', function (event) { - event.preventDefault(); - }); + var options = $.extend({ + // Use 'data-webform-help' attribute which can include HTML markup. + content: $link.attr('data-webform-help'), + items: '[data-webform-help]' + }, Drupal.webform.elementHelpIcon.options); + + $link.tooltip(options) + .on('click', function (event) { + // Prevent click from toggling <label>s wrapped around help. + event.preventDefault(); + }).on('keydown', function(event) { + // Prevent ESC from from closing dialogs. + if (event.keyCode === $.ui.keyCode.ESCAPE) { + event.stopPropagation(); + } + }); + + // Help tooltips are generally placed with <label> tags. + // Screen readers are also reading the <label> and the + // 'aria-describedby' attribute. + // To prevent this issue we are removing the <label>'s 'for' attribute + // when the tooltip is focused. + var $label = $(this).parent('label'); + var labelFor = $label.attr('for') || ''; + if ($label.length && labelFor) { + $link + .on('focus', function () { + $label.removeAttr('for'); + }) + .on('blur', function () { + $label.attr('for', labelFor); + }); + } }); } }; diff --git a/web/modules/webform/js/webform.element.html_editor.js b/web/modules/webform/js/webform.element.html_editor.js index 09a32d507f..398c4a88dc 100644 --- a/web/modules/webform/js/webform.element.html_editor.js +++ b/web/modules/webform/js/webform.element.html_editor.js @@ -41,11 +41,13 @@ && drupalSettings.yamlEditor.source && drupalSettings.yamlEditor.source.indexOf('noconflict') !== -1) { delete plugins.codemirror; - ('console' in window) && window.console.log('YAML Editor module is not compatible with the ckeditor.codemirror plugin. @see Issue #2936147: ckeditor.codemirror plugin breaks admin textarea.'); + if ('console' in window) { + window.console.log('YAML Editor module is not compatible with the ckeditor.codemirror plugin. @see Issue #2936147: ckeditor.codemirror plugin breaks admin textarea.'); + } } for (var plugin_name in plugins) { - if(plugins.hasOwnProperty(plugin_name)) { + if (plugins.hasOwnProperty(plugin_name)) { CKEDITOR.plugins.addExternal(plugin_name, plugins[plugin_name]); } } @@ -114,7 +116,7 @@ // Catch and suppress // "Uncaught TypeError: Cannot read property 'getEditor' of undefined". - // + // // Steps to reproduce this error. // - Goto any form elements. // - Edit an element. diff --git a/web/modules/webform/js/webform.element.icheck.js b/web/modules/webform/js/webform.element.icheck.js index 59077fbdba..4ba85fade5 100644 --- a/web/modules/webform/js/webform.element.icheck.js +++ b/web/modules/webform/js/webform.element.icheck.js @@ -12,17 +12,6 @@ Drupal.webform.iCheck = Drupal.webform.iCheck || {}; Drupal.webform.iCheck.options = Drupal.webform.iCheck.options || {}; - function initializeCheckbox($input, options) { - $input.addClass('js-webform-icheck') - .iCheck(options) - // @see https://github.com/fronteed/iCheck/issues/244 - .on('ifChecked', function (e) { - $(e.target).attr('checked', 'checked').change(); - }) - .on('ifUnchecked', function (e) { - $(e.target).removeAttr('checked').change(); - }); - } /** * Enhance checkboxes and radios using iCheck. * @@ -92,12 +81,12 @@ $(this).iCheck(options); }) - .on('ifChanged', function () { - var _index = $(this).parents('th').index() + 1; - $(this).parents('thead').next('tbody').find('tr td:nth-child(' + _index + ') input') - .iCheck(!$(this).is(':checked') ? 'check' : 'uncheck') - .iCheck($(this).is(':checked') ? 'check' : 'uncheck'); - }); + .on('ifChanged', function () { + var _index = $(this).parents('th').index() + 1; + $(this).parents('thead').next('tbody').find('tr td:nth-child(' + _index + ') input') + .iCheck(!$(this).is(':checked') ? 'check' : 'uncheck') + .iCheck($(this).is(':checked') ? 'check' : 'uncheck'); + }); }); } }; diff --git a/web/modules/webform/js/webform.element.inputhide.js b/web/modules/webform/js/webform.element.inputhide.js new file mode 100644 index 0000000000..c6cd555a81 --- /dev/null +++ b/web/modules/webform/js/webform.element.inputhide.js @@ -0,0 +1,51 @@ +/** + * @file + * JavaScript behaviors for input hiding. + */ + +(function ($, Drupal) { + + 'use strict'; + + var isChrome = /Chrome/.test(window.navigator.userAgent) && /Google Inc/.test(window.navigator.vendor); + + /** + * Initialize input hiding. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformInputHide = { + attach: function (context) { + // Apply chrome fix to prevent password input from being autofilled. + // @see https://stackoverflow.com/questions/15738259/disabling-chrome-autofill + if (isChrome) { + $(context).find('form:has(input.js-webform-input-hide)') + .once('webform-input-hide-chrome-workaround') + .each(function () { + $(this).prepend('<input style="display:none" type="text" name="chrome_autocomplete_username"/><input style="display:none" type="password" name="chrome_autocomplete_password"/>'); + }); + } + + // Convert text based inputs to password input on blur. + $(context).find('input.js-webform-input-hide') + .once('webform-input-hide') + .each(function () { + var type = this.type; + // Initialize input hiding. + this.type = 'password'; + + // Attach blur and focus event handlers. + $(this) + .on('blur', function () { + this.type = 'password'; + $(this).attr('autocomplete', 'off'); + }) + .on('focus', function () { + this.type = type; + $(this).removeAttr('autocomplete'); + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.location.js b/web/modules/webform/js/webform.element.location.geocomplete.js similarity index 61% rename from web/modules/webform/js/webform.element.location.js rename to web/modules/webform/js/webform.element.location.geocomplete.js index bb66d6e2be..d428f0f76d 100644 --- a/web/modules/webform/js/webform.element.location.js +++ b/web/modules/webform/js/webform.element.location.geocomplete.js @@ -3,7 +3,7 @@ * JavaScript behaviors for Geocomplete location integration. */ -(function ($, Drupal, drupalSettings) { +(function ($, Drupal) { 'use strict'; @@ -14,7 +14,7 @@ Drupal.webform.locationGeocomplete.options = Drupal.webform.locationGeocomplete.options || {}; /** - * Initialize location Geocompletion. + * Initialize location geocomplete. * * @type {Drupal~behavior} */ @@ -24,19 +24,23 @@ return; } - $(context).find('div.js-form-type-webform-location').once('webform-location').each(function () { + $(context).find('.js-webform-type-webform-location-geocomplete').once('webform-location-geocomplete').each(function () { var $element = $(this); var $input = $element.find('.webform-location-geocomplete'); + + // Display a map. var $map = null; - if ($input.attr('data-webform-location-map')) { - $map = $('<div class="webform-location-map"><div class="webform-location-map--container"></div></div>').insertAfter($input).find('.webform-location-map--container'); + if ($input.attr('data-webform-location-geocomplete-map')) { + $map = $('<div class="webform-location-geocomplete-map"><div class="webform-location-geocomplete-map--container"></div></div>').insertAfter($input).find('.webform-location-geocomplete-map--container'); } var options = $.extend({ details: $element, - detailsAttribute: 'data-webform-location-attribute', + detailsAttribute: 'data-webform-location-geocomplete-attribute', types: ['geocode'], map: $map, + geocodeAfterResult: false, + restoreValueAfterBlur: true, mapOptions: { disableDefaultUI: true, zoomControl: true @@ -45,26 +49,13 @@ var $geocomplete = $input.geocomplete(options); - $geocomplete.on('input', function () { - // Reset attributes on input. - $element.find('[data-webform-location-attribute]').val(''); - }).on('blur', function () { - // Make sure to get attributes on blur. - if ($element.find('[data-webform-location-attribute="location"]').val() === '') { - var value = $geocomplete.val(); - if (value) { - $geocomplete.geocomplete('find', value); - } - } - }); - // If there is default value look up location's attributes, else see if // the default value should be set to the browser's current geolocation. var value = $geocomplete.val(); if (value) { $geocomplete.geocomplete('find', value); } - else if (navigator.geolocation && $geocomplete.attr('data-webform-location-geolocation')) { + else if (navigator.geolocation && $geocomplete.attr('data-webform-location-geocomplete-geolocation')) { navigator.geolocation.getCurrentPosition(function (position) { $geocomplete.geocomplete('find', position.coords.latitude + ', ' + position.coords.longitude); }); @@ -73,4 +64,4 @@ } }; -})(jQuery, Drupal, drupalSettings); +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.location.places.js b/web/modules/webform/js/webform.element.location.places.js new file mode 100644 index 0000000000..336b31549b --- /dev/null +++ b/web/modules/webform/js/webform.element.location.places.js @@ -0,0 +1,101 @@ +/** + * @file + * JavaScript behaviors for Algolia places location integration. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + // @see https://github.com/algolia/places + // @see https://community.algolia.com/places/documentation.html#options + Drupal.webform = Drupal.webform || {}; + Drupal.webform.locationPlaces = Drupal.webform.locationPlaces || {}; + Drupal.webform.locationPlaces.options = Drupal.webform.locationPlaces.options || {}; + + var mapping = { + lat: 'lat', + lng: 'lng', + name: 'name', + postcode: 'postcode', + locality: 'locality', + city: 'city', + administrative: 'administrative', + country: 'country', + countryCode: 'country_code', + county: 'county', + suburb: 'suburb' + }; + + /** + * Initialize location places. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformLocationPlaces = { + attach: function (context) { + if (!window.places) { + return; + } + + $(context).find('.js-webform-type-webform-location-places').once('webform-location-places').each(function () { + var $element = $(this); + var $input = $element.find('.webform-location-places'); + + // Prevent the 'Enter' key from submitting the form. + $input.keydown(function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + } + }); + + var options = $.extend({ + type: 'address', + useDeviceLocation: true, + container: $input.get(0) + }, Drupal.webform.locationPlaces.options); + + // Add application id and API key. + if (drupalSettings.webform.location.places.app_id && drupalSettings.webform.location.places.api_key) { + options.appId = drupalSettings.webform.location.places.app_id; + options.apiKey = drupalSettings.webform.location.places.api_key; + } + + var placesAutocomplete = window.places(options); + + // Disable autocomplete. + // @see https://gist.github.com/niksumeiko/360164708c3b326bd1c8 + var isChrome = /Chrome/.test(window.navigator.userAgent) && /Google Inc/.test(window.navigator.vendor); + $input.attr('autocomplete', (isChrome) ? 'off' : 'false'); + + // Sync values on change and clear events. + placesAutocomplete.on('change', function (e) { + $.each(mapping, function (source, destination) { + var value = (source === 'lat' || source === 'lng' ? e.suggestion.latlng[source] : e.suggestion[source]) || ''; + setValue(destination, value); + }); + }); + placesAutocomplete.on('clear', function (e) { + $.each(mapping, function (source, destination) { + setValue(destination, ''); + }); + }); + + /** + * Set attribute value. + * + * @param {string} name + * The attribute name + * @param {string} value + * The attribute value + */ + function setValue(name, value) { + var inputSelector = ':input[data-webform-location-places-attribute="' + name + '"]'; + $element.find(inputSelector).val(value); + } + }); + } + }; + +})(jQuery, Drupal, drupalSettings); + diff --git a/web/modules/webform/js/webform.element.managed_file.js b/web/modules/webform/js/webform.element.managed_file.js new file mode 100644 index 0000000000..1628e9ddf8 --- /dev/null +++ b/web/modules/webform/js/webform.element.managed_file.js @@ -0,0 +1,109 @@ +/** + * @file + * JavaScript behaviors for managed file uploads. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Track file uploads and display confirm dialog when an file upload is inprogress. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformManagedFileAutoUpload = { + attach: function attach(context) { + // Add submit handler to file upload form. + $(context).find('form') + .once('webform-auto-file-upload') + .on('submit', function (event) { + var $form = $(this); + if ($form.data('webform-auto-file-uploads') > 0 && blockSubmit($form)) { + event.preventDefault(); + return false; + } + return true; + }); + + // Add submit handler to form.beforeSend. + // Update Drupal.Ajax.prototype.beforeSend only once. + if (typeof Drupal.Ajax !== 'undefined' && typeof Drupal.Ajax.prototype.beforeSubmitWebformManagedFileAutoUploadOriginal === 'undefined') { + Drupal.Ajax.prototype.beforeSubmitWebformManagedFileAutoUploadOriginal = Drupal.Ajax.prototype.beforeSubmit; + Drupal.Ajax.prototype.beforeSubmit = function (form_values, element_settings, options) { + var $form = this.$form; + var $element = $(this.element); + + // Determine if the triggering element is within .form-actions. + var isFormActions = $element + .closest('.form-actions').length; + + // Determine if the triggering element is within a multiple element. + var isMultipleUpload = $element + .parents('.js-form-type-webform-multiple, .js-form-type-webform-custom-composite') + .find('.js-form-managed-file').length; + + // Determine if the triggering element is not within a + // managed file element. + var isManagedUploadButton = $element.parents('.js-form-managed-file').length; + + // Only trigger block submit for .form-actions and multiple element + // with file upload. + if ($form.data('webform-auto-file-uploads') > 0 && + (isFormActions || (isMultipleUpload && !isManagedUploadButton)) && + blockSubmit($form)) { + this.ajaxing = false; + return false; + } + return this.beforeSubmitWebformManagedFileAutoUploadOriginal(); + }; + } + + $(context).find('input[type="file"]').once('webform-auto-file-upload').on('change', function () { + // Track file upload. + $(this).data('msk-auto-file-upload', true); + + // Increment form file uploads. + var $form = $(this.form); + var fileUploads = ($form.data('webform-auto-file-uploads') || 0); + $form.data('webform-auto-file-uploads', fileUploads + 1); + }); + }, + detach: function detach(context, settings, trigger) { + if (trigger === 'unload') { + $(context).find('input[type="file"]').removeOnce('webform-auto-file-upload').each(function () { + if ($(this).data('msk-auto-file-upload')) { + // Remove file upload tracking. + $(this).removeData('msk-auto-file-upload'); + + // Decrease form file uploads. + var $form = $(this.form); + var fileUploads = ($form.data('webform-auto-file-uploads') || 0); + $form.data('webform-auto-file-uploads', fileUploads - 1); + } + }); + } + } + }; + + /** + * Block form submit. + * + * @param {jQuery} form + * A form. + * + * @return {boolean} + * TRUE if form submit should be blocked. + */ + function blockSubmit(form) { + if ($(form).data('webform-auto-file-uploads') < 0) { + return false; + } + + var message = Drupal.t('File upload inprogress. Uploaded file may be lost.') + + '\n' + + Drupal.t('Do you want to continue?'); + return !window.confirm(message); + } + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.more.js b/web/modules/webform/js/webform.element.more.js index 44b225c4d1..739bafd832 100644 --- a/web/modules/webform/js/webform.element.more.js +++ b/web/modules/webform/js/webform.element.more.js @@ -16,11 +16,42 @@ attach: function (context) { $(context).find('.js-webform-element-more').once('webform-element-more').each(function (event) { var $more = $(this); - $more.find('a').on('click', function() { - $more.toggleClass('is-open'); - $more.find('.webform-element-more--content').slideToggle(); + var $a = $more.find('a'); + var $content = $more.find('.webform-element-more--content'); + + // Add aria-* attributes. + $a.attr({ + 'aria-expanded': false, + 'aria-controls': $content.attr('id') + }); + + // Add event handlers. + $a.on('click', toggle) + .on('keydown', function (event) { + // Space or Return. + if (event.which === 32 || event.which === 13) { + toggle(event); + } + }); + + function toggle(event) { + var expanded = ($a.attr('aria-expanded') === 'true'); + + // Toggle `aria-expanded` attributes on link. + $a.attr('aria-expanded', !expanded); + + // Toggle content and more .is-open state. + if (expanded) { + $more.removeClass('is-open'); + $content.slideUp(); + } + else { + $more.addClass('is-open'); + $content.slideDown(); + } + event.preventDefault(); - }) + } }); } }; diff --git a/web/modules/webform/js/webform.element.multiple.js b/web/modules/webform/js/webform.element.multiple.js index 01787c322f..8923a201e3 100644 --- a/web/modules/webform/js/webform.element.multiple.js +++ b/web/modules/webform/js/webform.element.multiple.js @@ -1,6 +1,6 @@ /** * @file - * JavaScript behaviors for message element integration. + * JavaScript behaviors for multiple element. */ (function ($, Drupal) { @@ -12,17 +12,42 @@ * * @type {Drupal~behavior} */ - Drupal.behaviors.webformMultiple = { + Drupal.behaviors.webformMultipleTableDrag = { attach: function (context, settings) { for (var base in settings.tableDrag) { if (settings.tableDrag.hasOwnProperty(base)) { - var $tableDrag = $(context).find('#' + base); - var $toggleWeight = $tableDrag.parent().find('.tabledrag-toggle-weight'); - $toggleWeight.addClass('webform-multiple-tabledrag-toggle-weight'); - $tableDrag.after($toggleWeight); + $(context).find('.js-form-type-webform-multiple #' + base).once('webform-multiple-table-drag').each(function () { + var $tableDrag = $(this); + var $toggleWeight = $tableDrag.prev().prev('.tabledrag-toggle-weight-wrapper'); + if ($toggleWeight.length) { + $toggleWeight.addClass('webform-multiple-tabledrag-toggle-weight'); + $tableDrag.after($toggleWeight); + } + }); } } } }; + /** + * Submit multiple add number input value when enter is pressed. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformMultipleAdd = { + attach: function (context, settings) { + $(context).find('.js-webform-multiple-add').once('webform-multiple-add').each(function () { + var $submit = $(this).find('input[type="submit"], button'); + var $number = $(this).find('input[type="number"]'); + $number.keyup(function (event) { + if (event.which === 13) { + // Note: Mousedown is the default trigger for Ajax events. + // @see Drupal.Ajax. + $submit.mousedown(); + } + }); + }); + } + }; + })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.options.admin.js b/web/modules/webform/js/webform.element.options.admin.js index 0d1071eb79..0a1db52b4e 100644 --- a/web/modules/webform/js/webform.element.options.admin.js +++ b/web/modules/webform/js/webform.element.options.admin.js @@ -15,7 +15,7 @@ */ Drupal.behaviors.webformOptionsAdmin = { attach: function (context) { - $(context).find('.js-form-type-webform-options .js-webform-options-value').once('webform-options-value').each(function() { + $(context).find('.js-webform-options-sync').once('js-webform-options-sync').each(function () { // Target input name and not id because the id will be changing via // Ajax callbacks. var name = this.name; @@ -47,7 +47,7 @@ } }); - }) + }); } }; diff --git a/web/modules/webform/js/webform.element.other.js b/web/modules/webform/js/webform.element.other.js index 72a2e4b0d8..88f46438a7 100644 --- a/web/modules/webform/js/webform.element.other.js +++ b/web/modules/webform/js/webform.element.other.js @@ -33,11 +33,15 @@ // Display the element. $element[showEffect](); - // Focus and require the input. - $input.focus().prop('required', true).attr('aria-required', 'true'); + // If not initializing, then focus the other element. + if (effect !== false) { + $input.focus(); + } + // Require the input. + $input.prop('required', true).attr('aria-required', 'true'); // Restore the input's value. var value = $input.data('webform-value'); - if (value !== undefined) { + if (typeof value !== 'undefined') { $input.val(value); var input = $input.get(0); // Move cursor to the beginning of the other text input. diff --git a/web/modules/webform/js/webform.element.radios.js b/web/modules/webform/js/webform.element.radios.js index c01fe32f8b..bcf014ee70 100644 --- a/web/modules/webform/js/webform.element.radios.js +++ b/web/modules/webform/js/webform.element.radios.js @@ -16,7 +16,7 @@ */ Drupal.behaviors.webformRadiosRequired = { attach: function (context) { - $('.js-webform-type-radios[required="required"], .js-form-type-webform-radios-other[required="required"]', context).each(function() { + $('.js-webform-type-radios.required, .js-webform-type-webform-radios-other.required', context).each(function () { $(this).find('input[type="radio"]').attr({'required': 'required', 'aria-required': 'true'}); }); } diff --git a/web/modules/webform/js/webform.element.range.js b/web/modules/webform/js/webform.element.range.js index 87bdf62d47..5c1a8d7aa6 100644 --- a/web/modules/webform/js/webform.element.range.js +++ b/web/modules/webform/js/webform.element.range.js @@ -39,7 +39,7 @@ }); }); } - } + }; /** * Display HTML5 range output in a floating bubble. @@ -66,7 +66,6 @@ return; } - $element.css('position', 'relative'); $input.on('input', function () { @@ -91,7 +90,7 @@ // range's buttons so we only incrementally move the output bubble. var inputWidth = $input.outerWidth(); var buttonPosition = Math.floor(inputWidth * (inputValue - $input.attr('min')) / ($input.attr('max') - $input.attr('min'))); - var increment = Math.floor(inputWidth/5); + var increment = Math.floor(inputWidth / 5); var outputWidth = $output.outerWidth(); // Set output left position. @@ -106,7 +105,7 @@ } } else if (buttonPosition <= increment * 3) { - left = (increment * 2.5) - (outputWidth/2); + left = (increment * 2.5) - (outputWidth / 2); } else if (buttonPosition <= increment * 4) { @@ -122,29 +121,28 @@ left = Math.floor($input.position().left + left); // Finally, position the output. - $output.css({top: top, left: left}) + $output.css({top: top, left: left}); }) - // Fake a change to position output at page load. - .trigger('input'); + // Fake a change to position output at page load. + .trigger('input'); // Add fade in/out event handlers if opacity is defined. var defaultOpacity = $output.css('opacity'); if (defaultOpacity < 1) { // Fade in/out on focus/blur of the input. - $input.on('focus mouseover', function() { + $input.on('focus mouseover', function () { $output.stop().fadeTo('slow', 1); }); - $input.on('blur mouseout', function() { + $input.on('blur mouseout', function () { $output.stop().fadeTo('slow', defaultOpacity); }); // Also fade in when focusing the output. - $output.on('touchstart mouseover', function() { + $output.on('touchstart mouseover', function () { $output.stop().fadeTo('slow', 1); }); } }); } - } - + }; })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.rating.js b/web/modules/webform/js/webform.element.rating.js index 482a07bf6b..627e646046 100644 --- a/web/modules/webform/js/webform.element.rating.js +++ b/web/modules/webform/js/webform.element.rating.js @@ -28,6 +28,11 @@ var $rateit = $(this); var $input = $($rateit.attr('data-rateit-backingfld')); + // Rateit only initialize inputs on load. + if (document.readyState === 'complete') { + $rateit.rateit(); + } + // Update the RateIt widget when the input's value has changed. // @see webform.states.js $input.on('change', function () { diff --git a/web/modules/webform/js/webform.element.select2.js b/web/modules/webform/js/webform.element.select2.js index 39698e2dcc..fc7a7a382c 100644 --- a/web/modules/webform/js/webform.element.select2.js +++ b/web/modules/webform/js/webform.element.select2.js @@ -28,32 +28,59 @@ .once('webform-select2') // http://stackoverflow.com/questions/14313001/select2-not-calculating-resolved-width-correctly-if-select-is-hidden .css('width', '100%') - .select2(Drupal.webform.select2.options); - - - /** - * ISSUE: - * Hiding/showing element via #states API cause select2 dropdown to appear in the wrong position. - * - * WORKAROUND: - * Close (aka hide) select2 dropdown when #states API hides or shows an element. - * - * Steps to reproduce: - * - Add custom 'Submit button(s)' - * - Hide submit button - * - Save - * - Open 'Submit button(s)' dialog - * - * Dropdown body is positioned incorrectly when dropdownParent isn't statically positioned. - * @see https://github.com/select2/select2/issues/3303 - */ - $(function () { - $(document).on('state:visible state:visible-slide', function (e) { - $('select.select2-hidden-accessible').select2('close'); + .each(function () { + var $select = $(this); + + var options = $.extend({}, Drupal.webform.select2.options); + if ($select.data('placeholder')) { + options.placeholder = $select.data('placeholder'); + if (!$select.prop('multiple')) { + // Allow single option to be deselected. + options.allowClear = true; + } + } + + $select.select2(options); }); - }); } }; + /** + * ISSUE: + * Hiding/showing element via #states API cause select2 dropdown to appear in the wrong position. + * + * WORKAROUND: + * Close (aka hide) select2 dropdown when #states API hides or shows an element. + * + * Steps to reproduce: + * - Add custom 'Submit button(s)' + * - Hide submit button + * - Save + * - Open 'Submit button(s)' dialog + * + * Dropdown body is positioned incorrectly when dropdownParent isn't statically positioned. + * @see https://github.com/select2/select2/issues/3303 + */ + $(function () { + if ($.fn.select2) { + $(document).on('state:visible state:visible-slide', function (e) { + $('select.select2-hidden-accessible').select2('close'); + }); + } + + // Select2 search broken inside jQuery UI 1.10.x modal Dialog. + // @see https://github.com/select2/select2/issues/1246 + if ($.ui && $.ui.dialog && $.ui.dialog.prototype._allowInteraction) { + var ui_dialog_interaction = $.ui.dialog.prototype._allowInteraction; + $.ui.dialog.prototype._allowInteraction = function(e) { + if ($(e.target).closest('.select2-dropdown').length) { + return true; + } + return ui_dialog_interaction.apply(this, arguments); + }; + } + }); + + })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.signature.js b/web/modules/webform/js/webform.element.signature.js index f00d71dca7..38bf87e80a 100644 --- a/web/modules/webform/js/webform.element.signature.js +++ b/web/modules/webform/js/webform.element.signature.js @@ -23,6 +23,7 @@ return; } + $(context).find('input.js-webform-signature').once('webform-signature').each(function () { var $input = $(this); var value = $input.val(); @@ -30,12 +31,17 @@ var $canvas = $wrapper.find('canvas'); var $button = $wrapper.find(':button, :submit'); var canvas = $canvas[0]; + + var calculateDimensions = function () { + $canvas.attr('width', $wrapper.width()); + $canvas.attr('height', $wrapper.width() / 3); + }; + // Set height. $canvas.attr('width', $wrapper.width()); $canvas.attr('height', $wrapper.width() / 3); $(window).resize(function () { - $canvas.attr('width', $wrapper.width()); - $canvas.attr('height', $wrapper.width() / 3); + calculateDimensions(); // Resizing clears the canvas so we need to reset the signature pad. signaturePad.clear(); @@ -61,17 +67,23 @@ // Set reset handler. $button.on('click', function () { signaturePad.clear(); - $input.val(); + $input.val(''); this.blur(); return false; }); // Input onchange clears signature pad if value is empty. + // Onchange events handlers are triggered when a webform is + // hidden or shown. // @see webform.states.js + // @see triggerEventHandlers() $input.on('change', function () { if (!$input.val()) { signaturePad.clear(); } + setTimeout(function () { + calculateDimensions(); + }, 1); }); // Turn signature pad off/on when the input is disabled/enabled. diff --git a/web/modules/webform/js/webform.element.states.js b/web/modules/webform/js/webform.element.states.js new file mode 100644 index 0000000000..1f59bfb5c9 --- /dev/null +++ b/web/modules/webform/js/webform.element.states.js @@ -0,0 +1,102 @@ +/** + * @file + * JavaScript behaviors for element #states. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Element #states builder. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformElementStates = { + attach: function (context) { + $(context).find('.webform-states-table--condition').once('webform-element-states-condition').each(function () { + var $condition = $(this); + var $selector = $condition.find('.webform-states-table--selector select'); + var $value = $condition.find('.webform-states-table--value input'); + var $trigger = $condition.find('.webform-states-table--trigger select'); + + // Initialize autocompletion. + $value.autocomplete({minLength: 0}).on('focus', function () { + $value.autocomplete('search', ''); + }); + + // Initialize trigger and selector. + $trigger.on('change', function () {$selector.change();}); + + $selector.on('change', function () { + var selector = $selector.val(); + var sourceKey = drupalSettings.webformElementStates.selectors[selector]; + var source = drupalSettings.webformElementStates.sources[sourceKey]; + var notPattern = ($trigger.val().indexOf('pattern') === -1); + if (source && notPattern) { + // Enable autocompletion. + $value + .autocomplete('option', 'source', source) + .addClass('form-autocomplete'); + } + else { + // Disable autocompletion. + $value + .autocomplete('option', 'source', []) + .removeClass('form-autocomplete'); + } + }).change(); + }); + + // If the states:state is required or optional the required checkbox + // should be checked and disabled. + var $state = $(context).find('.webform-states-table--state select'); + if ($state.length) { + $state.once('webform-element-states-state') + .on('change', toggleRequiredCheckbox); + toggleRequiredCheckbox(); + } + } + }; + + /** + * Track required checked state. + * + * @type {null|boolean} + */ + var requiredChecked = null; + + /** + * Toggle the required checkbox when states:state is required or optional. + */ + function toggleRequiredCheckbox() { + var $input = $('input[name="properties[required]"]'); + if (!$input.length) { + return; + } + + // Determine if any states:state is required or optional. + var required = false; + $('.webform-states-table--state select').each(function () { + var value = $(this).val(); + if (value === 'required' || value === 'optional') { + required = true; + } + }); + + if (required) { + requiredChecked = $input.prop('checked'); + $input.attr('disabled', true); + $input.prop('checked', true); + } + else { + $input.attr('disabled', false); + if (requiredChecked !== null) { + $input.prop('checked', requiredChecked); + requiredChecked = null; + } + } + $input.change(); + } + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/webform/js/webform.element.tableselect.js b/web/modules/webform/js/webform.element.tableselect.js new file mode 100644 index 0000000000..45751a98cc --- /dev/null +++ b/web/modules/webform/js/webform.element.tableselect.js @@ -0,0 +1,66 @@ +/** + * @file + * JavaScript behaviors for tableselect enhancements. + * + * @see core/misc/tableselect.es6.js + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize and tweak webform tableselect behavior. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformTableSelect = { + attach: function (context) { + $(context) + .find('table.js-webform-tableselect') + .once('webform-tableselect') + .each(Drupal.webformTableSelect); + } + }; + + /** + * Callback used in {@link Drupal.behaviors.tableSelect}. + */ + Drupal.webformTableSelect = function () { + var $table = $(this); + + // Set default table rows to .selected class. + $table.find('tr').each(function () { + // Set table row selected for checkboxes. + var $tr = $(this); + if ($tr.find('input[type="checkbox"]:checked').length && !$tr.hasClass('selected')) { + $tr.addClass('selected'); + } + }); + + // Add .selected class event handler to all tableselect elements. + // Currently .selected is only added to tables with .select-all. + if ($table.find('th.select-all').length === 0) { + $table.find('td input[type="checkbox"]:enabled').on('click', function () { + $(this).closest('tr').toggleClass('selected', this.checked); + }); + } + + // Add click event handler to the table row that toggles the + // checkbox or radio. + $table.find('tr').on('click', function (event) { + if ($.inArray(event.target.tagName, ['A', 'BUTTON', 'INPUT', 'SELECT']) !== -1) { + return true; + } + + var $tr = $(this); + var $checkbox = $tr.find('td input[type="checkbox"]:enabled, td input[type="radio"]:enabled'); + if ($checkbox.length === 0) { + return true; + } + + $checkbox.click(); + }); + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.telephone.js b/web/modules/webform/js/webform.element.telephone.js index c60d2f65c5..c8e0ecfbd1 100644 --- a/web/modules/webform/js/webform.element.telephone.js +++ b/web/modules/webform/js/webform.element.telephone.js @@ -30,9 +30,11 @@ var $error = $('<div class="form-item--error-message">' + Drupal.t('Invalid phone number') + '</div>').hide(); $telephone.closest('.js-form-item').append($error); - // @todo: Figure out how to lazy load utilsScript (build/js/utils.js). - // @see https://github.com/jackocnr/intl-tel-input#utilities-script var options = $.extend({ + // The utilsScript is fetched when the page has finished. + // @see \Drupal\webform\Plugin\WebformElement\Telephone::prepare + // @see https://github.com/jackocnr/intl-tel-input + utilsScript: drupalSettings.webform.intlTelInput.utilsScript, nationalMode: false, initialCountry: $telephone.attr('data-webform-telephone-international-initial-country') || '' }, Drupal.webform.intlTelInput.options); diff --git a/web/modules/webform/js/webform.element.terms_of_service.js b/web/modules/webform/js/webform.element.terms_of_service.js index e2f2387d9c..55535c5813 100644 --- a/web/modules/webform/js/webform.element.terms_of_service.js +++ b/web/modules/webform/js/webform.element.terms_of_service.js @@ -21,10 +21,11 @@ attach: function (context) { $(context).find('.js-form-type-webform-terms-of-service').once('webform-terms-of-service').each(function () { var $element = $(this); - var type = $element.attr('data-webform-terms-of-service-type'); - + var $a = $element.find('label a'); var $details = $element.find('.webform-terms-of-service-details'); + var type = $element.attr('data-webform-terms-of-service-type'); + // Initialize the modal. if (type === 'modal') { // Move details title to attribute. @@ -43,15 +44,38 @@ $details.dialog(options); } - $element.find('label a').click(function (event) { + // Add aria-* attributes. + if (type !== 'modal') { + $a.attr({ + 'aria-expanded': false, + 'aria-controls': $details.attr('id') + }); + } + + // Set event handlers. + $a.click(openDetails) + .on('keydown', function (event) { + // Space or Return. + if (event.which === 32 || event.which === 13) { + openDetails(event); + } + }); + + function openDetails(event) { if (type === 'modal') { $details.dialog('open'); } else { - $details.slideToggle(); + var expanded = ($a.attr('aria-expanded') === 'true'); + + // Toggle `aria-expanded` attributes on link. + $a.attr('aria-expanded', !expanded); + + // Toggle details. + $details[expanded ? 'slideUp' : 'slideDown'](); } event.preventDefault(); - }); + } }); } }; diff --git a/web/modules/webform/js/webform.element.time.js b/web/modules/webform/js/webform.element.time.js index 9b987075e2..2ca0c7b51a 100644 --- a/web/modules/webform/js/webform.element.time.js +++ b/web/modules/webform/js/webform.element.time.js @@ -31,7 +31,7 @@ // Skip if time inputs are supported by the browser and input is not a text field. // @see \Drupal\webform\Element\WebformDatetime - if (window.Modernizr && Modernizr.inputtypes.time === true && $input.attr('type') !== 'text') { + if (window.Modernizr && Modernizr.inputtypes && Modernizr.inputtypes.time === true && $input.attr('type') !== 'text') { return; } @@ -62,6 +62,6 @@ $input.timepicker(options); }); } - } + }; })(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.element.toggle.js b/web/modules/webform/js/webform.element.toggle.js index d366b30a69..e6f146852c 100644 --- a/web/modules/webform/js/webform.element.toggle.js +++ b/web/modules/webform/js/webform.element.toggle.js @@ -43,10 +43,10 @@ // Trigger change event for #states API. // @see Drupal.states.Trigger.states.checked.change - $toggle.on('toggle', function() { - $checkbox.trigger("change"); + $toggle.on('toggle', function () { + $checkbox.trigger('change'); }); - + // If checkbox is disabled then add the .disabled class to the toggle. if ($checkbox.attr('disabled') || $checkbox.attr('readonly')) { $toggle.addClass('disabled'); @@ -66,7 +66,7 @@ var $wrapper = $toggle.parent(); var $checkbox = $wrapper.find('input[type="checkbox"]'); var isDisabled = ($checkbox.attr('disabled') || $checkbox.attr('readonly')); - (isDisabled) ? $toggle.addClass('disabled') : $toggle.removeClass('disabled'); + $toggle[isDisabled ? 'disabled' : 'disabled'](); }); }); } diff --git a/web/modules/webform/js/webform.filter.js b/web/modules/webform/js/webform.filter.js new file mode 100644 index 0000000000..aee14d1848 --- /dev/null +++ b/web/modules/webform/js/webform.filter.js @@ -0,0 +1,145 @@ +/** + * @file + * JavaScript behaviors for filter by text. + */ + +(function ($, Drupal, debounce) { + + 'use strict'; + + /** + * Filters the webform element list by a text input search string. + * + * The text input will have the selector `input.webform-form-filter-text`. + * + * The target element to do searching in will be in the selector + * `input.webform-form-filter-text[data-element]` + * + * The text source where the text should be found will have the selector + * `.webform-form-filter-text-source` + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the webform element filtering. + */ + Drupal.behaviors.webformFilterByText = { + attach: function (context, settings) { + $('input.webform-form-filter-text', context).once('webform-form-filter-text').each(function () { + var $input = $(this); + var $table = $($input.data('element')); + var $summary = $($input.data('summary')); + var $noResults = $($input.data('no-results')); + var $details = $table.closest('details'); + var $filterRows; + + var focusInput = $input.data('focus') || 'true'; + var sourceSelector = $input.data('source') || '.webform-form-filter-text-source'; + var parentSelector = $input.data('parent') || 'tr'; + var selectedSelector = $input.data('selected') || ''; + + var hasDetails = $details.length; + var totalItems; + var args = { + '@item': $input.data('item-singlular') || Drupal.t('item'), + '@items': $input.data('item-plural') || Drupal.t('items'), + '@total': null + }; + + if ($table.length) { + $filterRows = $table.find(sourceSelector); + $input + .attr('autocomplete', 'off') + .on('keyup', debounce(filterElementList, 200)) + .keyup(); + + // Make sure the filter input is always focused. + if (focusInput === 'true') { + setTimeout(function () {$input.focus();}); + } + } + + /** + * Filters the webform element list. + * + * @param {jQuery.Event} e + * The jQuery event for the keyup event that triggered the filter. + */ + function filterElementList(e) { + var query = $(e.target).val().toLowerCase(); + + // Filter if the length of the query is at least 2 characters. + if (query.length >= 2) { + // Reset count. + totalItems = 0; + if ($details.length) { + $details.hide(); + } + $filterRows.each(toggleEntry); + + // Announce filter changes. + // @see Drupal.behaviors.blockFilterByText + Drupal.announce(Drupal.formatPlural( + totalItems, + '1 @item is available in the modified list.', + '@total @items are available in the modified list.', + args + )); + } + else { + totalItems = $filterRows.length; + $filterRows.each(function (index) { + $(this).closest(parentSelector).show(); + if ($details.length) { + $details.show(); + } + }); + } + + // Set total. + args['@total'] = totalItems; + + // Hide/show no results. + $noResults[totalItems ? 'hide' : 'show'](); + + // Update summary. + if ($summary.length) { + $summary.html(Drupal.formatPlural( + totalItems, + '1 @item', + '@total @items', + args + )); + $summary[totalItems ? 'show' : 'hide'](); + } + + /** + * Shows or hides the webform element entry based on the query. + * + * @param {number} index + * The index in the loop, as provided by `jQuery.each` + * @param {HTMLElement} label + * The label of the webform. + */ + function toggleEntry(index, label) { + var $label = $(label); + var $row = $label.closest(parentSelector); + + var textMatch = $label.text().toLowerCase().indexOf(query) !== -1; + var isSelected = (selectedSelector && $row.find(selectedSelector).length) ? true : false; + + var isVisible = textMatch || isSelected; + $row.toggle(isVisible); + if (isVisible) { + totalItems++; + if (hasDetails) { + $row.closest('details').show(); + } + } + } + } + }); + } + }; + +})(jQuery, Drupal, Drupal.debounce); diff --git a/web/modules/webform/js/webform.form.js b/web/modules/webform/js/webform.form.js index 5ac9a6788f..3b1f182a03 100644 --- a/web/modules/webform/js/webform.form.js +++ b/web/modules/webform/js/webform.form.js @@ -7,6 +7,28 @@ 'use strict'; + /** + * Remove single submit event listener. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for removing single submit event listener. + * + * @see Drupal.behaviors.formSingleSubmit + */ + Drupal.behaviors.webformRemoveFormSingleSubmit = { + attach: function attach() { + function onFormSubmit(e) { + var $form = $(e.currentTarget); + $form.removeAttr('data-drupal-form-submit-last'); + } + $('body') + .once('webform-single-submit') + .on('submit.singleSubmit', 'form.webform-remove-single-submit', onFormSubmit); + } + }; + /** * Autofocus first input. * @@ -37,8 +59,7 @@ .not(':button, :submit, :reset, :image, :file') .once('webform-disable-autosubmit') .on('keyup keypress', function (e) { - var keyCode = e.keyCode || e.which; - if (keyCode === 13) { + if (e.which === 13) { e.preventDefault(); return false; } @@ -53,12 +74,15 @@ * * @prop {Drupal~behaviorAttach} attach * Attaches the behavior for the skipping client-side validation. + * + * @deprecated in Webform 8.x-5.x and will be removed in Webform 8.x-6.x. + * Use 'formnovalidate' attribute instead. */ Drupal.behaviors.webformSubmitNoValidate = { attach: function (context) { - $(context).find(':submit.js-webform-novalidate').once('webform-novalidate').on('click', function () { - $(this.form).attr('novalidate', 'novalidate'); - }); + $(context).find(':submit.js-webform-novalidate') + .once('webform-novalidate') + .attr('formnovalidate', 'formnovalidate'); } }; @@ -80,30 +104,38 @@ }; /** - * Custom required error message. + * Custom required and pattern validation error messages. * * @type {Drupal~behavior} * * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior for the webform custom required error message. + * Attaches the behavior for the webform custom required and pattern + * validation error messages. * * @see http://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message - */ + **/ Drupal.behaviors.webformRequiredError = { attach: function (context) { - $(context).find(':input[data-webform-required-error]').once('webform-required-error') - .on('invalid', function() { + $(context).find(':input[data-webform-required-error], :input[data-webform-pattern-error]').once('webform-required-error') + .on('invalid', function () { this.setCustomValidity(''); - if (!this.valid) { + if (this.valid) { + return; + } + + if (this.validity.patternMismatch && $(this).attr('data-webform-pattern-error')) { + this.setCustomValidity($(this).attr('data-webform-pattern-error')); + } + else if (this.validity.valueMissing && $(this).attr('data-webform-required-error')) { this.setCustomValidity($(this).attr('data-webform-required-error')); } }) - .on('input, change', function() { + .on('input, change', function () { // Find all related elements by name and reset custom validity. // This specifically applies to required radios and checkboxes. var name = $(this).attr('name'); - $(this.form).find(':input[name="' + name + '"]').each( - function() {this.setCustomValidity(''); + $(this.form).find(':input[name="' + name + '"]').each(function () { + this.setCustomValidity(''); }); }); } @@ -113,86 +145,9 @@ // custom validity. $(document).on('state:required', function (e) { $(e.target).filter('[data-webform-required-error]') - .each(function() {this.setCustomValidity('');}); + .each(function () {this.setCustomValidity('');}); }); - /** - * Filters the webform element list by a text input search string. - * - * The text input will have the selector `input.webform-form-filter-text`. - * - * The target element to do searching in will be in the selector - * `input.webform-form-filter-text[data-element]` - * - * The text source where the text should be found will have the selector - * `.webform-form-filter-text-source` - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior for the webform element filtering. - */ - Drupal.behaviors.webformFilterByText = { - attach: function (context, settings) { - var $input = $('input.webform-form-filter-text').once('webform-form-filter-text'); - var $table = $($input.attr('data-element')); - var $details = $table.closest('details'); - var $filter_rows; - - /** - * Filters the webform element list. - * - * @param {jQuery.Event} e - * The jQuery event for the keyup event that triggered the filter. - */ - function filterElementList(e) { - var query = $(e.target).val().toLowerCase(); - - /** - * Shows or hides the webform element entry based on the query. - * - * @param {number} index - * The index in the loop, as provided by `jQuery.each` - * @param {HTMLElement} label - * The label of the webform. - */ - function toggleEntry(index, label) { - var $label = $(label); - var $row = $label.closest('tr'); - var textMatch = $label.text().toLowerCase().indexOf(query) !== -1; - $row.toggle(textMatch); - if (textMatch && $details.length) { - $row.closest('details').show(); - } - } - - // Filter if the length of the query is at least 2 characters. - if (query.length >= 2) { - if ($details.length) { - $details.hide(); - } - $filter_rows.each(toggleEntry); - } - else { - $filter_rows.each(function (index) { - $(this).closest('tr').show(); - if ($details.length) { - $details.show(); - } - }); - } - } - - if ($table.length) { - $filter_rows = $table.find('.webform-form-filter-text-source'); - $input.on('keyup', filterElementList); - if ($input.val()) { - $input.keyup(); - } - } - } - }; - if (window.imceInput) { window.imceInput.processUrlInput = function (i, el) { var button = imceInput.createUrlButton(el.id, el.getAttribute('data-imce-type')); diff --git a/web/modules/webform/js/webform.form.submit_back.js b/web/modules/webform/js/webform.form.submit_back.js index 4d024af2f1..642c43b499 100644 --- a/web/modules/webform/js/webform.form.submit_back.js +++ b/web/modules/webform/js/webform.form.submit_back.js @@ -11,7 +11,7 @@ if (window.history && window.history.pushState) { window.history.pushState('', null, ''); window.onpopstate = function (event) { - $('#edit-wizard-prev, #edit-preview-prev').click(); + $('#edit-wizard-prev, #edit-preview-prev').click(); }; } diff --git a/web/modules/webform/js/webform.form.submit_once.js b/web/modules/webform/js/webform.form.submit_once.js index bc2dc2403c..ab8414b9d2 100644 --- a/web/modules/webform/js/webform.form.submit_once.js +++ b/web/modules/webform/js/webform.form.submit_once.js @@ -19,14 +19,18 @@ attach: function (context) { $('.js-webform-submit-once', context).each(function () { var $form = $(this); - $form.removeAttr('webform-submitted'); - $form.find('.form-actions :submit').removeAttr('webform-clicked'); + // Remove data-webform-submitted. + $form.removeData('webform-submitted'); + // Remove .js-webform-submit-clicked. + $form.find('.form-actions :submit').removeClass('js-webform-submit-clicked'); // Track which submit button was clicked. // @see http://stackoverflow.com/questions/5721724/jquery-how-to-get-which-button-was-clicked-upon-form-submission $form.find('.form-actions :submit').click(function () { - $form.find('.form-actions :submit').removeAttr('webform-clicked'); - $(this).attr('webform-clicked', 'true'); + $form.find('.form-actions :submit') + .removeClass('js-webform-submit-clicked'); + $(this) + .addClass('js-webform-submit-clicked'); }); $(this).submit(function () { @@ -36,10 +40,10 @@ } // Track webform submitted. - if ($form.attr('webform-submitted')) { + if ($form.data('webform-submitted')) { return false; } - $form.attr('webform-submitted', 'true'); + $form.data('webform-submitted', 'true'); // Visually disable all submit buttons. // Submit buttons can't disabled because their op(eration) must to be posted back to the server. @@ -47,7 +51,7 @@ // Set the throbber progress indicator. // @see Drupal.Ajax.prototype.setProgressIndicatorThrobber - var $clickedButton = $form.find('.form-actions input[type=submit][webform-clicked=true]'); + var $clickedButton = $form.find('.form-actions :submit.js-webform-submit-clicked'); var $progress = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); $clickedButton.after($progress); }); diff --git a/web/modules/webform/js/webform.form.tabs.js b/web/modules/webform/js/webform.form.tabs.js index 63b34fd20d..d8ffaaa0df 100644 --- a/web/modules/webform/js/webform.form.tabs.js +++ b/web/modules/webform/js/webform.form.tabs.js @@ -27,27 +27,21 @@ */ Drupal.behaviors.webformFormTabs = { attach: function (context) { - // Set active tab and clear the location hash once it is set. - if (location.hash) { - var active = $('a[href="' + location.hash + '"]').data('tab-index'); - if (active !== undefined) { - Drupal.webform.formTabs.options.active = active; - location.hash = ''; - } - } - - $(context).find('div.webform-tabs').once('webform-tabs').each(function() { + $(context).find('div.webform-tabs').once('webform-tabs').each(function () { var $tabs = $(this); var options = jQuery.extend({}, Drupal.webform.formTabs.options); - // Set active tab from data-tab-active attribute. - var tab_name = $tabs.attr('data-tab-active'); - if (tab_name) { - options.active = $('a[href="#' + tab_name + '"]').data('tab-index'); + // Set active tab and clear the location hash once it is set. + if (location.hash) { + var active = $('a[href="' + location.hash + '"]').data('tab-index'); + if (typeof active !== 'undefined') { + options.active = active; + location.hash = ''; + } } $tabs.tabs(options); - }) + }); } }; diff --git a/web/modules/webform/js/webform.form.unsaved.js b/web/modules/webform/js/webform.form.unsaved.js index c6e2c03003..2549847864 100644 --- a/web/modules/webform/js/webform.form.unsaved.js +++ b/web/modules/webform/js/webform.form.unsaved.js @@ -18,11 +18,16 @@ * Attaches the behavior for unsaved changes. */ Drupal.behaviors.webformUnsaved = { + clear: function () { + // Allow Ajax refresh/redirect to clear unsaved flag. + // @see Drupal.AjaxCommands.prototype.webformRefresh + unsaved = false; + }, attach: function (context) { - // Look for the 'data-webform-unsaved' attribute which indicates that the - // multi-step webform has unsaved data. + // Look for the 'data-webform-unsaved' attribute which indicates that + // a multi-step webform has unsaved data. // @see \Drupal\webform\WebformSubmissionForm::buildForm - if ($('.js-webform-unsaved[data-webform-unsaved]').length) { + if ($('.js-webform-unsaved[data-webform-unsaved]').once('data-webform-unsaved').length) { unsaved = true; } else { @@ -36,9 +41,38 @@ }); } - $('.js-webform-unsaved button, .js-webform-unsaved input[type="submit"]', context).once('webform-unsaved').on('click', function () { + $('.js-webform-unsaved button, .js-webform-unsaved input[type="submit"]', context).once('webform-unsaved').on('click', function (event) { + // For reset button we must confirm unsaved changes before the + // before unload event handler. + if ($(this).hasClass('webform-button--reset') && unsaved) { + if (!window.confirm(Drupal.t('Changes you made may not be saved.') + '\n\n' + Drupal.t('Press OK to leave this page or Cancel to stay.'))) { + return false; + } + } + unsaved = false; }); + + // Add submit handler to form.beforeSend. + // Update Drupal.Ajax.prototype.beforeSend only once. + if (typeof Drupal.Ajax !== 'undefined' && typeof Drupal.Ajax.prototype.beforeSubmitWebformUnsavedOriginal === 'undefined') { + Drupal.Ajax.prototype.beforeSubmitWebformUnsavedOriginal = Drupal.Ajax.prototype.beforeSubmit; + Drupal.Ajax.prototype.beforeSubmit = function (form_values, element_settings, options) { + unsaved = false; + return this.beforeSubmitWebformUnsavedOriginal(); + }; + } + + // Track all CKEditor change events. + // @see https://ckeditor.com/old/forums/Support/CKEditor-jQuery-change-event + if (window.CKEDITOR && !CKEDITOR.webformUnsaved) { + CKEDITOR.webformUnsaved = true; + CKEDITOR.on('instanceCreated', function (event) { + event.editor.on('change', function (evt) { + unsaved = true; + }); + }); + } } }; @@ -65,9 +99,9 @@ } $('a').bind('click', function (evt) { var href = $(evt.target).closest('a').attr('href'); - if (href !== undefined && !(href.match(/^#/) || href.trim() === '')) { + if (typeof href !== 'undefined' && !(href.match(/^#/) || href.trim() === '')) { if ($(window).triggerHandler('beforeunload')) { - if (!confirm(Drupal.t('Changes you made may not be saved.') + '\n\n' + Drupal.t('Press OK to leave this page or Cancel to stay.'))) { + if (!window.confirm(Drupal.t('Changes you made may not be saved.') + '\n\n' + Drupal.t('Press OK to leave this page or Cancel to stay.'))) { return false; } } diff --git a/web/modules/webform/js/webform.form.wizard.js b/web/modules/webform/js/webform.form.wizard.js deleted file mode 100644 index 5d310978cb..0000000000 --- a/web/modules/webform/js/webform.form.wizard.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file - * JavaScript behaviors for webform wizard. - */ - -(function ($, Drupal) { - - 'use strict'; - - /** - * Tracks the wizard's current page in the URL. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Tracks the wizard's current page in the URL. - */ - Drupal.behaviors.webformWizardTrackPage = { - attach: function (context) { - // Make sure on page load or Ajax refresh the location ?page is correct - // since conditional logic can skip pages. - // Note: window.history is only supported by IE 10+ and all other browsers. - if (window.history && history.replaceState) { - $('form[data-webform-wizard-current-page]', context).once('webform-wizard-current-page').each(function () { - var page = $(this).attr('data-webform-wizard-current-page'); - history.replaceState(null, null, window.location.toString().replace(/\?.+$/, '') + '?page=' + page); - }); - } - - // When paging next and back update the URL so that Drupal knows what - // the expected page name or index is going to be. - // NOTE: If conditional wizard page logic is configured the - // expected page name or index may not be accurate. - $(':button[data-webform-wizard-page], :submit[data-webform-wizard-page]', context).once('webform-wizard-page').on('click', function() { - var page = $(this).attr('data-webform-wizard-page'); - this.form.action = this.form.action.replace(/\?.+$/, '') + '?page=' + page; - }); - } - }; - - -})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.imce.js b/web/modules/webform/js/webform.imce.js index e6d3f383f9..7f59b38fa6 100644 --- a/web/modules/webform/js/webform.imce.js +++ b/web/modules/webform/js/webform.imce.js @@ -9,6 +9,11 @@ /** * Override processUrlInput to place the 'Open File Browser' links after the target element. + * + * @param {int} i + * Element's index. + * @param {element} el + * A element. */ window.imceInput.processUrlInput = function (i, el) { var button = imceInput.createUrlButton(el.id, el.getAttribute('data-imce-type')); diff --git a/web/modules/webform/js/webform.jquery.ui.dialog.js b/web/modules/webform/js/webform.jquery.ui.dialog.js new file mode 100644 index 0000000000..479fc2be0f --- /dev/null +++ b/web/modules/webform/js/webform.jquery.ui.dialog.js @@ -0,0 +1,52 @@ +/** + * @file + * JavaScript behaviors to fix jQuery UI dialogs. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Ensure that ckeditor has focus when displayed inside of jquery-ui dialog widget + * + * @see http://stackoverflow.com/questions/20533487/how-to-ensure-that-ckeditor-has-focus-when-displayed-inside-of-jquery-ui-dialog + */ + var _allowInteraction = $.ui.dialog.prototype._allowInteraction; + $.ui.dialog.prototype._allowInteraction = function (event) { + if ($(event.target).closest('.cke_dialog').length) { + return true; + } + return _allowInteraction.apply(this, arguments); + }; + + /** + * Attaches webform dialog behaviors. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches event listeners for webform dialogs. + */ + Drupal.behaviors.webformDialogEvents = { + attach: function () { + $(window).once('webform-dialog').on({ + 'dialog:aftercreate': function (event, dialog, $element, settings) { + setTimeout(function () { + var hasFocus = $element.find('[autofocus]:tabbable'); + if (!hasFocus.length) { + // Move focus to first input which is not a button. + hasFocus = $element.find(':input:tabbable:not(:button)'); + } + if (!hasFocus.length) { + // Move focus to close dialog button. + hasFocus = $element.parent().find('.ui-dialog-titlebar-close'); + } + hasFocus.eq(0).focus(); + }); + } + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.off-canvas.js b/web/modules/webform/js/webform.off-canvas.js new file mode 100644 index 0000000000..1ce8a28278 --- /dev/null +++ b/web/modules/webform/js/webform.off-canvas.js @@ -0,0 +1,39 @@ +/** + * @file + * JavaScript behaviors for webform off-canvas dialogs. + * + * @see misc/dialog/off-canvas.js + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Attaches webform off-canvas behaviors. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches event listeners to window for off-canvas dialogs. + */ + Drupal.behaviors.webformOffCanvasEvents = { + attach: function () { + // Resize seven.theme tabs when off-canvas dialog opened and closed. + // @see core/themes/seven/js/nav-tabs.js + $(window).once('webform-off-canvas').on({ + 'dialog:aftercreate': function (event, dialog, $element, settings) { + if (Drupal.offCanvas.isOffCanvas($element)) { + $(window).trigger('resize.tabs'); + } + }, + 'dialog:afterclose': function (event, dialog, $element, settings) { + if (Drupal.offCanvas.isOffCanvas($element)) { + $(window).trigger('resize.tabs'); + } + } + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.states.js b/web/modules/webform/js/webform.states.js index 3daae913bd..1e1ee9c543 100644 --- a/web/modules/webform/js/webform.states.js +++ b/web/modules/webform/js/webform.states.js @@ -20,7 +20,7 @@ * @param {string} data * The data attribute name. * - * @returns {boolean} + * @return {boolean} * TRUE if an element has a specified data attribute. */ $.fn.hasData = function (data) { @@ -30,7 +30,7 @@ /** * Check if element is within the webform or not. * - * @returns {boolean} + * @return {boolean} * TRUE if element is within the webform. */ $.fn.isWebform = function () { @@ -45,20 +45,56 @@ return this.val() === ''; }; + // Apply solution included in #1962800 patch. + // Issue #1962800: Form #states not working with literal integers as + // values in IE11. + // @see https://www.drupal.org/project/drupal/issues/1962800 + // @see https://www.drupal.org/files/issues/core-states-not-working-with-integers-ie11_1962800_46.patch + // + // This issue causes pattern, less than, and greater than support to break. + // @see https://www.drupal.org/project/webform/issues/2981724 + var states = Drupal.states; + Drupal.states.Dependent.prototype.compare = function compare(reference, selector, state) { + var value = this.values[selector][state.name]; + + var name = reference.constructor.name; + if (!name) { + name = $.type(reference); + + name = name.charAt(0).toUpperCase() + name.slice(1); + } + if (name in states.Dependent.comparisons) { + return states.Dependent.comparisons[name](reference, value); + } + + if (reference.constructor.name in states.Dependent.comparisons) { + return states.Dependent.comparisons[reference.constructor.name](reference, value); + } + + return _compare2(reference, value); + }; + function _compare2(a, b) { + if (a === b) { + return typeof a === 'undefined' ? a : true; + } + + return typeof a === 'undefined' || typeof b === 'undefined'; + } + // Adds pattern, less than, and greater than support to #state API. // @see http://drupalsun.com/julia-evans/2012/03/09/extending-form-api-states-regular-expressions Drupal.states.Dependent.comparisons.Object = function (reference, value) { if ('pattern' in reference) { - return (new RegExp(reference.pattern)).test(value); + return (new RegExp(reference['pattern'])).test(value); } else if ('!pattern' in reference) { return !((new RegExp(reference['!pattern'])).test(value)); } else if ('less' in reference) { - return (value !== '' && reference.less > value); + return (value !== '' && parseFloat(reference['less']) > parseFloat(value)); } else if ('greater' in reference) { - return (value !== '' && reference.greater < value); + return (value !== '' && parseFloat(reference['greater']) < parseFloat(value)); } else { return reference.indexOf(value) !== false; @@ -83,12 +119,14 @@ // @see Issue #2938414: Checkboxes don't support #states required // @see Issue #2731991: Setting required on radios marks all options required. // @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. - if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes')) { + // Fix #required for fieldsets. + // @see Issue #2977569: Hidden fieldsets that become visible with conditional logic cannot be made required. + if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes, fieldset')) { if (e.value) { - $target.find('legend span').addClass('js-form-required form-required'); + $target.find('legend span.fieldset-legend:not(.visually-hidden)').addClass('js-form-required form-required'); } else { - $target.find('legend span').removeClass('js-form-required form-required'); + $target.find('legend span.fieldset-legend:not(.visually-hidden)').removeClass('js-form-required form-required'); } } @@ -124,6 +162,11 @@ .unbind('click', checkboxRequiredhandler); } } + + // Issue #2986017: Fieldsets shouldn't have required attribute. + if ($target.is('fieldset')) { + $target.removeAttr('required aria-required'); + } } }); @@ -153,11 +196,11 @@ } }); - $document.bind('state:visible-slide', function(e) { + $document.bind('state:visible-slide', function (e) { if (e.trigger && $(e.target).isWebform()) { var effect = e.value ? 'slideDown' : 'slideUp'; var duration = Drupal.webform.states[effect].duration; - $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect ](duration); + $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect](duration); } }); Drupal.states.State.aliases['invisible-slide'] = '!visible-slide'; @@ -169,11 +212,11 @@ $(e.target) .prop('disabled', e.value) .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) - .find('select, input, textarea').prop('disabled', e.value); + .find('select, input, textarea, button').prop('disabled', e.value); // Trigger webform:disabled. $(e.target).trigger('webform:disabled') - .find('select, input, textarea').trigger('webform:disabled'); + .find('select, input, textarea, button').trigger('webform:disabled'); } }); @@ -195,7 +238,7 @@ /** * Trigger an input's event handlers. * - * @param input + * @param {element} input * An input. */ function triggerEventHandlers(input) { @@ -215,7 +258,7 @@ .trigger('change', extraParameters) .trigger('blur', extraParameters); } - else if (type !== 'submit' && type !== 'button') { + else if (type !== 'submit' && type !== 'button' && type !== 'file') { $input .trigger('input', extraParameters) .trigger('change', extraParameters) @@ -228,7 +271,7 @@ /** * Backup an input's current value and required attribute * - * @param input + * @param {element} input * An input. */ function backupValueAndRequired(input) { @@ -262,7 +305,7 @@ /** * Restore an input's value and required attribute. * - * @param input + * @param {element} input * An input. */ function restoreValueAndRequired(input) { @@ -301,7 +344,7 @@ /** * Clear an input's value and required attributes. * - * @param input + * @param {element} input * An input. */ function clearValueAndRequired(input) { diff --git a/web/modules/webform/js/webform.tooltip.js b/web/modules/webform/js/webform.tooltip.js index 545821a239..15ff5346dc 100644 --- a/web/modules/webform/js/webform.tooltip.js +++ b/web/modules/webform/js/webform.tooltip.js @@ -14,16 +14,16 @@ var tooltipDefaultOptions = { // @see https://stackoverflow.com/questions/18231315/jquery-ui-tooltip-html-with-links - show: null, + show: {delay: 100}, close: function (event, ui) { ui.tooltip.hover( function () { $(this).stop(true).fadeTo(400, 1); }, function () { - $(this).fadeOut("400", function () { + $(this).fadeOut('400', function () { $(this).remove(); - }) + }); }); } }; @@ -47,14 +47,14 @@ $(context).find('.js-webform-tooltip-element').once('webform-tooltip-element').each(function () { var $element = $(this); - // Checkboxes, radios, buttons, toggles, etc... use fieldsets. + // Checkboxes, radios, buttons, toggles, etc… use fieldsets. // @see \Drupal\webform\Plugin\WebformElement\OptionsBase::prepare var $description; if ($element.is('fieldset')) { - $description = $element.find('> .fieldset-wrapper > .field-suffix .description.visually-hidden'); + $description = $element.find('> .fieldset-wrapper > .description > .webform-element-description.visually-hidden'); } else { - $description = $element.children('.description.visually-hidden'); + $description = $element.find('> .description > .webform-element-description.visually-hidden'); } var has_visible_input = $element.find(':input:not([type=hidden])').length; diff --git a/web/modules/webform/js/webform.wizard.pages.js b/web/modules/webform/js/webform.wizard.pages.js new file mode 100644 index 0000000000..805033ad1c --- /dev/null +++ b/web/modules/webform/js/webform.wizard.pages.js @@ -0,0 +1,61 @@ +/** + * @file + * JavaScript behaviors for webform wizard. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Link the wizard's previous pages. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Links the wizard's previous pages. + */ + Drupal.behaviors.webformWizardPagesLink = { + attach: function (context) { + $('.js-webform-wizard-pages-links', context).once('webform-wizard-pages-links').each(function () { + var $pages = $(this); + var $form = $pages.closest('form'); + + var hasProgressLink = $pages.data('wizard-progress-link'); + var hasPreviewLink = $pages.data('wizard-preview-link'); + + $pages.find('.js-webform-wizard-pages-link').each(function () { + var $button = $(this); + var title = $button.attr('title'); + var page = $button.data('webform-page'); + + // Link progress marker and title. + if (hasProgressLink) { + var $progress = $form.find('.webform-progress [data-webform-page="' + page + '"]'); + $progress.find('.progress-marker, .progress-title') + .attr({ + 'role': 'link', + 'title': title, + 'aria-label': title + }) + .click(function () { + $button.click(); + }); + // Only allow the marker to be tabbable. + $progress.find('.progress-marker').attr('tabindex', 0); + } + + // Move button to preview page div container with [data-webform-page]. + // @see \Drupal\webform\Plugin\WebformElement\WebformWizardPage::formatHtmlItem + if (hasPreviewLink) { + $form + .find('.webform-preview [data-webform-page="' + page + '"]') + .append($button) + .show(); + } + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/js/webform.wizard.track.js b/web/modules/webform/js/webform.wizard.track.js new file mode 100644 index 0000000000..aa3974cdcf --- /dev/null +++ b/web/modules/webform/js/webform.wizard.track.js @@ -0,0 +1,67 @@ +/** + * @file + * JavaScript behaviors for webform wizard. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Tracks the wizard's current page in the URL. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Tracks the wizard's current page in the URL. + */ + Drupal.behaviors.webformWizardTrackPage = { + attach: function (context) { + // Make sure on page load or Ajax refresh the location ?page is correct + // since conditional logic can skip pages. + // Note: window.history is only supported by IE 10+. + if (window.history && window.history.replaceState) { + // Track the form's current page for 8.5.x and below. + // @todo Remove the below code once only 8.6.x is supported. + // @see https://www.drupal.org/project/drupal/issues/2508796 + $('form[data-webform-wizard-current-page]', context) + .once('webform-wizard-current-page') + .each(function () { + trackPage(this); + }); + + // Track the form's current page for 8.6.x and above. + if ($(context).hasData('webform-wizard-current-page')) { + trackPage(context); + } + } + + // When paging next and back update the URL so that Drupal knows what + // the expected page name or index is going to be. + // NOTE: If conditional wizard page logic is configured the + // expected page name or index may not be accurate. + $(':button[data-webform-wizard-page], :submit[data-webform-wizard-page]', context).once('webform-wizard-page').on('click', function () { + var page = $(this).attr('data-webform-wizard-page'); + this.form.action = this.form.action.replace(/\?.+$/, '') + '?page=' + page; + }); + + /** + * Append the form's current page data attribute to the browser's URL. + * + * @param {HTMLElement} form + * The form element. + */ + function trackPage(form) { + var $form = $(form); + // Make sure the form is visible before updating the URL. + if ($form.is(':visible')) { + var page = $form.attr('data-webform-wizard-current-page'); + var url = window.location.toString().replace(/\?.+$/, '') + + '?page=' + page; + window.history.replaceState(null, null, url); + } + } + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/modules/webform_access/config/schema/webform_access.entity.webform_access_type.schema.yml b/web/modules/webform/modules/webform_access/config/schema/webform_access.entity.webform_access_type.schema.yml new file mode 100644 index 0000000000..dcd532aa76 --- /dev/null +++ b/web/modules/webform/modules/webform_access/config/schema/webform_access.entity.webform_access_type.schema.yml @@ -0,0 +1,10 @@ +webform.webform_access_type.*: + type: config_entity + label: 'Access type' + mapping: + id: + type: string + label: 'Machine name' + label: + type: label + label: 'Label' diff --git a/web/modules/webform/modules/webform_access/config/schema/webform_access.schema.yml b/web/modules/webform/modules/webform_access/config/schema/webform_access.schema.yml new file mode 100644 index 0000000000..3dbd332c8d --- /dev/null +++ b/web/modules/webform/modules/webform_access/config/schema/webform_access.schema.yml @@ -0,0 +1,33 @@ +webform_access.webform_access_type.*: + type: config_entity + label: 'Access type' + mapping: + id: + type: string + label: 'Machine name' + label: + type: label + label: 'Label' + +webform_access.webform_access_group.*: + type: config_entity + label: 'Access group' + mapping: + id: + type: string + label: 'Machine name' + label: + type: label + label: 'Label' + description: + type: label + label: 'Administrative description' + type: + type: string + label: 'Type' + permissions: + type: sequence + label: 'Permissions' + sequence: + type: string + label: 'Permission' diff --git a/web/modules/webform/modules/webform_access/js/webform_access.admin.js b/web/modules/webform/modules/webform_access/js/webform_access.admin.js new file mode 100644 index 0000000000..026c651de7 --- /dev/null +++ b/web/modules/webform/modules/webform_access/js/webform_access.admin.js @@ -0,0 +1,36 @@ +/** + * @file + * JavaScript behaviors for webform access. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize webform access group administer permission toggle. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformAccessGroupPermissions = { + attach: function (context) { + $('#edit-permissions', context).once('webform-access-group-permissions').each(function () { + var $permissions = $(this); + var $checkbox = $permissions.find('input[name="permissions[administer]"]'); + + $checkbox.click(toggleAdminister); + if ($checkbox.prop('checked')) { + toggleAdminister(); + } + + function toggleAdminister() { + var checked = $checkbox.prop('checked'); + $permissions.find(':checkbox').prop('checked', checked); + $permissions.find(':checkbox:not([name="permissions[administer]"])').attr('disabled', checked); + } + }); + + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/modules/webform_access/src/Breadcrumb/WebformAccessBreadcrumbBuilder.php b/web/modules/webform/modules/webform_access/src/Breadcrumb/WebformAccessBreadcrumbBuilder.php new file mode 100644 index 0000000000..ec7a068f28 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Breadcrumb/WebformAccessBreadcrumbBuilder.php @@ -0,0 +1,92 @@ +<?php + +namespace Drupal\webform_access\Breadcrumb; + +use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface; +use Drupal\Core\Breadcrumb\Breadcrumb; +use Drupal\Core\Link; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\Url; + +/** + * Provides a webform access breadcrumb builder. + */ +class WebformAccessBreadcrumbBuilder implements BreadcrumbBuilderInterface { + + use StringTranslationTrait; + + /** + * The current route's entity or plugin type. + * + * @var string + */ + protected $type; + + /** + * Constructs a WebformAccessBreadcrumbBuilder. + * + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation service. + */ + public function __construct(TranslationInterface $string_translation) { + $this->setStringTranslation($string_translation); + } + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $route_match) { + $route_name = $route_match->getRouteName(); + // All routes must begin or contain 'webform_access'. + if (strpos($route_name, 'webform_access') === FALSE) { + return FALSE; + } + $path = Url::fromRouteMatch($route_match)->toString(); + + if (strpos($path, 'admin/structure/webform/access/') === FALSE) { + return FALSE; + } + + if (strpos($path, 'admin/structure/webform/access/group/manage/') !== FALSE) { + $this->type = 'webform_access_group'; + } + elseif (strpos($path, 'admin/structure/webform/access/type/manage/') !== FALSE) { + $this->type = 'webform_access_type'; + } + else { + $this->type = 'webform_access'; + } + + return ($this->type) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match) { + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Administration'), 'system.admin')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Structure'), 'system.admin_structure')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Webforms'), 'entity.webform.collection')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Access'), 'entity.webform_access_group.collection')); + switch ($this->type) { + case 'webform_access_group': + $breadcrumb->addLink(Link::createFromRoute($this->t('Groups'), 'entity.webform_access_group.collection')); + break; + + case 'webform_access_type'; + $breadcrumb->addLink(Link::createFromRoute($this->t('Types'), 'entity.webform_access_type.collection')); + break; + } + + // This breadcrumb builder is based on a route parameter, and hence it + // depends on the 'route' cache context. + $breadcrumb->addCacheContexts(['route']); + + return $breadcrumb; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Entity/WebformAccessGroup.php b/web/modules/webform/modules/webform_access/src/Entity/WebformAccessGroup.php new file mode 100644 index 0000000000..fd3ae8220a --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Entity/WebformAccessGroup.php @@ -0,0 +1,220 @@ +<?php + +namespace Drupal\webform_access\Entity; + +use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\webform_access\WebformAccessGroupInterface; + +/** + * Defines the webform access group entity. + * + * @ConfigEntityType( + * id = "webform_access_group", + * label = @Translation("Webform access group"), + * label_collection = @Translation("Access groups"), + * label_singular = @Translation("access group"), + * label_plural = @Translation("access groups"), + * label_count = @PluralTranslation( + * singular = "@count access group", + * plural = "@count access groups", + * ), + * handlers = { + * "storage" = "\Drupal\webform_access\WebformAccessGroupStorage", + * "access" = "Drupal\webform_access\WebformAccessGroupAccessControlHandler", + * "list_builder" = "Drupal\webform_access\WebformAccessGroupListBuilder", + * "form" = { + * "add" = "Drupal\webform_access\WebformAccessGroupForm", + * "edit" = "Drupal\webform_access\WebformAccessGroupForm", + * "duplicate" = "Drupal\webform_access\WebformAccessGroupForm", + * "delete" = "Drupal\webform_access\WebformAccessGroupDeleteForm", + * } + * }, + * admin_permission = "administer webform", + * entity_keys = { + * "id" = "id", + * "label" = "label", + * }, + * links = { + * "add-form" = "/admin/structure/webform/config/access/group/add", + * "edit-form" = "/admin/structure/webform/config/access/group/manage/{webform_access_group}", + * "duplicate-form" = "/admin/structure/webform/config/access/group/manage/{webform_access_group}/duplicate", + * "delete-form" = "/admin/structure/webform/config/access/group/manage/{webform_access_group}/delete", + * "collection" = "/admin/structure/webform/config/access/group/manage", + * }, + * config_export = { + * "id", + * "uuid", + * "label", + * "description", + * "type", + * "permissions", + * } + * ) + */ +class WebformAccessGroup extends ConfigEntityBase implements WebformAccessGroupInterface { + + use StringTranslationTrait; + + /** + * The webform access group ID. + * + * @var string + */ + protected $id; + + /** + * The webform access group UUID. + * + * @var string + */ + protected $uuid; + + /** + * The webform access group label. + * + * @var string + */ + protected $label; + + /** + * The webform access group description. + * + * @var string + */ + protected $description; + + /** + * The webform access group type. + * + * @var string + */ + protected $type; + + /** + * The webform access group permissions. + * + * @var array + */ + protected $permissions = []; + + /** + * The webform access group user ids. + * + * @var array + */ + protected $userIds = []; + + /** + * The webform access group source entity ids. + * + * @var array + */ + protected $entityIds = []; + + /** + * {@inheritdoc} + */ + public function getType() { + return $this->type ?: ''; + } + + /** + * {@inheritdoc} + */ + public function getTypeLabel() { + if (empty($this->type)) { + return ''; + } + + $webform_access_type = WebformAccessType::load($this->type); + return ($webform_access_type) ? $webform_access_type->label() : ''; + } + + /** + * {@inheritdoc} + */ + public function setUserIds(array $uids) { + $this->userIds = $uids; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getUserIds() { + return $this->userIds; + } + + /** + * {@inheritdoc} + */ + public function setEntityIds(array $entity_ids) { + $this->entityIds = $entity_ids; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getEntityIds() { + return $this->entityIds; + } + + /** + * {@inheritdoc} + */ + public function addEntityId($entity_type, $entity_id, $field_name, $webform_id) { + $entity = "$entity_type:$entity_id:$field_name:$webform_id"; + if (!in_array($entity, $this->entityIds)) { + $this->entityIds[] = $entity; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeEntityId($entity_type, $entity_id, $field_name, $webform_id) { + $entity = "$entity_type:$entity_id:$field_name:$webform_id"; + foreach ($this->entityIds as $index => $entityId) { + if ($entity == $entityId) { + unset($this->entityIds[$index]); + } + } + array_values($this->entityIds); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addUserId($uid) { + if (!in_array($uid, $this->userIds)) { + $this->userIds[] = $uid; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function removeUserId($uid) { + foreach ($this->userIds as $index => $userId) { + if ($userId == $uid) { + unset($this->userIds[$index]); + } + } + array_values($this->userIds); + return $this; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags() { + Cache::invalidateTags($this->getCacheTagsToInvalidate()); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Entity/WebformAccessType.php b/web/modules/webform/modules/webform_access/src/Entity/WebformAccessType.php new file mode 100644 index 0000000000..b60a7dbde4 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Entity/WebformAccessType.php @@ -0,0 +1,75 @@ +<?php + +namespace Drupal\webform_access\Entity; + +use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\webform_access\WebformAccessTypeInterface; + +/** + * Defines the webform access type entity. + * + * @ConfigEntityType( + * id = "webform_access_type", + * label = @Translation("Webform access type"), + * label_collection = @Translation("Access types"), + * label_singular = @Translation("access type"), + * label_plural = @Translation("access types"), + * label_count = @PluralTranslation( + * singular = "@count access type", + * plural = "@count access types", + * ), + * handlers = { + * "storage" = "\Drupal\webform_access\WebformAccessTypeStorage", + * "access" = "Drupal\webform_access\WebformAccessTypeAccessControlHandler", + * "list_builder" = "Drupal\webform_access\WebformAccessTypeListBuilder", + * "form" = { + * "add" = "Drupal\webform_access\WebformAccessTypeForm", + * "edit" = "Drupal\webform_access\WebformAccessTypeForm", + * "delete" = "Drupal\webform_access\WebformAccessTypeDeleteForm", + * } + * }, + * admin_permission = "administer webform", + * entity_keys = { + * "id" = "id", + * "label" = "label", + * }, + * links = { + * "add-form" = "/admin/structure/webform/config/access/type/add", + * "edit-form" = "/admin/structure/webform/config/access/type/manage/{webform_access_type}", + * "delete-form" = "/admin/structure/webform/config/access/type/manage/{webform_access_type}/delete", + * "collection" = "/admin/structure/webform/config/access/type/manage", + * }, + * config_export = { + * "id", + * "uuid", + * "label", + * } + * ) + */ +class WebformAccessType extends ConfigEntityBase implements WebformAccessTypeInterface { + + use StringTranslationTrait; + + /** + * The webform access type ID. + * + * @var string + */ + protected $id; + + /** + * The webform access type UUID. + * + * @var string + */ + protected $uuid; + + /** + * The webform access type label. + * + * @var string + */ + protected $label; + +} diff --git a/web/modules/webform/modules/webform_access/src/Plugin/Block/WebformAccessGroupEntityBlock.php b/web/modules/webform/modules/webform_access/src/Plugin/Block/WebformAccessGroupEntityBlock.php new file mode 100644 index 0000000000..2ec9c83f2a --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Plugin/Block/WebformAccessGroupEntityBlock.php @@ -0,0 +1,103 @@ +<?php + +namespace Drupal\webform_access\Plugin\Block; + +use Drupal\Core\Block\BlockBase; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Session\AccountInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a 'webform_access_group_entity' block. + * + * @Block( + * id = "webform_access_group_entity", + * admin_label = @Translation("Webform access group entities"), + * category = @Translation("Webform access") + * ) + */ +class WebformAccessGroupEntityBlock extends BlockBase implements ContainerFactoryPluginInterface { + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * The webform access group storage. + * + * @var \Drupal\webform_access\WebformAccessGroupStorageInterface + */ + protected $webformAccessGroupStorage; + + /** + * Creates a WebformAccessGroupEntityBlock instance. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->currentUser = $current_user; + $this->webformAccessGroupStorage = $entity_type_manager->getStorage('webform_access_group'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_user'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function build() { + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->webformAccessGroupStorage->getUserEntities($this->currentUser, 'node'); + if (empty($nodes)) { + return NULL; + } + + $items = []; + foreach ($nodes as $node) { + if ($node->access()) { + $items[] = $node->toLink()->toRenderable(); + } + } + if (empty($items)) { + return NULL; + } + + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // @todo Setup cache tags and context . + return 0; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Tests/WebformAccessSubmissionViewsTest.php b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessSubmissionViewsTest.php new file mode 100644 index 0000000000..065447c7a6 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessSubmissionViewsTest.php @@ -0,0 +1,99 @@ +<?php + +namespace Drupal\webform_access\Tests; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\WebformInterface; + +/** + * Tests for webform access submission views. + * + * @group WebformAccess + */ +class WebformAccessSubmissionViewsTest extends WebformAccessTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['views', 'webform_test_views']; + + /** + * Tests webform access submission views. + */ + public function testWebformAccessSubmissionViewsTest() { + // Create a test submission for each node and user account. + $webform = Webform::load('contact'); + /** @var \Drupal\webform\WebformSubmissionGenerateInterface $submission_generate */ + $submission_generate = \Drupal::service('webform_submission.generate'); + foreach ($this->nodes as $node) { + foreach ($this->users as $user) { + WebformSubmission::create([ + 'webform_id' => 'contact', + 'entity_type' => 'node', + 'entity_id' => $node->id(), + 'uid' => $user->id(), + 'data' => $submission_generate->getData($webform), + ])->save(); + } + } + + $this->checkUserSubmissionAccess($webform, $this->users); + } + + /** + * Check user submission access. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * @param array $accounts + * An associative array of test users. + * + * @see \Drupal\Tests\webform\Functional\WebformSubmissionViewsAccessTest::checkUserSubmissionAccess + */ + protected function checkUserSubmissionAccess(WebformInterface $webform, array $accounts) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $webform_submission_storage */ + $webform_submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + + // Reset the static cache to make sure we are hitting actual fresh access + // results. + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('webform_submission')->resetCache(); + + foreach ($accounts as $account_type => $account) { + // Login the current user. + $this->drupalLogin($account); + + // Get the webform_test_views_access view and the sid for each + // displayed record. Submission access is controlled via the query. + // @see webform_query_webform_submission_access_alter() + $this->drupalGet('/admin/structure/webform/test/views_access'); + + $views_sids = []; + foreach ($this->cssSelect('.view .view-content tbody .views-field-sid') as $node) { + $views_sids[] = $node->getText(); + } + sort($views_sids); + + $expected_sids = []; + + // Load all webform submissions and check access using the access method. + // @see \Drupal\webform\WebformSubmissionAccessControlHandler::checkAccess + $webform_submissions = $webform_submission_storage->loadByEntities($webform); + + foreach ($webform_submissions as $webform_submission) { + if ($webform_submission->access('view', $account)) { + $expected_sids[] = $webform_submission->id(); + } + } + + sort($expected_sids); + + // Check that the views sids is equal to the expected sids. + $this->assertEqual($expected_sids, $views_sids, "User '" . $account_type . "' access has correct access through view on webform submission entity type."); + } + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTest.php b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTest.php new file mode 100644 index 0000000000..43c8520a15 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTest.php @@ -0,0 +1,165 @@ +<?php + +namespace Drupal\webform_access\Tests; + +use Drupal\field\Entity\FieldConfig; + +/** + * Tests for webform access. + * + * @group WebformAccess + */ +class WebformAccessTest extends WebformAccessTestBase { + + /** + * Tests webform access. + */ + public function testWebformAccess() { + $nid = $this->nodes['contact_01']->id(); + + $this->drupalLogin($this->rootUser); + + // Check that employee and manager groups exist. + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertLink('employee_group'); + $this->assertLink('manager_group'); + + // Check that webform node is assigned to groups. + $this->assertLink($this->nodes['contact_01']->label()); + + // Check that employee and manager users can't access webform results. + foreach ($this->users as $account) { + $this->drupalLogin($account); + $this->drupalGet("/node/$nid/webform/results/submissions"); + $this->assertResponse(403); + } + + // Assign users to groups via the UI. + $this->drupalLogin($this->rootUser); + foreach ($this->groups as $name => $group) { + $this->drupalPostForm( + "/admin/structure/webform/access/group/manage/$name", + ['users[]' => $this->users[$name]->id()], + t('Save') + ); + } + + // Check that manager and employee users can access webform results. + foreach (['manager', 'employee'] as $name) { + $account = $this->users[$name]; + $this->drupalLogin($account); + $this->drupalGet("/node/$nid/webform/results/submissions"); + $this->assertResponse(200); + } + + // Check that employee can't delete results. + $this->drupalLogin($this->users['employee']); + $this->drupalGet("/node/$nid/webform/results/clear"); + $this->assertResponse(403); + + // Check that manager can delete results. + $this->drupalLogin($this->users['manager']); + $this->drupalGet("/node/$nid/webform/results/clear"); + $this->assertResponse(200); + + // Unassign employee user from employee group via the UI. + $this->drupalLogin($this->rootUser); + $this->drupalPostForm( + '/admin/structure/webform/access/group/manage/employee', + ['users[]' => 1], + t('Save') + ); + + // Assign employee user to manager group via the UI. + $this->drupalLogin($this->rootUser); + $this->drupalPostForm( + '/user/' . $this->users['employee']->id() . '/edit', + ['webform_access_group[]' => 'manager'], + t('Save') + ); + + // Check that employee can now delete results. + $this->drupalLogin($this->users['employee']); + $this->drupalGet("/node/$nid/webform/results/clear"); + $this->assertResponse(200); + + // Unassign node from groups. + $this->drupalLogin($this->rootUser); + foreach ($this->groups as $name => $group) { + $this->drupalPostForm( + "/admin/structure/webform/access/group/manage/$name", + ['entities[]' => 'node:' . $this->nodes['contact_02']->id() . ':webform:contact'], + t('Save') + ); + } + + // Check that employee can't access results. + $this->drupalLogin($this->users['employee']); + $this->drupalGet("/node/$nid/webform/results/clear"); + $this->assertResponse(403); + + // Assign webform node to group via the UI. + $this->drupalLogin($this->rootUser); + $this->drupalPostForm( + "/node/$nid/edit", + ['webform[0][settings][webform_access_group][]' => 'manager'], + t('Save') + ); + + // Check that employee can now access results. + $this->drupalLogin($this->users['employee']); + $this->drupalGet("/node/$nid/webform/results/clear"); + $this->assertResponse(200); + + // Delete employee group. + $this->groups['employee']->delete(); + + // Check that employee group is configured. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertRaw('manager_type'); + $this->assertLink('manager_group'); + $this->assertLink('manager_user'); + $this->assertLink('employee_user'); + $this->assertLink('contact_01'); + $this->assertLink('contact_02'); + + // Reset caches. + \Drupal::entityTypeManager()->getStorage('webform_access_group')->resetCache(); + \Drupal::entityTypeManager()->getStorage('webform_access_type')->resetCache(); + + // Delete types. + foreach ($this->types as $type) { + $type->delete(); + } + + // Check that manager type has been removed. + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertNoRaw('manager_type'); + + // Delete users. + foreach ($this->users as $user) { + $user->delete(); + } + + // Check that manager type has been removed. + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertNoLink('manager_user'); + $this->assertNoLink('employee_user'); + + // Delete contact 2. + $this->nodes['contact_02']->delete(); + + // Check that contact_02 has been removed. + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertNoLink('contact_02'); + + // Delete webform field config. + FieldConfig::loadByName('node', 'webform', 'webform')->delete(); + + // Check that contact_02 has been removed. + $this->drupalGet('/admin/structure/webform/access/group/manage'); + $this->assertNoLink('contact_02'); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTestBase.php b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTestBase.php new file mode 100644 index 0000000000..a1069fe872 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTestBase.php @@ -0,0 +1,97 @@ +<?php + +namespace Drupal\webform_access\Tests; + +use Drupal\webform_access\Entity\WebformAccessGroup; +use Drupal\webform_access\Entity\WebformAccessType; +use Drupal\webform_node\Tests\WebformNodeTestBase; + +/** + * Test base for webform access. + */ +abstract class WebformAccessTestBase extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_access']; + + /** + * Webform node[]. + * + * @var \Drupal\node\NodeInterface + */ + protected $nodes = []; + + /** + * Users. + * + * @var \Drupal\user\UserInterface[] + */ + protected $users = []; + + /** + * Access types (manager, employee, and customer). + * + * @var \Drupal\webform_access\WebformAccessTypeInterface[] + */ + protected $types = []; + + /** + * Access groups (manager, employee, and customer). + * + * @var \Drupal\webform_access\WebformAccessGroupInterface[] + */ + protected $groups = []; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + // Create webform nodes. + $this->nodes['contact_01'] = $this->createWebformNode('contact', ['title' => 'contact_01']); + $this->nodes['contact_02'] = $this->createWebformNode('contact', ['title' => 'contact_02']); + + // Create webform access types and groups. + $types = [ + 'manager' => [ + 'administer', + ], + 'employee' => [ + 'view_any', + 'update_any', + ], + 'customer' => [ + 'view_own', + 'update_own', + ], + ]; + foreach ($types as $type => $permissions) { + $this->users[$type] = $this->drupalCreateUser([], $type . '_user'); + + $values = [ + 'id' => $type, + 'label' => $type . '_type', + ]; + $webform_access_type = WebformAccessType::create($values); + $webform_access_type->save(); + $this->types[$type] = $webform_access_type; + + $values = [ + 'id' => $type, + 'type' => $type, + 'label' => $type . '_group', + 'permissions' => $permissions, + ]; + $webform_access_group = WebformAccessGroup::create($values); + $webform_access_group->addEntityId('node', $this->nodes['contact_01']->id(), 'webform', 'contact'); + $webform_access_group->save(); + $this->groups[$type] = $webform_access_group; + } + } + +} diff --git a/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTokensTest.php b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTokensTest.php new file mode 100644 index 0000000000..78768ea32d --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/Tests/WebformAccessTokensTest.php @@ -0,0 +1,54 @@ +<?php + +namespace Drupal\webform_access\Tests; + +use Drupal\webform\Entity\WebformSubmission; + +/** + * Tests for webform tokens access. + * + * @group WebformAccess + */ +class WebformAccessTokensTest extends WebformAccessTestBase { + + /** + * Tests webform access tokens. + */ + public function testWebformAccessTokens() { + // Add both users to employee group. + foreach ($this->users as $account) { + $this->groups['employee']->addUserId($account->id()); + } + $this->groups['employee']->save(); + $this->users['other'] = $this->drupalCreateUser([], 'other_user'); + $this->groups['manager']->setUserIds([$this->users['other']->id()]); + $this->groups['manager']->save(); + + // Create a submission. + $edit = [ + 'name' => 'name', + 'email' => 'name@example.com', + 'subject' => 'subject', + 'message' => 'message', + ]; + $sid = $this->postNodeSubmission($this->nodes['contact_01'], $edit); + $webform_submission = WebformSubmission::load($sid); + + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $token_data['webform_access'] = $webform_submission; + + // Check [webform_access:type:employee] token. + $result = $token_manager->replace('[webform_access:type:employee]', $webform_submission, $token_data); + $this->assertEqual('customer_user@example.com,employee_user@example.com,manager_user@example.com', $result); + + // Check [webform_access:type:manager] token. + $result = $token_manager->replace('[webform_access:type:manager]', $webform_submission, $token_data); + $this->assertEqual('other_user@example.com', $result); + + // Check [webform_access:type:all] token. + $result = $token_manager->replace('[webform_access:type]', $webform_submission, $token_data); + $this->assertEqual('customer_user@example.com,employee_user@example.com,manager_user@example.com,other_user@example.com', $result); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupAccessControlHandler.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupAccessControlHandler.php new file mode 100644 index 0000000000..f307bc3fb8 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupAccessControlHandler.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Defines the access control handler for the webform access entity type. + * + * @see \Drupal\webform_access\Entity\WebformAccessGroup. + */ +class WebformAccessGroupAccessControlHandler extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + return AccessResult::allowedIfHasPermission($account, 'administer webform'); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupDeleteForm.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupDeleteForm.php new file mode 100644 index 0000000000..c51b26a65b --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupDeleteForm.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\webform\Form\WebformConfigEntityDeleteFormBase; + +/** + * Provides a delete webform access group form. + */ +class WebformAccessGroupDeleteForm extends WebformConfigEntityDeleteFormBase { + + /** + * {@inheritdoc} + */ + protected $confirmCheckbox = FALSE; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove configuration'), + $this->t('Affect any fields which use this access group'), + ], + ], + ]; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupForm.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupForm.php new file mode 100644 index 0000000000..664544aa7d --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupForm.php @@ -0,0 +1,317 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\Utility\WebformDialogHelper; +use Drupal\webform\WebformAccessRulesManagerInterface; +use Drupal\webform\WebformEntityReferenceManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Entity\EntityManager; + +/** + * Provides a form to define a webform access group. + */ +class WebformAccessGroupForm extends EntityForm { + + /** + * The database object. + * + * @var object + */ + protected $database; + + /** + * Entity manager. + * + * @var Drupal\Core\Entity\EntityManager + */ + protected $entityManager; + + /** + * The webform element manager. + * + * @var \Drupal\webform\Plugin\WebformElementManagerInterface + */ + protected $elementManager; + + /** + * The webform entity reference manager. + * + * @var \Drupal\webform\WebformEntityReferenceManagerInterface + */ + protected $webformEntityReferenceManager; + + /** + * The webform access rules manager. + * + * @var \Drupal\webform\WebformAccessRulesManagerInterface + */ + protected $webformAccessRulesManager; + + /** + * Constructs a WebformAccessGroupForm. + * + * @param \Drupal\Core\Database\Connection $database + * The database. + * @param \Drupal\Core\Entity\EntityManager $entity_manager + * The entity manager. + * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager + * The webform element manager. + * @param \Drupal\webform\WebformEntityReferenceManagerInterface $webform_entity_reference_manager + * The webform entity reference manager. + * @param \Drupal\webform\WebformAccessRulesManagerInterface $webform_access_rules_manager + * The webform access rules manager. + */ + public function __construct(Connection $database, EntityManager $entity_manager, WebformElementManagerInterface $element_manager, WebformEntityReferenceManagerInterface $webform_entity_reference_manager, WebformAccessRulesManagerInterface $webform_access_rules_manager) { + $this->database = $database; + $this->entityManager = $entity_manager; + $this->elementManager = $element_manager; + $this->webformEntityReferenceManager = $webform_entity_reference_manager; + $this->webformAccessRulesManager = $webform_access_rules_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('database'), + $container->get('entity.manager'), + $container->get('plugin.manager.webform.element'), + $container->get('webform.entity_reference_manager'), + $container->get('webform.access_rules_manager') + ); + } + + /** + * {@inheritdoc} + */ + protected function prepareEntity() { + + if ($this->operation == 'duplicate') { + $this->setEntity($this->getEntity()->createDuplicate()); + } + + parent::prepareEntity(); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface $webform_access_group */ + $webform_access_group = $this->getEntity(); + + // Customize title for duplicate and edit operation. + switch ($this->operation) { + case 'duplicate': + $form['#title'] = $this->t("Duplicate '@label' access group", ['@label' => $webform_access_group->label()]); + break; + + case 'edit': + $form['#title'] = $webform_access_group->label(); + break; + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface $webform_access_group */ + $webform_access_group = $this->entity; + + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#required' => TRUE, + '#attributes' => ($webform_access_group->isNew()) ? ['autofocus' => 'autofocus'] : [], + '#default_value' => $webform_access_group->label(), + ]; + $form['id'] = [ + '#type' => 'machine_name', + '#machine_name' => [ + 'exists' => '\Drupal\webform_access\Entity\WebformAccessGroup::load', + 'label' => '<br/>' . $this->t('Machine name'), + ], + '#maxlength' => 32, + '#field_suffix' => ' (' . $this->t('Maximum @max characters', ['@max' => 32]) . ')', + '#required' => TRUE, + '#disabled' => !$webform_access_group->isNew(), + '#default_value' => $webform_access_group->id(), + ]; + $form['description'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Description'), + '#default_value' => $webform_access_group->get('description'), + ]; + $form['type'] = [ + '#type' => 'webform_entity_select', + '#title' => $this->t('Type'), + '#target_type' => 'webform_access_type', + '#empty_option' => $this->t('- None -'), + '#default_value' => $webform_access_group->get('type'), + ]; + + // Users. + $form['users'] = [ + '#type' => 'webform_entity_select', + '#title' => $this->t('Users'), + '#target_type' => 'user', + '#multiple' => TRUE, + '#selection_handler' => 'default:user', + '#selection_settings' => [ + 'include_anonymous' => FALSE, + ], + '#select2' => TRUE, + '#default_value' => $webform_access_group->getUserIds(), + + ]; + $this->elementManager->processElement($form['users']); + + // Entities (Nodes). + $form['entities'] = [ + '#type' => 'select', + '#title' => $this->t('Nodes'), + '#multiple' => TRUE, + '#select2' => TRUE, + '#options' => $this->getEntitiesAsOptions(), + '#default_value' => $webform_access_group->getEntityIds(), + ]; + $this->elementManager->processElement($form['entities']); + + // Permissions. + $permissions_options = []; + $access_rules = $this->webformAccessRulesManager->getAccessRulesInfo(); + foreach ($access_rules as $permission => $access_rule) { + $permissions_options[$permission] = [ + 'title' => $access_rule['title'], + ]; + } + $form['permissions_label'] = [ + '#type' => 'label', + '#title' => $this->t('Permissions'), + ]; + $form['permissions'] = [ + '#type' => 'tableselect', + '#header' => ['title' => $this->t('Permission')], + '#js_select' => FALSE, + '#options' => $permissions_options, + '#default_value' => $webform_access_group->get('permissions'), + ]; + $this->elementManager->processElement($form['permissions']); + + $form['#attached']['library'][] = 'webform_access/webform_access.admin'; + + return parent::form($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + + // Open delete button in a modal dialog. + if (isset($actions['delete'])) { + $actions['delete']['#attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, $actions['delete']['#attributes']['class']); + WebformDialogHelper::attachLibraries($actions['delete']); + } + + return $actions; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $form_state->setValue('permissions', array_filter($form_state->getValue('permissions'))); + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface $webform_access_group */ + $webform_access_group = $this->getEntity(); + $webform_access_group->setUserIds($form_state->getValue('users')); + $webform_access_group->setEntityIds($form_state->getValue('entities')); + $webform_access_group->save(); + + // Log and display message. + $context = [ + '@label' => $webform_access_group->label(), + 'link' => $webform_access_group->toLink($this->t('Edit'), 'edit-form')->toString(), + ]; + $this->logger('webform')->notice('Access group @label saved.', $context); + $this->messenger()->addStatus($this->t('Access group %label saved.', ['%label' => $webform_access_group->label()])); + + // Redirect to list. + $form_state->setRedirect('entity.webform_access_group.collection'); + } + + /** + * Get webform entities as options. + * + * @return array + * An associative array container webform node options. + */ + protected function getEntitiesAsOptions() { + // Collects webform nodes. + $webform_nodes = []; + $nids = []; + $webform_ids = []; + + $table_names = $this->webformEntityReferenceManager->getTableNames(); + foreach ($table_names as $table_name => $field_name) { + if (strpos($table_name, 'node_revision__') !== 0) { + continue; + } + $query = $this->database->select($table_name, 'n'); + $query->distinct(); + $query->fields('n', ['entity_id', $field_name . '_target_id']); + $query->condition($field_name . '_target_id', '', '<>'); + $query->isNotNull($field_name . '_target_id'); + $result = $query->execute()->fetchAllKeyed(); + foreach ($result as $nid => $webform_id) { + $webform_nodes[$nid][$field_name][$webform_id] = $webform_id; + $webform_ids[$webform_id] = $webform_id; + $nids[$nid] = $nid; + } + } + + /** @var \Drupal\webform\WebformInterface[] $webforms */ + $webforms = Webform::loadMultiple($webform_ids); + + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->entityManager->getStorage('node')->loadMultiple($nids); + + $options = []; + foreach ($webform_nodes as $nid => $field_names) { + if (!isset($nodes[$nid])) { + continue; + } + $node = $nodes[$nid]; + foreach ($field_names as $field_name => $webform_ids) { + foreach ($webform_ids as $webform_id) { + if (!isset($webforms[$webform_id])) { + continue; + } + $webform = $webforms[$webform_id]; + $options['node:' . $node->id() . ':' . $field_name . ':' . $webform->id()] = $node->label() . ': ' . $webform->label(); + } + } + } + asort($options); + return $options; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupInterface.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupInterface.php new file mode 100644 index 0000000000..644cfa5b49 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupInterface.php @@ -0,0 +1,119 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityInterface; + +/** + * Provides an interface defining a webform access group entity. + */ +interface WebformAccessGroupInterface extends ConfigEntityInterface { + + /** + * Get webform access group type. + * + * @return string + * The webform access group type. + */ + public function getType(); + + /** + * Get webform access group type label. + * + * @return string + * The webform access group type label. + */ + public function getTypeLabel(); + + /** + * Set user ids assigned to webform access group. + * + * @param array $uids + * An array of user ids. + * + * @return $this + */ + public function setUserIds(array $uids); + + /** + * Get user ids assigned to webform access group. + * + * @return array + * An array of user ids. + */ + public function getUserIds(); + + /** + * Set entities assigned to webform access group. + * + * @param array $entity_ids + * An array of entity ids. + * Formatted as 'node:type:field_name:webform'. + * + * @return $this + */ + public function setEntityIds(array $entity_ids); + + /** + * Get entities assigned to webform access group. + * + * @return array + * An array of entity ids. + * Formatted as 'node:type:field_name:webform' + */ + public function getEntityIds(); + + /** + * Add entity id to webform access group. + * + * @param string $entity_type + * The source entity type. + * @param string $entity_id + * The source entity id. + * @param string $field_name + * The source entity webform field name. + * @param string $webform_id + * The webform id. + */ + public function addEntityId($entity_type, $entity_id, $field_name, $webform_id); + + /** + * Remove entity id to webform access group. + * + * @param string $entity_type + * The source entity type. + * @param string $entity_id + * The source entity id. + * @param string $field_name + * The source entity webform field name. + * @param string $webform_id + * The webform id. + */ + public function removeEntityId($entity_type, $entity_id, $field_name, $webform_id); + + /** + * Add user id to webform access group. + * + * @param int $uid + * A user id. + * + * @return $this + */ + public function addUserId($uid); + + /** + * Remove user id to webform access group. + * + * @param int $uid + * A user id. + * + * @return $this + */ + public function removeUserId($uid); + + /** + * Invalidates an entity's cache tags upon save. + */ + public function invalidateTags(); + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupListBuilder.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupListBuilder.php new file mode 100644 index 0000000000..694f35de45 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupListBuilder.php @@ -0,0 +1,210 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityListBuilder; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Url; +use Drupal\user\Entity\User; +use Drupal\webform\Element\WebformHtmlEditor; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Utility\WebformDialogHelper; + +/** + * Defines a class to build a listing of webform access group entities. + * + * @see \Drupal\webform\Entity\WebformOption + */ +class WebformAccessGroupListBuilder extends ConfigEntityListBuilder { + + /** + * {@inheritdoc} + */ + protected $limit = FALSE; + + /** + * {@inheritdoc} + */ + public function render() { + $build = []; + + // Filter form. + $build['filter_form'] = $this->buildFilterForm(); + + // Display info. + $build['info'] = $this->buildInfo(); + + // Table. + $build += parent::render(); + $build['table']['#sticky'] = TRUE; + $build['table']['#attributes']['class'][] = 'webform-access-group-table'; + + // Attachments. + $build['#attached']['library'][] = 'webform/webform.admin.dialog'; + + return $build; + } + + /** + * Build the filter form. + * + * @return array + * A render array representing the filter form. + */ + protected function buildFilterForm() { + return [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by keyword.'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-element' => '.webform-access-group-table', + 'data-summary' => '.webform-access-group-summary', + 'data-item-singlular' => $this->t('access group'), + 'data-item-plural' => $this->t('access groups'), + 'title' => $this->t('Enter a keyword to filter by.'), + 'autofocus' => 'autofocus', + ], + ]; + } + + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { + $total = $this->getStorage()->getQuery()->count()->execute(); + if (!$total) { + return []; + } + + return [ + '#markup' => $this->formatPlural($total, '@total access group', '@total access groups', ['@total' => $total]), + '#prefix' => '<div class="webform-access-group-summary">', + '#suffix' => '</div>', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header = []; + $header['label'] = $this->t('Label/Description'); + $header['type'] = [ + 'data' => $this->t('Type'), + 'class' => [RESPONSIVE_PRIORITY_MEDIUM], + ]; + $header['users'] = [ + 'data' => $this->t('Users'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + $header['entities'] = [ + 'data' => $this->t('Nodes'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + $header['permissions'] = [ + 'data' => $this->t('Permissions'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface $entity */ + + // Label/Description. + $row['label'] = [ + 'data' => [ + 'label' => $entity->toLink($entity->label(), 'edit-form')->toRenderable() + ['#suffix' => '<br/>'], + 'description' => WebformHtmlEditor::checkMarkup($entity->get('description')), + ], + ]; + + // Type. + $row['type'] = $entity->getTypeLabel(); + + // Users. + $uids = $entity->getUserIds(); + /** @var \Drupal\user\UserInterface[] $users */ + $users = $uids ? User::loadMultiple($uids) : []; + $items = []; + foreach ($users as $user) { + $items[] = $user->toLink(); + } + $row['users'] = ['data' => ['#theme' => 'item_list', '#items' => $items]]; + + // Entities. + $source_entities = $entity->getEntityIds(); + $items = []; + foreach ($source_entities as $source_entity_record) { + list($source_entity_type, $source_entity_id, $field_name, $webform_id) = explode(':', $source_entity_record); + $source_entity = \Drupal::entityManager()->getStorage($source_entity_type)->load($source_entity_id); + $webform = Webform::load($webform_id); + if ($source_entity && $webform) { + $items[] = [ + 'source_entity' => $source_entity->toLink()->toRenderable(), + 'webform' => ['#prefix' => '<br/>', '#markup' => $webform->label()], + ]; + } + } + $row['entities'] = ['data' => ['#theme' => 'item_list', '#items' => $items]]; + + // Permissions. + $permissions = array_intersect_key([ + 'create' => $this->t('Create submissions'), + 'view_any' => $this->t('View any submissions'), + 'update_any' => $this->t('Update any submissions'), + 'delete_any' => $this->t('Delete any submissions'), + 'purge_any' => $this->t('Purge any submissions'), + 'view_own' => $this->t('View own submissions'), + 'update_own' => $this->t('Update own submissions'), + 'delete_own' => $this->t('Delete own submissions'), + 'administer' => $this->t('Administer submissions'), + 'test' => $this->t('Test webform'), + ], array_flip($entity->get('permissions'))); + $row['permissions'] = ['data' => ['#theme' => 'item_list', '#items' => $permissions]]; + $row = $row + parent::buildRow($entity); + + return [ + 'data' => $row, + 'class' => ['webform-form-filter-text-source'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { + $operations = parent::getDefaultOperations($entity); + if ($entity->access('duplicate')) { + $operations['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'weight' => 23, + 'url' => Url::fromRoute('entity.webform_access_group.duplicate_form', ['webform_access_group' => $entity->id()]), + ]; + } + if (isset($operations['delete'])) { + $operations['delete']['attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + public function buildOperations(EntityInterface $entity) { + return parent::buildOperations($entity) + [ + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorage.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorage.php new file mode 100644 index 0000000000..378e90c21e --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorage.php @@ -0,0 +1,227 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\Entity\ConfigEntityStorage; +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\webform\WebformInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Storage controller class for "webform_access_group" configuration entities. + */ +class WebformAccessGroupStorage extends ConfigEntityStorage implements WebformAccessGroupStorageInterface { + + /** + * Active database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a WebformAccessGroupStorage object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory service. + * @param \Drupal\Component\Uuid\UuidInterface $uuid_service + * The UUID service. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + * @param \Drupal\Core\Database\Connection $database + * The database connection to be used. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, Connection $database, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); + $this->database = $database; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('config.factory'), + $container->get('uuid'), + $container->get('language_manager'), + $container->get('database'), + $container->get('entity_type.manager') + + ); + } + + /** + * {@inheritdoc} + */ + protected function doLoadMultiple(array $ids = NULL) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface[] $webform_access_groups */ + $webform_access_groups = parent::doLoadMultiple($ids); + + // Load users. + $result = $this->database->select('webform_access_group_user', 'gu') + ->fields('gu', ['group_id', 'uid']) + ->condition('group_id', $ids, 'IN') + ->orderBy('group_id') + ->orderBy('uid') + ->execute(); + $users = []; + while ($record = $result->fetchAssoc()) { + $users[$record['group_id']][] = $record['uid']; + } + foreach ($webform_access_groups as $group_id => $webform_access_group) { + $webform_access_group->setUserIds((isset($users[$group_id])) ? $users[$group_id] : []); + } + + // Load entities. + $result = $this->database->select('webform_access_group_entity', 'ge') + ->fields('ge', ['group_id', 'entity_type', 'entity_id', 'field_name', 'webform_id']) + ->condition('group_id', $ids, 'IN') + ->orderBy('group_id') + ->execute(); + $entities = []; + while ($record = $result->fetchAssoc()) { + $group_id = $record['group_id']; + unset($record['group_id']); + $entities[$group_id][] = implode(':', $record); + } + foreach ($webform_access_groups as $group_id => $webform_access_group) { + $webform_access_group->setEntityIds((isset($entities[$group_id])) ? $entities[$group_id] : []); + } + + return $webform_access_groups; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, EntityInterface $entity) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface $entity */ + $result = parent::doSave($id, $entity); + + // Save users. + $users = $entity->getUserIds(); + $this->database->delete('webform_access_group_user') + ->condition('group_id', $entity->id()) + ->execute(); + $query = $this->database + ->insert('webform_access_group_user') + ->fields(['group_id', 'uid']); + $values = ['group_id' => $entity->id()]; + foreach ($users as $uid) { + $values['uid'] = $uid; + $query->values($values); + } + $query->execute(); + + // Save entities. + $entities = $entity->getEntityIds(); + $this->database->delete('webform_access_group_entity') + ->condition('group_id', $entity->id()) + ->execute(); + $query = $this->database + ->insert('webform_access_group_entity') + ->fields(['group_id', 'entity_type', 'entity_id', 'field_name', 'webform_id']); + $values = ['group_id' => $entity->id()]; + foreach ($entities as $entity) { + list($values['entity_type'], $values['entity_id'], $values['field_name'], $values['webform_id']) = explode(':', $entity); + $query->values($values); + } + $query->execute(); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function delete(array $entities) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface[] $entities */ + foreach ($entities as $entity) { + $this->database->delete('webform_access_group_entity') + ->condition('group_id', $entity->id()) + ->execute(); + $this->database->delete('webform_access_group_user') + ->condition('group_id', $entity->id()) + ->execute(); + } + return parent::delete($entities); + } + + /** + * {@inheritdoc} + */ + public function loadByEntities(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $type = NULL) { + $query = $this->database->select('webform_access_group_entity', 'ge'); + $query->fields('ge', ['group_id']); + + // Webform. + if ($webform) { + $query->condition('webform_id', $webform->id()); + } + // Source entity. + if ($source_entity) { + $query->condition('entity_type', $source_entity->getEntityTypeId()); + $query->condition('entity_id', $source_entity->id()); + } + // Account. + if ($account) { + $query->innerjoin('webform_access_group_user', 'gu', 'ge.group_id = gu.group_id'); + $query->condition('uid', $account->id()); + } + // Webform access type. + if ($type) { + $type_group_ids = $this->getQuery() + ->condition('type', $type) + ->accessCheck(FALSE) + ->execute(); + if (empty($type_group_ids)) { + return []; + } + $query->condition('group_id', $type_group_ids, 'IN'); + } + + $group_ids = $query->execute()->fetchCol(); + return $group_ids ? $this->loadMultiple($group_ids) : []; + } + + /** + * {@inheritdoc} + */ + public function getUserEntities(AccountInterface $account, $entity_type = NULL) { + /** @var \Drupal\webform_access\WebformAccessGroupInterface[] $webform_access_groups */ + $webform_access_groups = $this->loadByEntities(NULL, NULL, $account); + + $source_entity_ids = []; + foreach ($webform_access_groups as $webform_access_group) { + $entities = $webform_access_group->getEntityIds(); + foreach ($entities as $entity) { + list($source_entity_type, $source_entity_id) = explode(':', $entity); + if (!$entity_type || $source_entity_type === $entity_type) { + $source_entity_ids[] = $source_entity_id; + } + } + } + return $this->entityTypeManager->getStorage($entity_type)->loadMultiple($source_entity_ids); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorageInterface.php b/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorageInterface.php new file mode 100644 index 0000000000..874af230ae --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessGroupStorageInterface.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityStorageInterface; +use Drupal\Core\Config\Entity\ImportableEntityStorageInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\webform\WebformInterface; + +/** + * Provides an interface for Webform Access Group storage. + */ +interface WebformAccessGroupStorageInterface extends ConfigEntityStorageInterface, ImportableEntityStorageInterface { + + /** + * Load webform access groups by their related entity references. + * + * @param \Drupal\webform\WebformInterface|null $webform + * (optional) The webform that the submission token is associated with. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * (optional) A webform submission source entity. + * @param \Drupal\Core\Session\AccountInterface|null $account + * (optional) A user account. + * @param string $type + * (optional) Webform access type. + * + * @return \Drupal\webform\WebformSubmissionInterface[] + * An array of webform access group objects indexed by their ids. + */ + public function loadByEntities(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $type = NULL); + + /** + * Get source entities associated with a user account. + * + * @param \Drupal\Core\Session\AccountInterface $account + * A user account. + * @param string|null $entity_type + * Source entity type. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * Get source entities associated with a user account. + */ + public function getUserEntities(AccountInterface $account, $entity_type = NULL); + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeAccessControlHandler.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeAccessControlHandler.php new file mode 100644 index 0000000000..4a2b468831 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeAccessControlHandler.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Defines the access control handler for the webform access entity type. + * + * @see \Drupal\webform_access\Entity\WebformAccessType. + */ +class WebformAccessTypeAccessControlHandler extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + return AccessResult::allowedIfHasPermission($account, 'administer webform'); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeDeleteForm.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeDeleteForm.php new file mode 100644 index 0000000000..f11367e500 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeDeleteForm.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\webform\Form\WebformConfigEntityDeleteFormBase; + +/** + * Provides a delete webform access type form. + */ +class WebformAccessTypeDeleteForm extends WebformConfigEntityDeleteFormBase { + + /** + * {@inheritdoc} + */ + protected $confirmCheckbox = FALSE; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove configuration'), + $this->t('Affect any access groups which use this type'), + ], + ], + ]; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeForm.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeForm.php new file mode 100644 index 0000000000..77e4991f9a --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeForm.php @@ -0,0 +1,79 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformDialogHelper; + +/** + * Provides a form to define a webform access type. + */ +class WebformAccessTypeForm extends EntityForm { + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_access\WebformAccessTypeInterface $webform_access_type */ + $webform_access_type = $this->entity; + + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#required' => TRUE, + '#attributes' => ($webform_access_type->isNew()) ? ['autofocus' => 'autofocus'] : [], + '#default_value' => $webform_access_type->label(), + ]; + $form['id'] = [ + '#type' => 'machine_name', + '#machine_name' => [ + 'exists' => '\Drupal\webform_access\Entity\WebformAccessType::load', + 'label' => '<br/>' . $this->t('Machine name'), + ], + '#maxlength' => 32, + '#field_suffix' => ' (' . $this->t('Maximum @max characters', ['@max' => 32]) . ')', + '#required' => TRUE, + '#disabled' => !$webform_access_type->isNew(), + '#default_value' => $webform_access_type->id(), + ]; + + return parent::form($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + + // Open delete button in a modal dialog. + if (isset($actions['delete'])) { + $actions['delete']['#attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, $actions['delete']['#attributes']['class']); + WebformDialogHelper::attachLibraries($actions['delete']); + } + + return $actions; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_access\WebformAccessTypeInterface $webform_access_type */ + $webform_access_type = $this->getEntity(); + $webform_access_type->save(); + + $context = [ + '@label' => $webform_access_type->label(), + 'link' => $webform_access_type->toLink($this->t('Edit'), 'edit-form')->toString(), + ]; + $this->logger('webform')->notice('Access type @label saved.', $context); + + $this->messenger()->addStatus($this->t('Access type %label saved.', ['%label' => $webform_access_type->label()])); + + $form_state->setRedirect('entity.webform_access_type.collection'); + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeInterface.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeInterface.php new file mode 100644 index 0000000000..7ba2c0e039 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeInterface.php @@ -0,0 +1,12 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityInterface; + +/** + * Provides an interface defining a webform access type entity. + */ +interface WebformAccessTypeInterface extends ConfigEntityInterface { + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeListBuilder.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeListBuilder.php new file mode 100644 index 0000000000..03d58c6955 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeListBuilder.php @@ -0,0 +1,146 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityListBuilder; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\webform\Utility\WebformDialogHelper; +use Drupal\webform_access\Entity\WebformAccessGroup; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Defines a class to build a listing of webform access type entities. + * + * @see \Drupal\webform\Entity\WebformOption + */ +class WebformAccessTypeListBuilder extends ConfigEntityListBuilder { + + /** + * {@inheritdoc} + */ + protected $limit = FALSE; + + /** + * Access group storage. + * + * @var \Drupal\webform_access\WebformAccessGroupStorageInterface + */ + protected $accessGroupStorage; + + /** + * {@inheritdoc} + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, WebformAccessGroupStorageInterface $access_group_storage) { + parent::__construct($entity_type, $storage); + $this->accessGroupStorage = $access_group_storage; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('entity.manager')->getStorage('webform_access_group') + ); + } + + /** + * {@inheritdoc} + */ + public function render() { + $build = []; + + // Display info. + $build['info'] = $this->buildInfo(); + + // Table. + $build += parent::render(); + $build['table']['#sticky'] = TRUE; + + // Attachments. + $build['#attached']['library'][] = 'webform/webform.admin.dialog'; + + return $build; + } + + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { + $total = $this->getStorage()->getQuery()->count()->execute(); + if (!$total) { + return []; + } + + return [ + '#markup' => $this->formatPlural($total, '@total access type', '@total access types', ['@total' => $total]), + '#prefix' => '<div>', + '#suffix' => '</div>', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildHeader() { + $header['label'] = $this->t('Label'); + $header['groups'] = [ + 'data' => $this->t('Groups'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\webform_access\WebformAccessTypeInterface $entity */ + + // Label. + $row['label'] = $entity->toLink($entity->label(), 'edit-form'); + + // Groups. + $entity_ids = $this->accessGroupStorage->getQuery() + ->condition('type', $entity->id()) + ->execute(); + $items = []; + if ($entity_ids) { + $webform_access_groups = WebformAccessGroup::loadMultiple($entity_ids); + foreach ($webform_access_groups as $webform_access_group) { + $items[] = $webform_access_group->label(); + } + } + $row['groups'] = ['data' => ['#theme' => 'item_list', '#items' => $items]]; + return $row + parent::buildRow($entity); + } + + /** + * {@inheritdoc} + */ + public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { + $operations = parent::getDefaultOperations($entity); + if (isset($operations['delete'])) { + $operations['delete']['attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW); + } + return $operations; + } + + /** + * {@inheritdoc} + */ + public function buildOperations(EntityInterface $entity) { + return parent::buildOperations($entity) + [ + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + } + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorage.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorage.php new file mode 100644 index 0000000000..7080efff97 --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorage.php @@ -0,0 +1,12 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityStorage; + +/** + * Storage controller class for "webform_access_type" configuration entities. + */ +class WebformAccessTypeStorage extends ConfigEntityStorage implements WebformAccessTypeStorageInterface { + +} diff --git a/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorageInterface.php b/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorageInterface.php new file mode 100644 index 0000000000..82c79e3cae --- /dev/null +++ b/web/modules/webform/modules/webform_access/src/WebformAccessTypeStorageInterface.php @@ -0,0 +1,13 @@ +<?php + +namespace Drupal\webform_access; + +use Drupal\Core\Config\Entity\ConfigEntityStorageInterface; +use Drupal\Core\Config\Entity\ImportableEntityStorageInterface; + +/** + * Provides an interface for Webform Access Group storage. + */ +interface WebformAccessTypeStorageInterface extends ConfigEntityStorageInterface, ImportableEntityStorageInterface { + +} diff --git a/web/modules/webform/modules/webform_access/webform_access.info.yml b/web/modules/webform/modules/webform_access/webform_access.info.yml new file mode 100644 index 0000000000..35b8a7749f --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.info.yml @@ -0,0 +1,14 @@ +name: 'Webform Access' +description: 'Provides webform access controls for webform nodes.' +# core: 8.x +package: Webform +type: module +dependencies: + - 'drupal:webform' + - 'drupal:webform_node' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_access/webform_access.install b/web/modules/webform/modules/webform_access/webform_access.install new file mode 100644 index 0000000000..a2267a8b0a --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.install @@ -0,0 +1,94 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the Webform access module. + */ + +use Drupal\Core\Entity\EntityTypeInterface; + +/** + * Implements hook_schema(). + */ +function webform_access_schema() { + $schema['webform_access_group_user'] = [ + 'description' => 'Stores users associated with a webform access group.', + 'fields' => [ + 'group_id' => [ + 'description' => 'The webform access group id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + 'uid' => [ + 'description' => 'The user id.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ], + ], + 'indexes' => [ + 'indexing' => ['group_id', 'uid'], + ], + 'primary key' => ['group_id', 'uid'], + ]; + + $schema['webform_access_group_entity'] = [ + 'description' => 'Stores users associated with a webform access group.', + 'fields' => [ + 'group_id' => [ + 'description' => 'The webform access group id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + 'entity_type' => [ + 'description' => 'The source entity type.', + 'type' => 'varchar', + 'length' => EntityTypeInterface::ID_MAX_LENGTH, + 'not null' => TRUE, + ], + 'entity_id' => [ + 'description' => 'The source entity id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + 'field_name' => [ + 'description' => 'The webform field name.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + 'webform_id' => [ + 'description' => 'The webform id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + ], + 'indexes' => [ + 'source_entity' => ['entity_type', 'entity_id', 'field_name', 'webform_id'], + ], + 'primary key' => ['group_id', 'webform_id', 'entity_type', 'entity_id', 'field_name'], + ]; + + return $schema; +} + +/** + * Issue #3034127: Error webform access with postgres. + */ +function webform_access_update_8001() { + // Converts storage for 'webform_access_group_entity.entity_id' + // from integer to string. + \Drupal::database() + ->schema() + ->changeField('webform_access_group_entity', 'entity_id', 'entity_id', [ + 'description' => 'The source entity id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ]); +} diff --git a/web/modules/webform/modules/webform_access/webform_access.libraries.yml b/web/modules/webform/modules/webform_access/webform_access.libraries.yml new file mode 100644 index 0000000000..c13c9bdf59 --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.libraries.yml @@ -0,0 +1,8 @@ +webform_access.admin: + version: VERSION + js: + js/webform_access.admin.js: {} + dependencies: + - core/drupal + - core/jquery + - core/jquery.once diff --git a/web/modules/webform/modules/webform_access/webform_access.links.action.yml b/web/modules/webform/modules/webform_access/webform_access.links.action.yml new file mode 100644 index 0000000000..2e98f69c2b --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.links.action.yml @@ -0,0 +1,11 @@ +entity.webform_access_group.add_form: + route_name: entity.webform_access_group.add_form + title: 'Add access group' + appears_on: + - entity.webform_access_group.collection + +entity.webform_access_type.add_form: + route_name: entity.webform_access_type.add_form + title: 'Add access type' + appears_on: + - entity.webform_access_type.collection diff --git a/web/modules/webform/modules/webform_access/webform_access.links.task.yml b/web/modules/webform/modules/webform_access/webform_access.links.task.yml new file mode 100644 index 0000000000..2329f888df --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.links.task.yml @@ -0,0 +1,29 @@ +webform.config.webform_access: + title: 'Access' + route_name: entity.webform_access_group.collection + base_route: entity.webform.collection + weight: 40 + +webform.config.webform_access_group: + title: 'Groups' + route_name: entity.webform_access_group.collection + parent_id: webform.config.webform_access + weight: 10 + +webform.config.webform_access_type: + title: 'Types' + route_name: entity.webform_access_type.collection + parent_id: webform.config.webform_access + weight: 20 + +entity.webform_access_group.edit_form: + route_name: entity.webform_access_group.edit_form + base_route: entity.webform_access_group.edit_form + title: 'Edit' + weight: 0 + +entity.webform_access_type.edit_form: + route_name: entity.webform_access_type.edit_form + base_route: entity.webform_access_type.edit_form + title: 'Edit' + weight: 0 diff --git a/web/modules/webform/modules/webform_access/webform_access.module b/web/modules/webform/modules/webform_access/webform_access.module new file mode 100644 index 0000000000..f3d122a5e4 --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.module @@ -0,0 +1,426 @@ +<?php + +/** + * @file + * Provides webform access controls for webform nodes. + */ + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform_access\Entity\WebformAccessGroup; + +/** + * Implements hook_webform_help_info(). + */ +function webform_access_webform_help_info() { + $help = []; + + // Access group. + $help['webform_access_group'] = [ + 'group' => 'access', + 'title' => t('Webform Access: Group'), + 'content' => t('The <strong>Access group</strong> page lists reusable groups used to access webform source entity and users.'), + 'video_id' => 'access', + 'routes' => [ + // @see /admin/structure/webform/access/group/manage + 'entity.webform_access_group.collection', + ], + ]; + + // Access type. + $help['webform_access_type'] = [ + 'type' => 'access', + 'title' => t('Webform Access: Type'), + 'content' => t('The <strong>Access type</strong> page lists types of groups used to send email notifications to users.'), + 'video_id' => 'access', + 'routes' => [ + // @see /admin/structure/webform/access/type/manage + 'entity.webform_access_type.collection', + ], + ]; + + return $help; +} + +/******************************************************************************/ +// Delete relationship hooks. +/******************************************************************************/ + +/** + * Implements hook_user_delete(). + */ +function webform_access_user_delete(EntityInterface $entity) { + \Drupal::database()->delete('webform_access_group_user') + ->condition('uid', $entity->id()) + ->execute(); +} + +/** + * Implements hook_node_delete(). + */ +function webform_access_node_delete(EntityInterface $entity) { + \Drupal::database()->delete('webform_access_group_entity') + ->condition('entity_type', 'node') + ->condition('entity_id', $entity->id()) + ->execute(); +} + +/** + * Implements hook_field_config_delete(). + */ +function webform_access_field_config_delete(EntityInterface $entity) { + /** @var Drupal\field\Entity\FieldConfig $definition */ + if ($entity->getType() === 'webform' && $entity->getEntityTypeId() === 'node') { + $entity_ids = \Drupal::entityQuery('webform_access_group') + ->condition('type', $entity->getTargetBundle()) + ->execute(); + if ($entity_ids) { + \Drupal::database()->delete('webform_access_group_entity') + ->condition('entity_type', 'node') + ->condition('entity_id', $entity_ids, 'IN') + ->condition('field_name', $entity->getName()) + ->execute(); + } + } +} + +/** + * Implements hook_field_storage_config_delete(). + */ +function webform_access_field_storage_config_delete(EntityInterface $entity) { + /** @var Drupal\field\Entity\FieldStorageConfig $entity */ + if ($entity->getType() === 'webform') { + \Drupal::database()->delete('webform_access_group_entity') + ->condition('entity_type', $entity->getEntityTypeId()) + ->condition('field_name', $entity->getName()) + ->execute(); + } +} + +/******************************************************************************/ +// Access checking. +/******************************************************************************/ + +/** + * Implements hook_menu_local_tasks_alter(). + * + * Add webform access group to local task cacheability. + * + * @see \Drupal\Core\Menu\Plugin\Block\LocalTasksBlock::build + */ +function webform_access_menu_local_tasks_alter(&$data, $route_name) { + // Change config entities 'Translate *' tab to be just label 'Translate'. + $webform_entities = [ + 'webform_access_group', + 'webform_access_type', + ]; + foreach ($webform_entities as $webform_entity) { + if (isset($data['tabs'][0]["config_translation.local_tasks:entity.$webform_entity.config_translation_overview"]['#link']['title'])) { + $data['tabs'][0]["config_translation.local_tasks:entity.$webform_entity.config_translation_overview"]['#link']['title'] = t('Translate'); + } + } + + $route_name = \Drupal::routeMatch()->getRouteName(); + if ($route_name !== 'entity.node.canonical' && strpos($route_name, 'entity.node.webform.') !== 0) { + return; + } + + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + $account = \Drupal::currentUser(); + $webform = $request_handler->getCurrentWebform(); + $source_entity = $request_handler->getCurrentSourceEntity(); + if (!$webform || $source_entity) { + return; + } + + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $webform_access_group */ + $webform_access_group_storage = \Drupal::entityTypeManager()->getStorage('webform_access_group'); + $webform_access_groups = $webform_access_group_storage->loadByEntities($webform, $source_entity, $account); + if (empty($webform_access_groups)) { + return; + } + + /** @var \Drupal\Core\Cache\CacheableMetadata $cacheability */ + $cacheability = $data['cacheability']; + foreach ($webform_access_groups as $webforn_access_group) { + $cacheability->addCacheableDependency($webforn_access_group); + } +} + +/** + * Implements hook_ENTITY_TYPE_access(). + */ +function webform_access_webform_access(WebformInterface $webform, $operation, AccountInterface $account) { + // Prevent recursion when a webform is being passed as the source entity + // via the URL. + // @see \Drupal\webform\Plugin\WebformSourceEntity\QueryStringWebformSourceEntity::getSourceEntity + if (\Drupal::request()->query->get('source_entity_type') === 'webform') { + return AccessResult::neutral(); + } + + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + $source_entity = $request_handler->getCurrentSourceEntity(); + if (!$source_entity) { + return AccessResult::neutral(); + } + + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $webform_access_group */ + $webform_access_group_storage = \Drupal::entityTypeManager()->getStorage('webform_access_group'); + $webform_access_groups = $webform_access_group_storage->loadByEntities($webform, $source_entity, $account); + if (empty($webform_access_groups)) { + return AccessResult::neutral(); + } + + $permission = str_replace('submission_', '', $operation); + foreach ($webform_access_groups as $webforn_access_group) { + $permissions = $webforn_access_group->get('permissions'); + if ( + // Is admin. + in_array('administer', $permissions) + // Is operation any. + || in_array($permission, $permissions)) { + return AccessResult::allowed() + ->cachePerUser() + ->addCacheableDependency($webforn_access_group); + } + } + + // No opinion. + return AccessResult::neutral(); +} + +/** + * Implements hook_ENTITY_TYPE_access(). + */ +function webform_access_webform_submission_access(WebformSubmissionInterface $webform_submission, $operation, AccountInterface $account) { + if (!in_array($operation, ['view', 'update', 'delete'])) { + return AccessResult::neutral(); + } + + $webform = $webform_submission->getWebform(); + $source_entity = $webform_submission->getSourceEntity(); + if (!$source_entity) { + return AccessResult::neutral(); + } + + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $webform_access_group */ + $webform_access_group_storage = \Drupal::entityTypeManager()->getStorage('webform_access_group'); + $webform_access_groups = $webform_access_group_storage->loadByEntities($webform, $source_entity, $account); + if (empty($webform_access_groups)) { + return AccessResult::neutral(); + } + + foreach ($webform_access_groups as $webforn_access_group) { + $permissions = $webforn_access_group->get('permissions'); + if ( + // Is admin. + (in_array('administer', $permissions)) || + // Is operation any. + (in_array($operation . '_any', $permissions)) || + // Is operation own. + (in_array($operation . '_own', $permissions) && $webform_submission->getOwnerId() == $account->id()) + ) { + return AccessResult::allowed() + ->cachePerUser() + ->addCacheableDependency($webforn_access_group); + } + } + + // No opinion. + return AccessResult::neutral(); +} + +/******************************************************************************/ +// Webform access groups (node) entity. +/******************************************************************************/ + +/** + * Implements hook_field_widget_form_alter(). + */ +function webform_access_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) { + /** @var \Drupal\Core\Field\FieldItemListInterface $items */ + $items = $context['items']; + $field_definition = $items->getFieldDefinition(); + if ($field_definition->getType() !== 'webform') { + return; + } + if ($items->getEntity()->getEntityTypeId() !== 'node') { + return; + } + + $node = $items->getEntity(); + + $default_value = ($node->id()) ? \Drupal::database()->select('webform_access_group_entity', 'ge') + ->fields('ge', ['group_id']) + ->condition('entity_type', 'node') + ->condition('entity_id', $node->id()) + ->condition('webform_id', $element['target_id']['#default_value']) + ->condition('field_name', $field_definition->getName()) + ->execute()->fetchCol() : []; + + $element['settings']['webform_access_group'] = _webform_access_group_build_element( + $default_value, + [], + $form_state + ); +} + +/** + * Implements hook_form_BASE_FORM_ID_alter(). + */ +function webform_access_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) { + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ + $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); + $node = $form_state->getFormObject()->getEntity(); + $field_names = $entity_reference_manager->getFieldNames($node); + if ($field_names) { + $form['actions']['submit']['#submit'][] = '_webform_access_form_node_form_submit'; + } +} + +/** + * Webform access group submit handler. + */ +function _webform_access_form_node_form_submit(&$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ + $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); + $node = $form_state->getFormObject()->getEntity(); + $field_names = $entity_reference_manager->getFieldNames($node); + + $record = [ + 'entity_type' => 'node', + 'entity_id' => $node->id(), + ]; + foreach ($field_names as $field_name) { + $value = $form_state->getValue($field_name); + // Handle hidden webform fields. + if ($value === NULL) { + continue; + } + + $record['field_name'] = $field_name; + // Delete all existing records. + \Drupal::database()->delete('webform_access_group_entity') + ->condition('entity_type', $record['entity_type']) + ->condition('entity_id', $record['entity_id']) + ->condition('field_name', $record['field_name']) + ->execute(); + foreach ($value as $item) { + $record['webform_id'] = $item['target_id']; + foreach ($item['settings']['webform_access_group'] as $group_id) { + $record['group_id'] = $group_id; + // Insert new record. + \Drupal::database()->insert('webform_access_group_entity') + ->fields(['group_id', 'entity_type', 'entity_id', 'field_name', 'webform_id']) + ->values($record) + ->execute(); + // Invalidate cache tags. + WebformAccessGroup::load($group_id)->invalidateTags(); + } + } + } +} + +/******************************************************************************/ +// Webform access group users. +/******************************************************************************/ + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Add the webform access group to an individual user's account page. + */ +function webform_access_form_user_form_alter(&$form, FormStateInterface $form_state) { + // Make sure some webform access groups exist before displaying + // the webform access details widget. + if (!WebformAccessGroup::loadMultiple()) { + return; + } + + // Only display the webform access detail widget if the current user can + // administer webform and users. + if (!\Drupal::currentUser()->hasPermission('administer webform') + || !\Drupal::currentUser()->hasPermission('administer users')) { + return; + } + + $account = $form_state->getFormObject()->getEntity(); + $default_value = \Drupal::database()->select('webform_access_group_user', 'gu') + ->fields('gu', ['group_id']) + ->condition('uid', $account->id()) + ->execute()->fetchCol(); + + $form['webform_access'] = [ + '#type' => 'details', + '#title' => t('Webform access'), + '#open' => TRUE, + '#weight' => 5, + ]; + $form['webform_access']['webform_access_group'] = _webform_access_group_build_element( + $default_value, + $form, + $form_state + ); + + $form['actions']['submit']['#submit'][] = '_webform_access_user_profile_form_submit'; +} + +/** + * Submit callback for the user profile form to save the webform_access user setting. + */ +function _webform_access_user_profile_form_submit($form, FormStateInterface $form_state) { + $account = $form_state->getFormObject()->getEntity(); + // Delete all existing records. + \Drupal::database()->delete('webform_access_group_user') + ->condition('uid', $account->id()) + ->execute(); + $record = ['uid' => $account->id()]; + $value = $form_state->getValue('webform_access_group'); + foreach ($value as $group_id) { + $record['group_id'] = $group_id; + // Insert new record. + \Drupal::database()->insert('webform_access_group_user') + ->fields(['group_id', 'uid']) + ->values($record) + ->execute(); + WebformAccessGroup::load($group_id)->invalidateTags(); + } +} + +/******************************************************************************/ +// Webform access group helper functions. +/******************************************************************************/ + +/** + * Build element used to select webform access groups. + * + * @param array $default_value + * Array of default group ids. + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * Element used to select webform access groups. + */ +function _webform_access_group_build_element(array $default_value, array $form, FormStateInterface $form_state) { + $element = [ + '#type' => 'webform_entity_select', + '#title' => 'Access group', + '#target_type' => 'webform_access_group', + '#selection_handler' => 'default:webform_access_group', + '#multiple' => TRUE, + '#select2' => TRUE, + '#default_value' => $default_value, + '#access' => \Drupal::currentUser()->hasPermission('administer webform'), + ]; + return WebformElementHelper::process($element); +} diff --git a/web/modules/webform/modules/webform_access/webform_access.routing.yml b/web/modules/webform/modules/webform_access/webform_access.routing.yml new file mode 100644 index 0000000000..50027dddd7 --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.routing.yml @@ -0,0 +1,75 @@ +# Access group. + +entity.webform_access_group.collection: + path: '/admin/structure/webform/access/group/manage' + defaults: + _entity_list: 'webform_access_group' + _title: 'Webforms: Access Group' + requirements: + _permission: 'administer webform' + +entity.webform_access_group.add_form: + path: '/admin/structure/webform/access/group/manage/add' + defaults: + _entity_form: 'webform_access_group.add' + _title: 'Add webform access group' + requirements: + _entity_create_access: 'webform_access_group' + +entity.webform_access_group.edit_form: + path: '/admin/structure/webform/access/group/manage/{webform_access_group}' + defaults: + _entity_form: 'webform_access_group.edit' + _title: 'Edit webform access group' + requirements: + _entity_access: 'webform_access_group.update' + +entity.webform_access_group.duplicate_form: + path: '/admin/structure/webform/access/group/{webform_access_group}/duplicate' + defaults: + _entity_form: 'webform_access_group.duplicate' + _title: 'Duplicate webform access group' + requirements: + _entity_access: 'webform_access_group.duplicate' + +entity.webform_access_group.delete_form: + path: '/admin/structure/webform/access/group/{webform_access_group}/delete' + defaults: + _entity_form: 'webform_access_group.delete' + _title: 'Delete webform access group' + requirements: + _entity_access: 'webform_access_group.delete' + +# Access type. + +entity.webform_access_type.collection: + path: '/admin/structure/webform/access/type/manage' + defaults: + _entity_list: 'webform_access_type' + _title: 'Webforms: Access Group' + requirements: + _permission: 'administer webform' + +entity.webform_access_type.add_form: + path: '/admin/structure/webform/access/type/manage/add' + defaults: + _entity_form: 'webform_access_type.add' + _title: 'Add webform access type' + requirements: + _entity_create_access: 'webform_access_type' + +entity.webform_access_type.edit_form: + path: '/admin/structure/webform/access/type/manage/{webform_access_type}' + defaults: + _entity_form: 'webform_access_type.edit' + _title: 'Edit webform access type' + requirements: + _entity_access: 'webform_access_type.update' + +entity.webform_access_type.delete_form: + path: '/admin/structure/webform/access/type/{webform_access_type}/delete' + defaults: + _entity_form: 'webform_access_type.delete' + _title: 'Delete webform access type' + requirements: + _entity_access: 'webform_access_type.delete' diff --git a/web/modules/webform/modules/webform_access/webform_access.services.yml b/web/modules/webform/modules/webform_access/webform_access.services.yml new file mode 100644 index 0000000000..fcabf34ff8 --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.services.yml @@ -0,0 +1,6 @@ +services: + webform_access.breadcrumb: + class: Drupal\webform_access\Breadcrumb\WebformAccessBreadcrumbBuilder + arguments: ['@string_translation'] + tags: + - { name: breadcrumb_builder, priority: 1003 } diff --git a/web/modules/webform/modules/webform_access/webform_access.tokens.inc b/web/modules/webform/modules/webform_access/webform_access.tokens.inc new file mode 100644 index 0000000000..be5fc5aa40 --- /dev/null +++ b/web/modules/webform/modules/webform_access/webform_access.tokens.inc @@ -0,0 +1,77 @@ +<?php + +/** + * @file + * Builds placeholder replacement tokens for webform access type. + */ + +use Drupal\Core\Render\BubbleableMetadata; +use Drupal\webform_access\Entity\WebformAccessType; + +/** + * Implements hook_token_info(). + */ +function webform_access_token_info() { + $types = []; + $types['webform_access'] = [ + 'name' => t('Webform access'), + 'description' => t("Tokens related to webform access group types. <em>This token is only available to a Webform email handler's 'To', 'CC', and 'BCC' email recipents.</em>"), + 'needs-data' => 'webform_access', + ]; + + $tokens = []; + $webform_access = []; + $webform_access_types = WebformAccessType::loadMultiple(); + $webform_access['type'] = [ + 'name' => t('All users'), + 'description' => t('The email addresses of all users assigned to the current webform.'), + ]; + foreach ($webform_access_types as $webform_access_type_name => $webform_access_type) { + $webform_access['type:' . $webform_access_type_name] = [ + 'name' => $webform_access_type->label(), + 'description' => t('The email addresses of all webform users assigned to the %title access type for the current webform.', ['%title' => $webform_access_type->label()]), + ]; + } + $tokens['webform_access'] = $webform_access; + + /****************************************************************************/ + + return ['types' => $types, 'tokens' => $tokens]; +} + +/** + * Implements hook_tokens(). + */ +function webform_access_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $replacements = []; + if ($type == 'webform_access' && !empty($data['webform_access'])) { + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $webform_access_group_storage */ + $webform_access_group_storage = \Drupal::entityTypeManager()->getStorage('webform_access_group'); + + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submision */ + $webform_submision = $data['webform_access']; + $webform = $webform_submision->getWebform(); + $source_entity = $webform_submision->getSourceEntity(); + foreach ($tokens as $name => $original) { + $webform_access_type_id = ($name === 'type') ? NULL : str_replace('type:', '', $name); + + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $webform_access_group */ + $webform_access_groups = $webform_access_group_storage->loadByEntities($webform, $source_entity, NULL, $webform_access_type_id); + + if ($webform_access_groups) { + $query = \Drupal::database()->select('webform_access_group_user', 'gu'); + $query->condition('gu.group_id', array_keys($webform_access_groups), 'IN'); + $query->join('users_field_data', 'u', 'u.uid = gu.uid'); + $query->fields('u', ['mail']); + $query->condition('u.status', 1); + $query->condition('u.mail', '', '<>'); + $query->orderBy('mail'); + $query->distinct(); + + $replacements[$original] = implode(',', $query->execute()->fetchCol()); + } + } + } + + return $replacements; +} diff --git a/web/modules/webform/modules/webform_attachment/src/Controller/WebformAttachmentController.php b/web/modules/webform/modules/webform_attachment/src/Controller/WebformAttachmentController.php new file mode 100644 index 0000000000..ea89ce25d3 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Controller/WebformAttachmentController.php @@ -0,0 +1,125 @@ +<?php + +namespace Drupal\webform_attachment\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform_attachment\Plugin\WebformElement\WebformAttachmentBase; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Defines a controller to return a webform attachment. + */ +class WebformAttachmentController extends ControllerBase implements ContainerInjectionInterface { + + /** + * Element info. + * + * @var \Drupal\Core\Render\ElementInfoManager + */ + protected $elementInfo; + + /** + * A webform element plugin manager. + * + * @var \Drupal\webform\Plugin\WebformElementManagerInterface + */ + protected $elementManager; + + /** + * Constructs a WebformAttachmentController object. + * + * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info + * The element info manager. + * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager + * A webform element plugin manager. + */ + public function __construct(ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager) { + $this->elementInfo = $element_info; + $this->elementManager = $element_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.element_info'), + $container->get('plugin.manager.webform.element') + ); + } + + /** + * Response callback to download an attachment. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param string $element + * The attachment element webform key. + * @param string $filename + * The attachment filename. + * + * @return \Symfony\Component\HttpFoundation\Response + * A response containing the attachment's file. + */ + public function download(WebformInterface $webform, WebformSubmissionInterface $webform_submission, $element, $filename) { + // Make sure the webform id and submission webform id match. + if ($webform->id() !== $webform_submission->getWebform()->id()) { + throw new NotFoundHttpException(); + } + + // Get the webform element and plugin. + $element = $webform_submission->getWebform()->getElement($element) ?: []; + $element_plugin = $this->elementManager->getElementInstance($element); + + // Make sure the element is a webform attachment. + if (!$element_plugin instanceof WebformAttachmentBase) { + throw new NotFoundHttpException(); + } + + // Make sure element #access is not FALSE. + // The #private property is used to to set #access to FALSE. + // @see \Drupal\webform\Entity\Webform::initElementsRecursive + if (isset($element['#access']) && $element['#access'] === FALSE) { + throw new AccessDeniedHttpException(); + } + + // Make sure the current user can view the element. + if (!$element_plugin->checkAccessRules('view', $element)) { + throw new AccessDeniedHttpException(); + } + + /** @var \Drupal\webform_attachment\Element\WebformAttachmentInterface $element_plugin */ + $element_info = $this->elementInfo->createInstance($element['#type']); + + // Get attachment information. + $attachment_name = $element_info::getFileName($element, $webform_submission); + $attachment_mime = $element_info::getFileMimeType($element, $webform_submission); + $attachment_content = $element_info::getFileContent($element, $webform_submission); + $attachment_size = mb_strlen($attachment_content); + $attachment_download = (!empty($element['#download'])) ? 'attachment;' : ''; + + // Make sure the attachment can be downloaded. + if (empty($attachment_name) || empty($attachment_content) || empty($attachment_mime)) { + throw new NotFoundHttpException(); + } + + // Return the file. + $headers = [ + 'Content-Length' => $attachment_size, + 'Content-Type' => $attachment_mime, + 'Content-Disposition' => $attachment_download . 'filename="' . $filename . '"', + ]; + return new Response($attachment_content, 200, $headers); + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentBase.php b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentBase.php new file mode 100644 index 0000000000..f873d12c60 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentBase.php @@ -0,0 +1,153 @@ +<?php + +namespace Drupal\webform_attachment\Element; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\RenderElement; +use Drupal\Core\Url; +use Drupal\webform\WebformSubmissionForm; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides a base class for 'webform_attachment' elements. + */ +abstract class WebformAttachmentBase extends RenderElement implements WebformAttachmentInterface { + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#filename' => '', + '#sanitize' => FALSE, + '#link_title' => '', + '#download' => FALSE, + '#trim' => FALSE, + '#process' => [ + [$class, 'processWebformAttachment'], + [$class, 'processAjaxForm'], + ], + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * Processes a 'webform_attachment' element. + */ + public static function processWebformAttachment(&$element, FormStateInterface $form_state, &$complete_form) { + $form_object = $form_state->getFormObject(); + + // Attachments only work for webform submissions. + if (!$form_object instanceof WebformSubmissionForm) { + $element['#access'] = FALSE; + return $element; + } + + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $form_object->getEntity(); + + // Attachments only work for completed and saved webform submissions. + if (!$webform_submission->id() || !$webform_submission->isCompleted()) { + $element['#access'] = FALSE; + return $element; + } + + // Link to file download. + $element['link'] = static::getFileLink($element, $webform_submission); + + return $element; + } + + /****************************************************************************/ + // File methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public static function getFileName(array $element, WebformSubmissionInterface $webform_submission) { + if (isset($element['#filename'])) { + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + + $filename = $token_manager->replace($element['#filename'], $webform_submission); + + // Remove forward slashes from filename to prevent the below error. + // + // Parameter "filename" for route + // "entity.webform.user.submission.attachment" must match "[^/]++". + $filename = str_replace('/', '', $filename); + + // Sanitize filename. + // @see http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename + // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::getFileDestinationUri + if (!empty($element['#sanitize'])) { + /** @var \Drupal\Component\Transliteration\TransliterationInterface $transliteration */ + $transliteration = \Drupal::service('transliteration'); + /** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */ + $language_manager = \Drupal::service('language_manager'); + $langcode = $language_manager->getCurrentLanguage()->getId(); + + $extension = pathinfo($filename, PATHINFO_EXTENSION); + + $basename = substr(pathinfo($filename, PATHINFO_BASENAME), 0, -strlen(".$extension")); + $basename = mb_strtolower($basename); + $basename = $transliteration->transliterate($basename, $langcode, '-'); + $basename = preg_replace('([^\w\s\d\-_~,;:\[\]\(\].]|[\.]{2,})', '', $basename); + $basename = preg_replace('/\s+/', '-', $basename); + $basename = trim($basename, '-'); + $filename = $basename . '.' . $extension; + } + + return $filename; + } + else { + return $element['#webform_key'] . '.txt'; + } + } + + /** + * {@inheritdoc} + */ + public static function getFileMimeType(array $element, WebformSubmissionInterface $webform_submission) { + /** @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $file_mime_type_guesser */ + $file_mime_type_guesser = \Drupal::service('file.mime_type.guesser'); + $file_name = static::getFileName($element, $webform_submission); + return $file_mime_type_guesser->guess($file_name); + } + + /** + * {@inheritdoc} + */ + public static function getFileUrl(array $element, WebformSubmissionInterface $webform_submission) { + if (!$webform_submission->id()) { + return NULL; + } + + $route_name = 'entity.webform.user.submission.attachment'; + $route_parameters = [ + 'webform' => $webform_submission->getWebform()->id(), + 'webform_submission' => $webform_submission->id(), + 'element' => $element['#webform_key'], + 'filename' => static::getFileName($element, $webform_submission), + ]; + $route_options = ['absolute' => TRUE]; + $url = Url::fromRoute($route_name, $route_parameters, $route_options); + return $url; + } + + /** + * {@inheritdoc} + */ + public static function getFileLink(array $element, WebformSubmissionInterface $webform_submission) { + $title = (!empty($element['#link_title'])) ? $element['#link_title'] : static::getFileName($element, $webform_submission); + $url = static::getFileUrl($element, $webform_submission); + return [ + '#type' => 'link', + '#title' => $title, + '#url' => $url, + ]; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentInterface.php b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentInterface.php new file mode 100644 index 0000000000..412ce8e7a8 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentInterface.php @@ -0,0 +1,78 @@ +<?php + +namespace Drupal\webform_attachment\Element; + +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides an interface for webform attachment elements. + */ +interface WebformAttachmentInterface { + + /** + * Get a webform attachment's file name. + * + * @param array $element + * The webform attachment element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return mixed|string + * The attachment's file name. + */ + public static function getFileName(array $element, WebformSubmissionInterface $webform_submission); + + /** + * Get a webform attachment's file content. + * + * @param array $element + * The webform attachment element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return mixed|string + * The attachment's file content. + */ + public static function getFileContent(array $element, WebformSubmissionInterface $webform_submission); + + /** + * Get a webform attachment's file type. + * + * @param array $element + * The webform attachment element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return mixed|string + * The attachment's file type. + */ + public static function getFileMimeType(array $element, WebformSubmissionInterface $webform_submission); + + /** + * Get a webform attachment's download URL. + * + * @param array $element + * The webform attachment element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return \Drupal\Core\Url|null + * A webform attachment's download URL. Return NULL if the submission is + * not saved to the database. + */ + public static function getFileUrl(array $element, WebformSubmissionInterface $webform_submission); + + /** + * Get a webform attachment's file link. + * + * @param array $element + * The webform attachment element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return array + * A renderable array containing a link to the webform attachment's URL. + */ + public static function getFileLink(array $element, WebformSubmissionInterface $webform_submission); + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentToken.php b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentToken.php new file mode 100644 index 0000000000..b7dd1d75e5 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentToken.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\webform_attachment\Element; + +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides a 'webform_attachment_token' element. + * + * @FormElement("webform_attachment_token") + */ +class WebformAttachmentToken extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#template' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getFileContent(array $element, WebformSubmissionInterface $webform_submission) { + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $content = $token_manager->replace($element['#template'], $webform_submission); + return (!empty($element['#trim'])) ? trim($content) : $content; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentTwig.php b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentTwig.php new file mode 100644 index 0000000000..d029af026b --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentTwig.php @@ -0,0 +1,34 @@ +<?php + +namespace Drupal\webform_attachment\Element; + +use Drupal\webform\Twig\TwigExtension; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides a 'webform_attachment_twig' element. + * + * @FormElement("webform_attachment_twig") + */ +class WebformAttachmentTwig extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#template' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getFileContent(array $element, WebformSubmissionInterface $webform_submission) { + $options = []; + $template = $element['#template']; + $content = TwigExtension::renderTwigTemplate($webform_submission, $template, $options); + return (!empty($element['#trim'])) ? trim($content) : $content; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentUrl.php b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentUrl.php new file mode 100644 index 0000000000..635cdcc541 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Element/WebformAttachmentUrl.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\webform_attachment\Element; + +use Drupal\webform\WebformSubmissionInterface; +use GuzzleHttp\Exception\RequestException; + +/** + * Provides a 'webform_attachment_url' element. + * + * @FormElement("webform_attachment_url") + */ +class WebformAttachmentUrl extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#url' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getFileContent(array $element, WebformSubmissionInterface $webform_submission) { + try { + $url = $element['#url']; + // URL can contain tokens. + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $url = $token_manager->replace($url, $webform_submission); + // Prepend scheme and host to root relative path. + if (strpos($url, '/') === 0) { + $url = \Drupal::request()->getSchemeAndHttpHost() . $url; + } + $content = (string) \Drupal::httpClient()->get($url)->getBody(); + } + catch (RequestException $exception) { + $content = ''; + } + return (!empty($element['#trim'])) ? trim($content) : $content; + } + + /** + * {@inheritdoc} + */ + public static function getFileName(array $element, WebformSubmissionInterface $webform_submission) { + if (!isset($element['#filename']) && !empty($element['#url'])) { + return basename($element['#url']); + } + else { + return parent::getFileName($element, $webform_submission); + } + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentBase.php b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentBase.php new file mode 100644 index 0000000000..360b96dd9a --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentBase.php @@ -0,0 +1,238 @@ +<?php + +namespace Drupal\webform_attachment\Plugin\WebformElement; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Element\WebformMessage; +use Drupal\webform\Plugin\WebformElement\WebformDisplayOnTrait; +use Drupal\webform\Plugin\WebformElementAttachmentInterface; +use Drupal\webform\Plugin\WebformElementBase; +use Drupal\webform\Plugin\WebformElementDisplayOnInterface; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides a base class for 'webform_attachment' elements. + */ +abstract class WebformAttachmentBase extends WebformElementBase implements WebformElementAttachmentInterface, WebformElementDisplayOnInterface { + + use WebformDisplayOnTrait; + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + // Element settings. + 'title' => '', + // Form display. + 'title_display' => '', + // Display settings. + 'display_on' => static::DISPLAY_ON_NONE, + // Attachment values. + 'filename' => '', + 'sanitize' => FALSE, + 'link_title' => '', + 'trim' => FALSE, + 'download' => FALSE, + // Attributes. + 'wrapper_attributes' => [], + 'label_attributes' => [], + ] + $this->getDefaultBaseProperties(); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultBaseProperties() { + $properties = parent::getDefaultBaseProperties(); + unset($properties['prepopulate']); + unset($properties['states_clear']); + return $properties; + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + /** @var \Drupal\webform_attachment\Element\WebformAttachmentInterface $attachment_element */ + $attachment_element = $this->getFormElementClassDefinition(); + + $format = $this->getItemFormat($element); + switch ($format) { + case 'name'; + return $attachment_element::getFileName($element, $webform_submission); + + case 'url'; + return $attachment_element::getFileUrl($element, $webform_submission)->toString(); + + default: + case 'link': + return $attachment_element::getFileLink($element, $webform_submission); + } + } + + /** + * {@inheritdoc} + */ + protected function formatTextItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + /** @var \Drupal\webform_attachment\Element\WebformAttachmentInterface $attachment_element */ + $attachment_element = $this->getFormElementClassDefinition(); + + $format = $this->getItemFormat($element); + switch ($format) { + case 'name'; + return $attachment_element::getFileName($element, $webform_submission); + + default: + case 'link'; + case 'url'; + return $attachment_element::getFileUrl($element, $webform_submission)->toString(); + } + } + + /** + * {@inheritdoc} + */ + public function hasValue(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return [ + 'link' => $this->t('File link'), + 'name' => $this->t('File name'), + 'url' => $this->t('File URL'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'link'; + } + + /** + * {@inheritdoc} + */ + public function preSave(array &$element, WebformSubmissionInterface $webform_submission) { + $key = $element['#webform_key']; + $data = $webform_submission->getData(); + // Make sure attachment element never stores a value. + if (isset($data[$key])) { + unset($data[$key]); + $webform_submission->setData($data); + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['attachment'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Attachment settings'), + ]; + $form['attachment']['message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t("Please make sure to enable 'Include files as attachments' for each email handler that you want to send this attachment."), + '#message_type' => 'warning', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#access' => TRUE, + ]; + $form['attachment']['display_on'] = [ + '#type' => 'select', + '#title' => $this->t('Display on'), + '#options' => $this->getDisplayOnOptions(TRUE), + ]; + $form['attachment']['display_on_message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t("The attachment's link will only be diplayed on the form after the submission is completed."), + '#message_type' => 'warning', + '#access' => TRUE, + '#states' => [ + 'visible' => [ + [':input[name="properties[display_on]"]' => ['value' => 'form']], + 'or', + [':input[name="properties[display_on]"]' => ['value' => 'both']], + ], + ], + ]; + $form['attachment']['filename'] = [ + '#type' => 'textfield', + '#title' => $this->t('File name'), + '#description' => $this->t("Please enter the attachment's file name with a file extension. The file extension will be used to determine the attachment's content (mime) type."), + '#maxlength' => NULL, + '#required' => TRUE, + ]; + $form['attachment']['link_title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Link title'), + '#description' => $this->t('Enter the title to be displayed when the attachment is displayed as a link.'), + ]; + $form['attachment']['trim'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + '#title' => $this->t("Remove whitespace around the attachment's content"), + '#description' => $this->t("If checked, all spaces and returns around the attachment's content with be removed."), + '#weight' => 10, + ]; + $form['attachment']['sanitize'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + '#title' => $this->t('Sanitize file name'), + '#description' => $this->t('If checked, file name will be transliterated, lower-cased and all special characters converted to dashes (-).'), + '#weight' => 10, + ]; + $form['attachment']['download'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + '#title' => $this->t('Force users to download the attachment'), + '#description' => $this->t('If checked the attachment will be automatically download.'), + '#weight' => 10, + ]; + $form['attachment']['tokens'] = ['#access' => TRUE, '#weight' => 10] + $this->tokenManager->buildTreeElement(); + return $form; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Attachment elements should never get a test value. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getAttachments(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + /** @var \Drupal\webform_attachment\Element\WebformAttachmentInterface $attachment_element */ + $attachment_element = $this->getFormElementClassDefinition(); + + $file_content = $attachment_element::getFileContent($element, $webform_submission); + $file_name = $attachment_element::getFileName($element, $webform_submission); + $file_mime = $attachment_element::getFileMimeType($element, $webform_submission); + $file_url = $attachment_element::getFileUrl($element, $webform_submission); + + $attachments = []; + if ($file_name && $file_content && $file_mime) { + $attachments[] = [ + 'filecontent' => $file_content, + 'filename' => $file_name, + 'filemime' => $file_mime, + // URI is used when debugging or resending messages. + // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::buildAttachments + '_uri' => ($file_url) ? $file_url->toString() : NULL, + ]; + } + return $attachments; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentToken.php b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentToken.php new file mode 100644 index 0000000000..11c9ff4450 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentToken.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\webform_attachment\Plugin\WebformElement; + +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a 'webform_attachment_token' element. + * + * @WebformElement( + * id = "webform_attachment_token", + * label = @Translation("Attachment token"), + * description = @Translation("Generates an attachment using tokens."), + * category = @Translation("File attachment elements"), + * ) + */ +class WebformAttachmentToken extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'template' => '', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['attachment']['template'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'text', + '#title' => $this->t('Template'), + ]; + return $form; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentTwig.php b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentTwig.php new file mode 100644 index 0000000000..0956502742 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentTwig.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\webform_attachment\Plugin\WebformElement; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Twig\TwigExtension; +use Drupal\webform\Utility\WebformElementHelper; + +/** + * Provides a 'webform_attachment_twig' element. + * + * @WebformElement( + * id = "webform_attachment_twig", + * label = @Translation("Attachment Twig"), + * description = @Translation("Generates an attachment using Twig."), + * category = @Translation("File attachment elements"), + * ) + */ +class WebformAttachmentTwig extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'template' => '', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['attachment']['template'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'twig', + '#title' => $this->t('Twig'), + ]; + $form['attachment']['help'] = TwigExtension::buildTwigHelp(); + WebformElementHelper::setPropertyRecursive($form['attachment']['help'], '#access', TRUE); + + return $form; + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentUrl.php b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentUrl.php new file mode 100644 index 0000000000..4b393813c2 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Plugin/WebformElement/WebformAttachmentUrl.php @@ -0,0 +1,78 @@ +<?php + +namespace Drupal\webform_attachment\Plugin\WebformElement; + +use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Form\FormStateInterface; +use GuzzleHttp\Exception\ClientException; + +/** + * Provides a 'webform_attachment_url' element. + * + * @WebformElement( + * id = "webform_attachment_url", + * label = @Translation("Attachment URL"), + * description = @Translation("Generates an attachment using a URL."), + * category = @Translation("File attachment elements"), + * ) + */ +class WebformAttachmentUrl extends WebformAttachmentBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'url' => '', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['attachment']['url'] = [ + '#type' => 'textfield', + '#title' => $this->t('URL/path'), + '#description' => $this->t("Make sure the attachment URL/Path is publicly accessible. The attachment's URL/path will never be displayed to end users."), + '#required' => TRUE, + '#element_validate' => [[get_class($this), 'validateAttachmentUrl']], + ]; + if (function_exists('imce_process_url_element')) { + $url_element = &$form['attachment']['url']; + imce_process_url_element($url_element, 'link'); + $form['#attached']['library'][] = 'webform/imce.input'; + } + return $form; + } + + /** + * Form API callback. Validate url/path. + * + * @see \Drupal\Core\Render\Element\Url::validateUrl + */ + public static function validateAttachmentUrl(&$element, FormStateInterface &$form_state) { + $value = trim($element['#value']); + $form_state->setValueForElement($element, $value); + + // Prepend scheme and host to root relative path. + if (strpos($value, '/') === 0) { + $value = \Drupal::request()->getSchemeAndHttpHost() . $value; + } + + // Validate URL formatting. + if ($value !== '' && !UrlHelper::isValid($value, TRUE)) { + $form_state->setError($element, t('The URL %url is not valid.', ['%url' => $value])); + } + + // Validate URL access. + try { + \Drupal::httpClient()->head($value); + } + catch (ClientException $e) { + $form_state->setError($element, t('The URL <a href=":url">@url</a> is not available.', [':url' => $value, '@url' => $value])); + } + } + +} diff --git a/web/modules/webform/modules/webform_attachment/src/Tests/WebformAttachmentTest.php b/web/modules/webform/modules/webform_attachment/src/Tests/WebformAttachmentTest.php new file mode 100644 index 0000000000..26271a4d81 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/src/Tests/WebformAttachmentTest.php @@ -0,0 +1,230 @@ +<?php + +namespace Drupal\webform_attachment\Tests; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\WebformTestBase; +use Drupal\webform_attachment\Element\WebformAttachmentToken; + +/** + * Tests for webform example element. + * + * @group Webform + */ +class WebformAttachmentTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['token', 'webform_attachment', 'webform_attachment_test']; + + /** + * Tests webform attachment. + */ + public function testWebformAttachment() { + global $base_url; + + $this->drupalLogin($this->rootUser); + + /**************************************************************************/ + // Email. + /**************************************************************************/ + + $webform_id = 'test_attachment_email'; + $webform_attachment_email = Webform::load('test_attachment_email'); + $attachment_date = date('Y-m-d'); + + // Check that the attachment is added to the sent email. + $sid = $this->postSubmission($webform_attachment_email); + $sent_email = $this->getLastEmail(); + $this->assertEqual($sent_email['params']['attachments'][0]['filename'], "attachment_token-$attachment_date.xml", "The attachment's file name"); + $this->assertEqual($sent_email['params']['attachments'][0]['filemime'], 'application/xml', "The attachment's file mime type"); + $this->assertEqual($sent_email['params']['attachments'][0]['filecontent'], "<?xml version=\"1.0\" encoding=\"UTF-8\"?> +<asx:abap xmlns:asx=\"http://www.sap.com/abapxml\" version=\"1.0\"> + <asx:values> + <VERSION>1.0</VERSION> + <SENDER>johnsmith@example.com</SENDER> + <WEBFORM_ID>test_attachment_email</WEBFORM_ID> + <SOURCE> + <o2PARAVALU> + <NAME>Lastname</NAME> + <VALUE>Smith</VALUE> + </o2PARAVALU> + <o2PARAVALU> + <NAME>Firstname</NAME> + <VALUE>John</VALUE> + </o2PARAVALU> + <o2PARAVALU> + <NAME>Emailaddress</NAME> + <VALUE>johnsmith@example.com</VALUE> + </o2PARAVALU> + </SOURCE> + </asx:values> +</asx:abap>", "The attachment's file content"); + + // Check access to the attachment. + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/attachment_token/attachment_token-$attachment_date.xml"); + $this->assertResponse(200, 'Access allowed to the attachment'); + + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/attachment_token/any-file-name.text"); + $this->assertResponse(200, 'Access allowed to the attachment with any file name,'); + + $this->drupalGet("/webform/not_a_webform/submissions/$sid/attachment/attachment/any-file-name.text"); + $this->assertResponse(404, 'Page not found to not a webform'); + + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/email/attachment-$attachment_date.xml"); + $this->assertResponse(404, 'Page not found when not an attachment element is specified'); + + /**************************************************************************/ + // Token. + /**************************************************************************/ + + $webform_id = 'test_attachment_token'; + $webform_attachment_token = Webform::load('test_attachment_token'); + + $sid = $this->postSubmissionTest($webform_attachment_token, ['textfield' => 'Some text']); + + // Check that both attachments are displayed on the results page. + $this->drupalGet('/admin/structure/webform/manage/test_attachment_token/results/submissions'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_token/test_token.txt">test_token.txt</a></td>'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_token_download/test_token.txt">Download</a></td>'); + + // Check that only the download attachment is displayed on + // the submission page. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_token/submission/$sid"); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_token/test_token.txt">test_token.txt</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_token_download/test_token.txt">Download</a>'); + + // Check the attachment's content. + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/webform_attachment_token_download/test_token.txt"); + $this->assertRaw('textfield: Some text'); + + /**************************************************************************/ + // Twig. + /**************************************************************************/ + + $webform_id = 'test_attachment_twig'; + $webform_attachment_twig = Webform::load('test_attachment_twig'); + + $sid = $this->postSubmissionTest($webform_attachment_twig, ['textfield' => 'Some text']); + + // Check that both attachments are displayed on the results page. + $this->drupalGet('/admin/structure/webform/manage/test_attachment_twig/results/submissions'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_twig/test_twig.xml">test_twig.xml</a></td>'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_twig_download/test_twig.xml">Download</a></td>'); + + // Check that only the download attachment is displayed on + // the submission page. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_twig/submission/$sid"); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_twig/test_twig.txt">test_twig.xml</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_twig_download/test_twig.xml">Download</a>'); + + // Check the attachment's content. + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/webform_attachment_twig_download/test_twig.xml"); + $this->assertRaw('<?xml version="1.0"?> +<textfield>Some text</textfield>'); + + /**************************************************************************/ + // URL. + /**************************************************************************/ + + $webform_id = 'test_attachment_url'; + $webform_attachment_url = Webform::load('test_attachment_url'); + + $sid = $this->postSubmissionTest($webform_attachment_url); + + // Check that both attachments are displayed on the results page. + $this->drupalGet('/admin/structure/webform/manage/test_attachment_url/results/submissions'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url/MAINTAINERS.txt">MAINTAINERS.txt</a></td>'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_path/durpalicon.png">durpalicon.png</a></td>'); + $this->assertRaw('<td><a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url_download/MAINTAINERS.txt">Download</a></td>'); + + // Check that only the download attachment is displayed on + // the submission page. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_url/submission/$sid"); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url/MAINTAINERS.txt">MAINTAINERS.txt</a>'); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_path/durpalicon.png">durpalicon.png</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url_download/MAINTAINERS.txt">Download</a>'); + + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url/MAINTAINERS.txt">MAINTAINERS.txt</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/webform_attachment_url_download/MAINTAINERS.txt">Download</a>'); + + // Check the attachment's content. + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/webform_attachment_url_download/MAINTAINERS.txt"); + $this->assertRaw('https://www.drupal.org/contribute'); + + /**************************************************************************/ + // Access. + /**************************************************************************/ + + // Switch to anonymous user. + $this->drupalLogout(); + + $webform_id = 'test_attachment_access'; + $webform_attachment_access = Webform::load('test_attachment_access'); + $sid = $this->postSubmission($webform_attachment_access); + $webform_submission = WebformSubmission::load($sid); + + // Check access to anonymous attachment allowed via $element access rules. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_url/submission/$sid"); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/anonymous/anonymous.txt">anonymous.txt</a>'); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/authenticated/authenticated.txt">authenticated.txt</a>'); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/private/private.txt">private.txt</a>'); + + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/anonymous/anonymous.txt"); + $this->assertResponse(200, 'Access allowed to anonymous.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/authenticated/authenticated.txt"); + $this->assertResponse(403, 'Access denied to authenticated.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/private/private.txt"); + $this->assertResponse(403, 'Access denied to private.txt'); + + // Switch to authenticated user and set user as the submission's owner. + $account = $this->createUser(); + $webform_submission->setOwnerId($account->id())->save(); + $this->drupalLogin($account); + + // Check access to authenticated attachment allowed via $element access rules. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_url/submission/$sid"); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/anonymous/anonymous.txt">anonymous.txt</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/authenticated/authenticated.txt">authenticated.txt</a>'); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/private/private.txt">private.txt</a>'); + + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/anonymous/anonymous.txt"); + $this->assertResponse(403, 'Access denied to anonymous.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/authenticated/authenticated.txt"); + $this->assertResponse(200, 'Access allow to authenticated.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/private/private.txt"); + $this->assertResponse(403, 'Access denied to private.txt'); + + // Switch to admin user. + $this->drupalLogin($this->rootUser); + + // Check access to all attachment allowed for admin. + $this->drupalGet("/admin/structure/webform/manage/test_attachment_url/submission/$sid"); + $this->assertNoRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/anonymous/anonymous.txt">anonymous.txt</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/authenticated/authenticated.txt">authenticated.txt</a>'); + $this->assertRaw('<a href="' . $base_url . '/webform/' . $webform_id . '/submissions/' . $sid . '/attachment/private/private.txt">private.txt</a>'); + + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/anonymous/anonymous.txt"); + $this->assertResponse(403, 'Access denied to anonymous.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/authenticated/authenticated.txt"); + $this->assertResponse(200, 'Access allowed to authenticated.txt'); + $this->drupalGet("/webform/$webform_id/submissions/$sid/attachment/private/private.txt"); + $this->assertResponse(200, 'Access allowed to private.txt'); + + /**************************************************************************/ + // Sanitize. + /**************************************************************************/ + + $webform_attachment_santize = Webform::load('test_attachment_sanitize'); + + $sid = $this->postSubmissionTest($webform_attachment_santize, ['textfield' => 'Some text!@#$%^&*)']); + $webform_submission = WebformSubmission::load($sid); + $element = $webform_attachment_santize->getElement('webform_attachment_token'); + $this->assertEqual(WebformAttachmentToken::getFileName($element, $webform_submission), 'some-text.txt'); + } + +} diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_access.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_access.yml new file mode 100644 index 0000000000..dfac1f6884 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_access.yml @@ -0,0 +1,205 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_access +title: 'Test: Webform Attachment Access' +description: 'Test Webform attachment element access.' +category: 'Test: Attachment' +elements: | + anonymous: + '#type': webform_attachment_token + '#title': anonymous + '#access_create_roles': + - anonymous + '#access_update_roles': + - anonymous + '#access_view_roles': + - anonymous + '#filename': anonymous.txt + '#template': 'Anonymous users can access this attachment' + '#display_on': both + authenticated: + '#type': webform_attachment_token + '#title': authenticated + '#access_create_roles': + - authenticated + '#access_update_roles': + - authenticated + '#access_view_roles': + - authenticated + '#filename': authenticated.txt + '#template': 'Authenticated users can access this attachment' + '#display_on': both + private: + '#type': webform_attachment_token + '#title': private + '#filename': private.txt + '#template': 'Admin users can access this attachment' + '#private': true + '#display_on': both + +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_email.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_email.yml new file mode 100644 index 0000000000..a97f9ed2ed --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_email.yml @@ -0,0 +1,259 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_email +title: 'Test: Webform Attachment Email' +description: 'Test Webform attachment element email.' +category: 'Test: Attachment' +elements: | + email: + '#type': email + '#title': email + '#required': true + '#default_value': johnsmith@example.com + '#xml_name': Emailaddress + first_name: + '#type': textfield + '#title': first_name + '#required': true + '#default_value': John + '#xml_name': Firstname + last_name: + '#type': textfield + '#title': last_name + '#required': true + '#default_value': Smith + '#xml_name': Lastname + attachment_token: + '#type': webform_attachment_token + '#title': attachment_token + '#filename': 'attachment_token-[current-date:html_date].xml' + '#trim': trim + '#template': | + <?xml version="1.0" encoding="UTF-8"?> + <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0"> + <asx:values> + <VERSION>1.0</VERSION> + <SENDER>[webform_submission:values:email:xmlencode:clear]</SENDER> + <WEBFORM_ID>[webform:id:xmlencode]</WEBFORM_ID> + <SOURCE> + <o2PARAVALU> + <NAME>Lastname</NAME> + <VALUE>[webform_submission:values:last_name:xmlencode:clear]</VALUE> + </o2PARAVALU> + <o2PARAVALU> + <NAME>Firstname</NAME> + <VALUE>[webform_submission:values:first_name:xmlencode:clear]</VALUE> + </o2PARAVALU> + <o2PARAVALU> + <NAME>Emailaddress</NAME> + <VALUE>[webform_submission:values:email:xmlencode:clear]</VALUE> + </o2PARAVALU> + </SOURCE> + </asx:values> + </asx:abap> + + attachment_twig: + '#type': webform_attachment_twig + '#title': attachment_twig + '#filename': 'attachment_twig-[current-date:html_date].xml' + '#template': "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<asx:abap xmlns:asx=\"http://www.sap.com/abapxml\" version=\"1.0\">\r\n <asx:values>\r\n <VERSION>1.0</VERSION>\r\n <SENDER>{{ webform_id }}</SENDER>\r\n <WEBFORM_ID>{{ data.email }}</WEBFORM_ID>\r\n <SOURCE>\r\n {% for key, value in data %}\r\n <o2PARAVALU>\r\n <NAME>{{ (elements_flattened[key]['#xml_name']) ? elements_flattened[key]['#xml_name'] : key }}</NAME>\r\n <VALUE>{{ value }}</VALUE>\r\n </o2PARAVALU>\r\n {% endfor %} \r\n </SOURCE>\r\n </asx:values>\r\n</asx:abap>" +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + email: + id: email + label: Email + handler_id: email + status: true + conditions: { } + weight: 0 + settings: + states: + - completed + to_mail: _default + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: _default + body: _default + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: true + twig: false + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' + theme_name: '' diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_sanitize.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_sanitize.yml new file mode 100644 index 0000000000..51ab5f7908 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_sanitize.yml @@ -0,0 +1,183 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_sanitize +title: 'Test: Webform Attachment Filename Sanitize' +description: 'Test Webform attachment filename sanitize element.' +category: 'Test: Attachment' +elements: | + textfield: + '#type': textfield + '#title': textfield + '#default_value': 'Some text!@#$%^&*' + webform_attachment_token: + '#type': webform_attachment_token + '#title': webform_attachment_token + '#filename': '[webform_submission:values:textfield:raw].txt' + '#sanitize': true + '#template': | + textfield: [webform_submission:values:textfield] + +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_token.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_token.yml new file mode 100644 index 0000000000..07e149e499 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_token.yml @@ -0,0 +1,191 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_token +title: 'Test: Webform Attachment Token' +description: 'Test Webform attachment token element.' +category: 'Test: Attachment' +elements: | + textfield: + '#type': textfield + '#title': textfield + '#default_value': '{sometext}' + webform_attachment_token: + '#type': webform_attachment_token + '#title': webform_attachment_token + '#filename': test_token.txt + '#template': | + textfield: [webform_submission:values:textfield] + webform_attachment_token_download: + '#type': webform_attachment_token + '#title': webform_attachment_token_download + '#filename': test_token.txt + '#link_title': 'Download' + '#template': | + textfield: [webform_submission:values:textfield] + '#download': true + '#display_on': both + +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_twig.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_twig.yml new file mode 100644 index 0000000000..12fe6da3b2 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_twig.yml @@ -0,0 +1,192 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_twig +title: 'Test: Webform Attachment Twig' +description: 'Test Webform attachment Twig element.' +category: 'Test: Attachment' +elements: | + textfield: + '#type': textfield + '#title': textfield + '#default_value': '{sometext}' + webform_attachment_twig: + '#type': webform_attachment_twig + '#title': webform_attachment_twig + '#filename': test_twig.xml + '#template': | + <?xml version="1.0"?> + <textfield>{{ webform_token('[webform_submission:values:textfield:xmlencode]', webform_submission) }}</textfield> + webform_attachment_twig_download: + '#type': webform_attachment_twig + '#title': webform_attachment_twig_download + '#filename': test_twig.xml + '#link_title': 'Download' + '#template': | + <?xml version="1.0"?> + <textfield>{{ webform_token('[webform_submission:values:textfield:xmlencode]', webform_submission) }}</textfield> + '#download': true + '#display_on': both +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_url.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_url.yml new file mode 100644 index 0000000000..9e8354bca9 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/config/install/webform.webform.test_attachment_url.yml @@ -0,0 +1,193 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_attachment_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_attachment_url +title: 'Test: Webform Attachment URL' +description: 'Test Webform attachment URL element.' +category: 'Test: Attachment' +elements: | + textfield: + '#type': textfield + '#title': textfield + '#default_value': '{sometext}' + webform_attachment_url: + '#type': webform_attachment_url + '#title': webform_attachment_url + '#filename': MAINTAINERS.txt + '#url': '' + webform_attachment_path: + '#type': webform_attachment_url + '#title': webform_attachment_path + '#filename': durpalicon.png + '#url': '' + webform_attachment_url_download: + '#type': webform_attachment_url + '#title': webform_attachment_url_download + '#filename': MAINTAINERS.txt + '#link_title': 'Download' + '#url': '' + '#download': true + '#display_on': both +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.info.yml b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.info.yml new file mode 100644 index 0000000000..be981e55e0 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Attachment Test' +type: module +description: 'Support module for webform attachment element that provides test webforms.' +package: Testing +# core: 8.x +dependencies: + - 'webform_attachment:webform_attachment' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.module b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.module new file mode 100644 index 0000000000..7cfc86f8d7 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/tests/modules/webform_attachment_test/webform_attachment_test.module @@ -0,0 +1,41 @@ +<?php + +/** + * @file + * Support module for webform attachment element that provides test webforms. + */ + +/** + * Implements hook_webform_load(). + */ +function webform_attachment_test_webform_load(array $entities) { + if (!isset($entities['test_attachment_url'])) { + return; + } + + global $base_url; + + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $entities['test_attachment_url']; + $elements = $webform->getElementsDecodedAndFlattened(); + + // Set absolute URL. + $element_keys = ['webform_attachment_url', 'webform_attachment_url_download']; + foreach ($element_keys as $element_key) { + if (isset($elements[$element_key])) { + $element = $elements[$element_key]; + if (empty($element['#url'])) { + $element['#url'] = $base_url . '/core/MAINTAINERS.txt'; + $webform->setElementProperties($element_key, $element); + } + } + } + + // Set root relative URL. + if (isset($elements['webform_attachment_path'])) { + $element = $elements['webform_attachment_path']; + $element['#url'] = base_path() . 'core/misc/druplicon.png'; + $webform->setElementProperties('webform_attachment_path', $element); + } + +} diff --git a/web/modules/webform/modules/webform_attachment/webform_attachment.info.yml b/web/modules/webform/modules/webform_attachment/webform_attachment.info.yml new file mode 100644 index 0000000000..7ffe0bb952 --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/webform_attachment.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Attachment' +type: module +description: 'Provides an element generates or loads a file that can be attached to a submission or email.' +package: 'Webform' +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_attachment/webform_attachment.routing.yml b/web/modules/webform/modules/webform_attachment/webform_attachment.routing.yml new file mode 100644 index 0000000000..46d74b1f1d --- /dev/null +++ b/web/modules/webform/modules/webform_attachment/webform_attachment.routing.yml @@ -0,0 +1,6 @@ +entity.webform.user.submission.attachment: + path: '/webform/{webform}/submissions/{webform_submission}/attachment/{element}/{filename}' + defaults: + _controller: '\Drupal\webform_attachment\Controller\WebformAttachmentController::download' + requirements: + _entity_access: 'webform_submission.view' diff --git a/web/modules/webform/modules/webform_bootstrap/README.md b/web/modules/webform/modules/webform_bootstrap/README.md index 4b89eaf9fe..f183981747 100644 --- a/web/modules/webform/modules/webform_bootstrap/README.md +++ b/web/modules/webform/modules/webform_bootstrap/README.md @@ -28,7 +28,7 @@ alert-dismissible'; **Button classes** -Use for 'Button CSS classes' and 'Confirmation back link CSS classes'. +Use for 'Button CSS classes' and 'Confirmation back link CSS classes'. ``` btn diff --git a/web/modules/webform/modules/webform_bootstrap/css/webform_bootstrap.css b/web/modules/webform/modules/webform_bootstrap/css/webform_bootstrap.css index 505d317d21..49320ce636 100644 --- a/web/modules/webform/modules/webform_bootstrap/css/webform_bootstrap.css +++ b/web/modules/webform/modules/webform_bootstrap/css/webform_bootstrap.css @@ -39,3 +39,19 @@ border: none; margin: 10px 0.5em 10px 0; } + +/** + * Image select. + */ +html.js .form-type-webform-image-select .select-wrapper:after { + display: none; +} + +/** + * Issue #3010798: Responsive confirmation modal. + */ +@media only screen and (max-width: 650px) { + .webform-confirmation-modal { + width: 90% !important; + } +} diff --git a/web/modules/webform/modules/webform_bootstrap/js/webform.element.help.js b/web/modules/webform/modules/webform_bootstrap/js/webform.element.help.js new file mode 100644 index 0000000000..f74e401a37 --- /dev/null +++ b/web/modules/webform/modules/webform_bootstrap/js/webform.element.help.js @@ -0,0 +1,45 @@ +/** + * @file + * JavaScript behaviors for Bootstrap element help text (tooltip). + * + * @see js/webform.element.help.js + */ + +(function ($, Drupal) { + + 'use strict'; + + // @see http://bootstrapdocs.com/v3.0.3/docs/javascript/#tooltips-usage + Drupal.webformBootstrap = Drupal.webformBootstrap || {}; + Drupal.webformBootstrap.elementHelpIcon = Drupal.webformBootstrap.elementHelpIcon || {}; + Drupal.webformBootstrap.elementHelpIcon.options = Drupal.webformBootstrap.elementHelpIcon.options || { + trigger: 'hover focus click', + placement: 'auto right', + delay: 200 + }; + + /** + * Bootstrap element help icon. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformBootstrapElementHelpIcon = { + attach: function (context) { + $(context).find('.webform-element-help').once('webform-element-help').each(function () { + var $link = $(this); + + var options = $.extend({ + title: $link.attr('data-webform-help'), + html: true + }, Drupal.webformBootstrap.elementHelpIcon.options); + + $link.tooltip(options) + .on('click', function (event) { + // Prevent click from toggling <label>s wrapped around help. + event.preventDefault(); + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/modules/webform_bootstrap/js/webform_bootstrap.states.js b/web/modules/webform/modules/webform_bootstrap/js/webform_bootstrap.states.js new file mode 100644 index 0000000000..f916f031df --- /dev/null +++ b/web/modules/webform/modules/webform_bootstrap/js/webform_bootstrap.states.js @@ -0,0 +1,28 @@ +/** + * @file + * JavaScript behaviors for custom webform #states. + */ + +(function ($, Drupal) { + + 'use strict'; + + $(document).on('state:required', function (e) { + if (e.trigger && $(e.target).isWebform()) { + var $target = $(e.target); + + // @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. + // Fix #required for fieldsets. + if ($target.is('.js-form-wrapper.panel')) { + if (e.value) { + $target.find('.panel-heading .panel-title').addClass('js-form-required form-required'); + } + else { + $target.find('.panel-heading .panel-title').removeClass('js-form-required form-required'); + } + } + + } + }); + +})(jQuery, Drupal); diff --git a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/images.html b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/images.html index 2c9629e2ff..19643d757c 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/images.html +++ b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/images.html @@ -11,9 +11,9 @@ <h3>Responsive Images</h3> <h3>Image style</h3> <p> - <img src="http://placehold.it/200x200" alt="..." class="img-rounded"> - <img src="http://placehold.it/200x200" alt="..." class="img-circle"> - <img src="http://placehold.it/200x200" alt="..." class="img-thumbnail"> + <img src="http://placehold.it/200x200" alt="…" class="img-rounded"> + <img src="http://placehold.it/200x200" alt="…" class="img-circle"> + <img src="http://placehold.it/200x200" alt="…" class="img-thumbnail"> </p> <h3>Thumbnails with Captions and Links</h3> diff --git a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/inputs.html b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/inputs.html index c31c592638..de9a49948b 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/inputs.html +++ b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/inputs.html @@ -107,12 +107,12 @@ <h2><a href="http://getbootstrap.com/css/#inputs">Inputs</a></h2> <div class="col-sm-10"> <div class="form-group"> <label class="control-label" for="focusedInput">Focused input</label> - <input class="form-control" id="focusedInput" type="text" value="This is focused..."> + <input class="form-control" id="focusedInput" type="text" value="This is focused…"> </div> <div class="form-group"> <label class="control-label" for="disabledInput">Disabled input</label> - <input class="form-control" id="disabledInput" type="text" placeholder="Disabled input here..." disabled=""> + <input class="form-control" id="disabledInput" type="text" placeholder="Disabled input here…" disabled=""> </div> <div class="form-group has-warning"> diff --git a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/typography.html b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/typography.html index 59688123d0..fd16237ab6 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/typography.html +++ b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/style-guide/typography.html @@ -33,9 +33,9 @@ <h3>Block elements</h3> <footer>footer <cite title="Citation Title">cite</cite></footer> </blockquote> -<pre><b>pre(formatted)</b><p>Sample text here...</p></pre> +<pre><b>pre(formatted)</b><p>Sample text here…</p></pre> -<pre class=".pre-scrollable"><b>pre.pre-scrollable</b><p>Sample text here...</p></pre> +<pre class=".pre-scrollable"><b>pre.pre-scrollable</b><p>Sample text here…</p></pre> <h3>Alignment classes</h3> diff --git a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.info.yml b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.info.yml index 1593010dfc..a11a933c33 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.info.yml +++ b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.info.yml @@ -8,8 +8,8 @@ dependencies: - 'webform:webform' - 'webform:webform_bootstrap' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.libraries.yml b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.libraries.yml index a8c17ee4a4..1822bc4fb7 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.libraries.yml +++ b/web/modules/webform/modules/webform_bootstrap/tests/modules/webform_bootstrap_test_module/webform_bootstrap_test_module.libraries.yml @@ -4,4 +4,4 @@ webform_bootstrap_test_module: theme: css/webform_bootstrap_test_module.css: {} js: - js/webform_bootstrap_test_module.js: {} + js/webform_bootstrap_test_module.js: {} diff --git a/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/src/Plugin/Preprocess/Region.php b/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/src/Plugin/Preprocess/Region.php index 6b48a6c6db..cac9f5330e 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/src/Plugin/Preprocess/Region.php +++ b/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/src/Plugin/Preprocess/Region.php @@ -7,6 +7,7 @@ use Drupal\bootstrap\Plugin\Preprocess\PreprocessInterface; if (class_exists('\Drupal\bootstrap\Plugin\Preprocess\PreprocessBase')) { + /** * Pre-processes variables for the "region" theme hook. * @@ -33,10 +34,14 @@ public function preprocessVariables(Variables $variables) { $variables->addClass($region_wells[$region]); } } - } } else { + + /** + * Empty "region" theme hook. + * + */ class Region {} } diff --git a/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/webform_bootstrap_test_theme.info.yml b/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/webform_bootstrap_test_theme.info.yml index 5f8d75901a..7108ae17be 100644 --- a/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/webform_bootstrap_test_theme.info.yml +++ b/web/modules/webform/modules/webform_bootstrap/tests/themes/webform_bootstrap_test_theme/webform_bootstrap_test_theme.info.yml @@ -22,8 +22,8 @@ regions: libraries: - 'webform_bootstrap_test_theme/global-styling' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.info.yml b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.info.yml index 259024596d..261c7c044d 100644 --- a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.info.yml +++ b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.info.yml @@ -6,8 +6,8 @@ package: Webform dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.libraries.yml b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.libraries.yml index d5ec83d853..3d3e20c893 100644 --- a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.libraries.yml +++ b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.libraries.yml @@ -1,5 +1,9 @@ webform_bootstrap: version: VERSION + js: + js/webform_bootstrap.states.js: {} css: theme: css/webform_bootstrap.css: {} + dependencies: + - webform/webform.states diff --git a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.module b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.module index 01b966cc4a..2df214d210 100644 --- a/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.module +++ b/web/modules/webform/modules/webform_bootstrap/webform_bootstrap.module @@ -7,6 +7,8 @@ use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformElementHelper; /** * Implements hook_page_attachments(). @@ -51,6 +53,18 @@ function webform_bootstrap_webform_element_alter(array &$element, FormStateInter $theme = \Drupal\bootstrap\Bootstrap::getTheme(); $smart_description_enabled = $theme->getSetting('tooltip_enabled') && $theme->getSetting('forms_smart_descriptions'); } + + // We need to set $element['#smart_description'] to false if the element's + // description_display is set to 'before' or 'after' otherwise Bootstrap will + // display as a tooltip regardless of the setting. + if ($smart_description_enabled + && isset($element['#description']) + && isset($element['#description_display']) + && empty($element['#smart_description']) + && ($element['#description_display'] === 'after' || $element['#description_display'] === 'before')) { + $element['#smart_description'] = FALSE; + } + if ($smart_description_enabled && isset($element['#description']) && is_array($element['#description']) && (empty($element['#smart_description']) || $element['#smart_description'] === TRUE)) { $element['#description'] = \Drupal::service('renderer')->renderPlain($element['#description']); } @@ -64,22 +78,17 @@ function webform_bootstrap_webform_element_alter(array &$element, FormStateInter // Tweak element types. if (isset($element['#type'])) { - switch ($element['#type']) { - case 'webform_checkboxes_other': - case 'webform_buttons': - case 'webform_buttons_other': - case 'webform_entity_checkboxes': - case 'webform_entity_radios': - case 'webform_radios_other': - case 'webform_select_other': - case 'webform_term_checkboxes': - case 'webform_toggles': - // Prevent elements that extend radios and checkboxes from being wrapped - // in a fieldset. - // @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter - $element['#bootstrap_panel'] = FALSE; - break; - + $element_info = \Drupal::service('element_info')->getInfo($element['#type']); + if (isset($element_info['#pre_render'])) { + foreach ($element_info['#pre_render'] as $pre_render) { + if (is_array($pre_render) && in_array($pre_render[1], ['preRenderCompositeFormElement', 'preRenderWebformCompositeFormElement'])) { + // Prevent elements that extend radios and checkboxes from being wrapped + // in a fieldset. + // @see \Drupal\bootstrap\Plugin\Alter\ElementInfo::alter + $element['#bootstrap_panel'] = FALSE; + break; + } + } } } } @@ -92,8 +101,17 @@ function webform_bootstrap_js_alter(&$javascript, AttachedAssetsInterface $asset return; } - // Make sure jQuery tooltip is never loaded. - unset($javascript['core/assets/vendor/jquery.ui/ui/tooltip-min.js']); + $path = drupal_get_path('module', 'webform_bootstrap'); + // Make sure jQuery tooltip is never loaded and use custom + // webform.element.help.js. + unset( + $javascript['core/assets/vendor/jquery.ui/ui/tooltip-min.js'], + $javascript['core/assets/vendor/jquery.ui/ui/widgets/tooltip-min.js'] + ); + + if (isset($javascript['modules/sandbox/webform/js/webform.element.help.js'])) { + $javascript['modules/sandbox/webform/js/webform.element.help.js']['data'] = $path . '/js/webform.element.help.js'; + } } /** @@ -138,6 +156,62 @@ function webform_bootstrap_preprocess_input(&$variables) { } } +/** + * Prepares variables for form element templates. + */ +function webform_bootstrap_preprocess_form_element(&$variables) { + if (!_webform_bootstrap_is_active_theme()) { + return; + } + + if (!WebformElementHelper::isWebformElement($variables['element'])) { + return; + } + + // Do not display phone number using inline form. + // @see https://www.drupal.org/project/webform/issues/2910776s + // @see https://www.drupal.org/project/bootstrap/issues/2829634 + if (WebformElementHelper::isType($variables['element'], 'tel') + && isset($variables['attributes']['class'])) { + WebformArrayHelper::removeValue($variables['attributes']['class'], 'form-inline'); + } +} + +/** + * Implements template_preprocess_fieldset(). + */ +function webform_bootstrap_preprocess_fieldset(&$variables) { + if (!_webform_bootstrap_is_active_theme()) { + return; + } + + // Make sure that the core/modules/system/templates/fieldset.html.twig + // template is being used because this the template that we are fixing. + // Caching the info just-in-case there are a lot of fieldsets. + static $is_system_fieldset; + if (!isset($is_system_fieldset)) { + /** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */ + $theme_registry = \Drupal::service('theme.registry')->getRuntime(); + $info = $theme_registry->get('fieldset'); + $is_system_fieldset = ($info['path'] === 'core/modules/system/templates') ? TRUE : FALSE; + } + if (!$is_system_fieldset) { + return; + } + + // Style fieldset error messages to match form element error messages. + // @see form-element.html.twig + if (!empty($variables['errors'])) { + $variables['errors'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['form-item--error-message', 'alert', 'alert-danger', 'alert-sm'], + ], + 'content' => ['#markup' => $variables['errors']], + ]; + } +} + /** * Implements template_preprocess_file_managed_file(). * diff --git a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml index 1f6aa7bdc5..f3c10e9e46 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml @@ -6,8 +6,10 @@ dependencies: - webform_demo_application_evaluation open: null close: null +weight: 0 uid: null template: false +archive: false id: demo_application title: 'Demo: Application' description: 'A demonstration of a very basic application form.' @@ -77,6 +79,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -84,6 +87,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -99,22 +103,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: 'Application: [webform_submission:values:contact:name]' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -125,6 +144,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -143,9 +163,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -199,4 +221,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml index a87d94bccf..08a639909e 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml @@ -6,8 +6,10 @@ dependencies: - webform_demo_application_evaluation open: null close: null +weight: 0 uid: null template: false +archive: false id: demo_application_evaluation title: 'Demo: Application Evaluation' description: 'A demonstration of a very basic application evaluation form. This form is attached to the ''Demo: Application'' using a Webform block.' @@ -33,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -40,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -55,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -81,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: '1' draft_multiple: false draft_auto_save: false @@ -99,9 +119,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: 1 @@ -154,4 +176,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml index a31bf10575..3b098e207b 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml @@ -23,6 +23,7 @@ settings: label_display: '0' webform_id: demo_application_evaluation default_data: '' + redirect: false visibility: webform: id: webform diff --git a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml index b164b44932..4f71a6118d 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml @@ -23,6 +23,7 @@ settings: label_display: '0' webform_id: demo_application_evaluation default_data: '' + redirect: false visibility: webform: id: webform diff --git a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml index f4e28ec059..9ef98a4605 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml @@ -8,8 +8,8 @@ dependencies: - 'webform:webform' - 'webform:webform_node' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/core.entity_view_display.node.webform_demo_event.default.yml b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/core.entity_view_display.node.webform_demo_event.default.yml index 5c3c18e93f..badc93505c 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/core.entity_view_display.node.webform_demo_event.default.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/core.entity_view_display.node.webform_demo_event.default.yml @@ -51,8 +51,13 @@ content: weight: 3 label: hidden settings: - source_entity: true + label: Register + dialog: normal + attributes: + class: + - button + style: 'margin: 1em 0' third_party_settings: { } - type: webform_entity_reference_entity_view + type: webform_entity_reference_link region: content hidden: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/field.field.node.webform_demo_event.webform.yml b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/field.field.node.webform_demo_event.webform.yml index cab185471c..b31b786664 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/field.field.node.webform_demo_event.webform.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/field.field.node.webform_demo_event.webform.yml @@ -19,15 +19,11 @@ default_value: status: open open: '' close: '' - target_uuid: c9a4a1ff-ba66-454d-a572-9c2624b73a67 + target_uuid: '' default_value_callback: '' settings: handler: 'default:webform' handler_settings: target_bundles: null auto_create: false - default_data: '' - status: open - open: '' - close: '' field_type: webform diff --git a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/webform.webform.demo_event_registration.yml b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/webform.webform.demo_event_registration.yml index ecf7ab87b9..78703d3cd2 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/webform.webform.demo_event_registration.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/config/install/webform.webform.demo_event_registration.yml @@ -8,8 +8,10 @@ dependencies: - webform_scheduled_email open: null close: null +weight: 0 uid: null template: false +archive: false id: demo_event_registration title: 'Demo: Event Registration' description: 'A demonstration of an event registration form.' @@ -53,6 +55,7 @@ settings: page: false page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -60,6 +63,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -75,22 +79,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: true + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -101,6 +120,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -119,9 +139,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -166,6 +188,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -177,7 +203,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -186,14 +212,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' @@ -209,23 +237,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: '[webform_submission:node:title:value]: Reminder' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' @@ -242,10 +272,10 @@ handlers: conditions: { } weight: -50 settings: - debug: '' - entity_limit_total: '[webform_submission:node:field_webform_entity_limit_total]' preview_title: '' preview_message: '' confirmation_url: '' confirmation_title: '' confirmation_message: '' + debug: '' + entity_limit_total: '[webform_submission:node:field_webform_entity_limit_total]' diff --git a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.info.yml b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.info.yml index f3c50cc63b..a46aa6c7d9 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.info.yml +++ b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.info.yml @@ -10,8 +10,8 @@ dependencies: - 'webform:webform_node' - 'webform:webform_scheduled_email' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.install b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.install index bee165532a..07c9b9959c 100644 --- a/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.install +++ b/web/modules/webform/modules/webform_demo/webform_demo_event_registration/webform_demo_event_registration.install @@ -2,7 +2,7 @@ /** * @file - * Install, update and uninstall functions for the webform node module. + * Install, update and uninstall functions for the webform demo event registration module. */ use Drupal\Core\Datetime\DrupalDateTime; @@ -37,8 +37,9 @@ function webform_demo_event_registration_install() { * Implements hook_uninstall(). */ function webform_demo_event_registration_uninstall() { - // Delete all webform:demo_application nodes. + // Delete all webform:demo_event_registration nodes. $entity_ids = \Drupal::entityQuery('node') + ->condition('type', 'webform_demo_event') ->condition('webform.target_id', 'demo_event_registration') ->execute(); if ($entity_ids) { diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_form_display.node.webform_demo_region.default.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_form_display.node.webform_demo_region.default.yml new file mode 100644 index 0000000000..9a5682986d --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_form_display.node.webform_demo_region.default.yml @@ -0,0 +1,83 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.webform_demo_region.body + - field.field.node.webform_demo_region.webform + - node.type.webform_demo_region + module: + - path + - text + - webform +id: node.webform_demo_region.default +targetEntityType: node +bundle: webform_demo_region +mode: default +content: + body: + type: text_textarea_with_summary + weight: 2 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + region: content + created: + type: datetime_timestamp + weight: 6 + region: content + settings: { } + third_party_settings: { } + path: + type: path + weight: 9 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 7 + region: content + third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 10 + region: content + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 8 + region: content + third_party_settings: { } + title: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } + webform: + weight: 3 + settings: + default_data: true + third_party_settings: { } + type: webform_entity_reference_select + region: content +hidden: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.default.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.default.yml new file mode 100644 index 0000000000..57fcb3e36f --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.default.yml @@ -0,0 +1,36 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.webform_demo_region.body + - field.field.node.webform_demo_region.webform + - node.type.webform_demo_region + module: + - text + - user + - webform +id: node.webform_demo_region.default +targetEntityType: node +bundle: webform_demo_region +mode: default +content: + body: + label: hidden + type: text_default + weight: 1 + settings: { } + third_party_settings: { } + region: content + links: + weight: 4 + region: content + settings: { } + third_party_settings: { } + webform: + weight: 3 + label: hidden + settings: { } + third_party_settings: { } + type: webform_entity_reference_entity_view + region: content +hidden: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.teaser.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.teaser.yml new file mode 100644 index 0000000000..83f2da8f6e --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/core.entity_view_display.node.webform_demo_region.teaser.yml @@ -0,0 +1,31 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.webform_demo_region.body + - field.field.node.webform_demo_region.webform + - node.type.webform_demo_region + module: + - text + - user +id: node.webform_demo_region.teaser +targetEntityType: node +bundle: webform_demo_region +mode: teaser +content: + body: + label: hidden + type: text_summary_or_trimmed + weight: 1 + settings: + trim_length: 600 + third_party_settings: { } + region: content + links: + weight: 2 + region: content + settings: { } + third_party_settings: { } +hidden: + webform: true diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.body.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.body.yml new file mode 100644 index 0000000000..1735ebfca5 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.body.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.webform_demo_region + module: + - text +id: node.webform_demo_region.body +field_name: body +entity_type: node +bundle: webform_demo_region +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +field_type: text_with_summary diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.webform.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.webform.yml new file mode 100644 index 0000000000..5e2d36d4a3 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/field.field.node.webform_demo_region.webform.yml @@ -0,0 +1,33 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.webform + - node.type.webform_demo_region + module: + - webform +id: node.webform_demo_region.webform +field_name: webform +entity_type: node +bundle: webform_demo_region +label: Webform +description: '' +required: true +translatable: true +default_value: + - default_data: '' + status: open + open: '' + close: '' + target_uuid: '' +default_value_callback: '' +settings: + handler: 'default:webform' + handler_settings: + target_bundles: null + auto_create: false + default_data: '' + status: open + open: '' + close: '' +field_type: webform diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/node.type.webform_demo_region.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/node.type.webform_demo_region.yml new file mode 100644 index 0000000000..1eefd06806 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/node.type.webform_demo_region.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform_demo_region_contact + module: + - menu_ui +third_party_settings: + menu_ui: + available_menus: + - main + parent: 'main:' +name: 'Demo: Region' +type: webform_demo_region +description: '' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: true diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/webform.webform.demo_region_contact.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/webform.webform.demo_region_contact.yml new file mode 100644 index 0000000000..3a15849ef8 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/install/webform.webform.demo_region_contact.yml @@ -0,0 +1,297 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_demo_region_contact +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: demo_region_contact +title: 'Demo: Region Contact' +description: 'A demonstration of a region based contact form.' +category: Demo +elements: | + name: + '#title': 'Your Name' + '#type': textfield + '#required': true + '#default_value': '[current-user:display-name]' + email: + '#title': 'Your Email' + '#type': email + '#required': true + '#default_value': '[current-user:mail]' + subject: + '#title': Subject + '#type': textfield + '#required': true + '#test': 'Testing contact webform from [site:name]' + message: + '#title': Message + '#type': textarea + '#required': true + '#test': 'Please ignore this email.' + actions: + '#type': webform_actions + '#title': 'Submit button(s)' + '#submit__label': 'Send message' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: url_message + confirmation_title: '' + confirmation_message: 'Your message has been sent.' + confirmation_url: '<front>' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + employee_email: + id: email + label: 'Employee: Email' + handler_id: employee_email + status: true + conditions: { } + weight: 0 + settings: + states: + - completed + to_mail: '[webform_access:type:demo_region_employee]' + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: 'Demo: Region Contact: Employee' + body: 'This email is sent to employess.' + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: false + twig: false + theme_name: '' + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' + manager_email: + id: email + label: 'Manager: Email' + handler_id: manager_email + status: true + conditions: { } + weight: 0 + settings: + states: + - completed + to_mail: '[webform_access:type:demo_region_manager]' + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: 'Demo: Region Contact: Manager' + body: 'This email is sent to managers.' + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: false + twig: false + theme_name: '' + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' + everyone_email: + id: email + label: 'Everyone: Email' + handler_id: manager_email + status: true + conditions: { } + weight: 0 + settings: + states: + - completed + to_mail: '[webform_access:type]' + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: 'Demo: Region Contact: Everyone' + body: 'This email is sent to everyone.' + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: false + twig: false + theme_name: '' + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/optional/block.block.bartik_webform_demo_region.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/optional/block.block.bartik_webform_demo_region.yml new file mode 100644 index 0000000000..727b3d5b39 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/config/optional/block.block.bartik_webform_demo_region.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - webform_access + theme: + - bartik + enforced: + module: + - webform_demo_region_contact +id: bartik_webform_demo_region +theme: bartik +region: sidebar_second +weight: 0 +provider: null +plugin: webform_access_group_entity +settings: + id: webform_access_group_entity + label: 'My Regional Webforms' + provider: webform_access + label_display: visible +visibility: { } diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.info.yml b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.info.yml new file mode 100644 index 0000000000..597959f1ad --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.info.yml @@ -0,0 +1,17 @@ +name: 'Webform Demo: Region Contact System' +type: module +description: 'Demonstrate how to use the Webform module to build a region based contact system.' +package: 'Webform Demo' +# core: 8.x +dependencies: + - 'drupal:datetime' + - 'token:token' + - 'webform:webform' + - 'webform:webform_node' + - 'webform:webform_access' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.install b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.install new file mode 100644 index 0000000000..d508c23587 --- /dev/null +++ b/web/modules/webform/modules/webform_demo/webform_demo_region_contact/webform_demo_region_contact.install @@ -0,0 +1,150 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the webform demo region contact module. + */ + +use Drupal\node\Entity\Node; +use Drupal\user\Entity\Role; +use Drupal\user\Entity\User; +use Drupal\webform\WebformInterface; +use Drupal\webform_access\Entity\WebformAccessGroup; +use Drupal\webform_access\Entity\WebformAccessType; + +/** + * Implements hook_install(). + */ +function webform_demo_region_contact_install() { + // User types. + $types = [ + 'manager' => [ + 'label' => t('Manager'), + 'permissions' => [ + 'administer', + ], + ], + 'employee' => [ + 'label' => t('Employee'), + 'permissions' => [ + 'view_any', + 'update_any', + ], + ], + ]; + + // U.S. regions. + $regions = [ + 'us_neast' => [ + 'label' => t('United States: Northeast'), + ], + 'us_midwest' => [ + 'label' => t('United States: Midwest'), + ], + 'us_south' => [ + 'label' => t('United States: South'), + ], + 'us_west' => [ + 'label' => t('United States: West'), + ], + ]; + + // Create role. + $role = Role::create(['id' => 'demo_region', 'label' => 'Demo region']); + $role->grantPermission('access toolbar'); + $role->grantPermission('view the administration theme'); + $role->save(); + + // Create webform access types. + foreach ($types as $type_name => $type) { + $type += [ + 'id' => 'demo_region_' . $type_name, + ]; + WebformAccessType::create($type) + ->save(); + } + + // Create webform access groups, nodes, and users by region. + foreach ($regions as $region_name => $region) { + // Create webform node. + $webform_node = Node::create([ + 'type' => 'webform_demo_region', + 'title' => $region['label'] . ': ' . t('Contact'), + 'status' => 1, + ]); + $webform_node->webform->target_id = 'demo_region_contact'; + $webform_node->webform->status = WebformInterface::STATUS_OPEN; + $webform_node->webform->open = ''; + $webform_node->webform->close = ''; + $webform_node->save(); + + foreach ($types as $type_name => $type) { + $webform_access_group_id = 'demo_region_' . $region_name . '_' . $type_name; + $webform_access_type_id = 'demo_region_' . $type_name; + + // Create region webform access group. + $values = [ + 'label' => $region['label'] . ': ' . $type['label'], + 'id' => $webform_access_group_id, + 'type' => $webform_access_type_id, + 'permissions' => $type['permissions'], + ]; + $webform_access_group = WebformAccessGroup::create($values); + + // Add webform node to webform access group. + $webform_access_group->addEntityId('node', $webform_node->id(), 'webform', 'demo_region_contact'); + + // Create region type user. + $user = User::create([ + 'name' => $webform_access_group_id, + 'password' => $webform_access_group_id, + 'mail' => "$webform_access_group_id@test.com", + 'status' => 1, + ]); + $user->addRole('demo_region'); + $user->save(); + + // Add user to webform access group. + $webform_access_group->addUserId($user->id()); + + // Save webform access group. + $webform_access_group->save(); + } + } +} + +/** + * Implements hook_uninstall(). + */ +function webform_demo_region_contact_uninstall() { + // Delete all 'webform:demo_region_contact' nodes. + $entity_ids = \Drupal::entityQuery('node') + ->condition('type', 'webform_demo_region') + ->condition('webform.target_id', 'demo_region_contact') + ->execute(); + if ($entity_ids) { + /** @var \Drupal\node\Entity\Node[] $nodes */ + $nodes = Node::loadMultiple($entity_ids); + foreach ($nodes as $node) { + $node->delete(); + } + } + + $entity_types = [ + 'user' => 'name', + 'user_role' => 'id', + 'webform_access_group' => 'id', + 'webform_access_type' => 'id', + ]; + foreach ($entity_types as $entity_type => $entity_key) { + $entity_ids = \Drupal::entityQuery($entity_type) + ->condition($entity_key, 'demo_region', 'STARTS_WITH') + ->execute(); + if ($entity_ids) { + $entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($entity_ids); + foreach ($entities as $entity) { + $entity->delete(); + } + } + } +} diff --git a/web/modules/webform/modules/webform_devel/config/install/webform_devel.settings.yml b/web/modules/webform/modules/webform_devel/config/install/webform_devel.settings.yml deleted file mode 100644 index 548d05b7ca..0000000000 --- a/web/modules/webform/modules/webform_devel/config/install/webform_devel.settings.yml +++ /dev/null @@ -1,2 +0,0 @@ -logger: - debug: true diff --git a/web/modules/webform/modules/webform_devel/config/schema/webform_devel.schema.yml b/web/modules/webform/modules/webform_devel/config/schema/webform_devel.schema.yml deleted file mode 100644 index f377029313..0000000000 --- a/web/modules/webform/modules/webform_devel/config/schema/webform_devel.schema.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Schema for the configuration files of the Webform devel module. - -webform_devel.settings: - type: config_object - label: 'Webform devel settings' - mapping: - logger: - type: mapping - label: 'Logging and errors' - mapping: - debug: - type: boolean - label: 'Display debugging notices and errors on screen' diff --git a/web/modules/webform/modules/webform_devel/drush.services.yml b/web/modules/webform/modules/webform_devel/drush.services.yml new file mode 100644 index 0000000000..c4e2c0d789 --- /dev/null +++ b/web/modules/webform/modules/webform_devel/drush.services.yml @@ -0,0 +1,6 @@ +services: + webform_devel.commands: + class: \Drupal\webform_devel\Commands\WebformDevelCommands + arguments: ['@state', '@user.data'] + tags: + - { name: drush.command } diff --git a/web/modules/webform/modules/webform_devel/src/Commands/WebformDevelCommands.php b/web/modules/webform/modules/webform_devel/src/Commands/WebformDevelCommands.php new file mode 100644 index 0000000000..642ce95a54 --- /dev/null +++ b/web/modules/webform/modules/webform_devel/src/Commands/WebformDevelCommands.php @@ -0,0 +1,117 @@ +<?php + +namespace Drupal\webform_devel\Commands; + +use Drupal\Core\Serialization\Yaml; +use Drupal\Core\State\State; +use Drupal\user\UserData; +use Drupal\webform\Utility\WebformYaml; +use Drush\Commands\DrushCommands; +use Drush\Exceptions\UserAbortException; +use Psr\Log\LogLevel; + +/** + * Webform devel commandfile. + */ +class WebformDevelCommands extends DrushCommands { + + /** + * Provides the state system. + * + * @var \Drupal\Core\State\State + */ + protected $state; + + /** + * The user data service. + * + * @var \Drupal\user\UserData + */ + protected $userData; + + /** + * The construct method. + * + * @param \Drupal\Core\State\State $state + * Provides the state system. + * @param \Drupal\user\UserData $user_data + * The user data service. + */ + public function __construct(State $state, UserData $user_data) { + parent::__construct(); + $this->state = $state; + $this->userData = $user_data; + } + + /** + * Executes devel export config. + * + * @command webform:devel:config:update + * @aliases wfdcu + */ + public function drush_webform_devel_config_update() { + module_load_include('inc', 'webform', 'includes/webform.install'); + + $files = file_scan_directory(drupal_get_path('module', 'webform'), '/^webform\.webform\..*\.yml$/'); + $total = 0; + foreach ($files as $filename => $file) { + try { + $original_yaml = file_get_contents($filename); + + $tidied_yaml = $original_yaml; + $data = Yaml::decode($tidied_yaml); + + // Skip translated configu files which don't include a langcode. + // @see tests/modules/webform_test_translation/config/install/language + if (empty($data['langcode'])) { + continue; + } + + $data = _webform_update_webform_setting($data); + $tidied_yaml = WebformYaml::encode($data) . PHP_EOL; + + if ($tidied_yaml != $original_yaml) { + $this->output()->writeln(dt('Updating @file…', ['@file' => $file->filename])); + file_put_contents($file->uri, $tidied_yaml); + $total++; + } + } + catch (\Exception $exception) { + $message = 'Error parsing: ' . $file->filename . PHP_EOL . $exception->getMessage(); + if (strlen($message) > 255) { + $message = substr($message, 0, 255) . '…'; + } + $this->logger()->log($message, LogLevel::ERROR); + $this->output()->writeln($message); + } + } + + if ($total) { + $this->output()->writeln(dt('@total webform.webform.* configuration file(s) updated.', ['@total' => $total])); + } + else { + $this->output()->writeln(dt('No webform.webform.* configuration files updated.')); + } + } + + /** + * Executes webform devel reset. + * + * @command webform:devel:reset + * @aliases wfdr + * + * @see drush_webform_devel_reset() + */ + public function drush_webform_devel_reset() { + if (!$this->io()->confirm(dt("Are you sure you want repair the Webform module's admin settings and webforms?"))) { + throw new UserAbortException(); + } + + $this->output()->writeln(dt('Resetting message closed via State API…')); + $this->state->delete('webform.element.message'); + + $this->output()->writeln(dt('Resetting message closed via User Data…')); + $this->userData->delete('webform', NULL, 'webform.element.message'); + } + +} diff --git a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntitySchemaForm.php b/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntitySchemaForm.php index 2f9a420aab..53b6833d3d 100644 --- a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntitySchemaForm.php +++ b/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntitySchemaForm.php @@ -66,7 +66,7 @@ public function form(array $form, FormStateInterface $form_state) { foreach ($element as $key => $value) { if ($key === 'options') { - $value = implode('; ', array_slice($value, 0, 12)) . (count($value) > 12 ? '; ...' : ''); + $value = implode('; ', array_slice($value, 0, 12)) . (count($value) > 12 ? '; …' : ''); } $rows[$element_key][$key] = ['#markup' => $value]; } diff --git a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelSubmissionApiForm.php b/web/modules/webform/modules/webform_devel/src/Form/WebformDevelSubmissionApiForm.php index fc681fdf9d..c093541ee8 100644 --- a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelSubmissionApiForm.php +++ b/web/modules/webform/modules/webform_devel/src/Form/WebformDevelSubmissionApiForm.php @@ -105,7 +105,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['submission']['message'] = [ '#type' => 'webform_message', - '#message_message' => $this->t("Submitting the below values with trigger the %title webform's ::valdiateForm() and ::submitForm() callbacks.", ['%title' => $webform->label()]), + '#message_message' => $this->t("Submitting the below values will trigger the %title webform's ::validateFormValues() and ::submitFormValues() callbacks.", ['%title' => $webform->label()]), '#message_type' => 'warning', ]; $form['submission']['values'] = [ @@ -185,7 +185,9 @@ public function validateForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { $values = $form_state->getValue('values'); $webform_submission = WebformSubmissionForm::submitFormValues($values); - drupal_set_message($this->t('New submission %title added.', ['%title' => $webform_submission->label()])); + $this->messenger()->addStatus($this->t('New submission %title added.', [ + '%title' => $webform_submission->label(), + ])); } } diff --git a/web/modules/webform/modules/webform_devel/src/Logger/WebformDevelLog.php b/web/modules/webform/modules/webform_devel/src/Logger/WebformDevelLog.php deleted file mode 100644 index d77f0052d5..0000000000 --- a/web/modules/webform/modules/webform_devel/src/Logger/WebformDevelLog.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php - -namespace Drupal\webform_devel\Logger; - -use Drupal\Core\Config\ConfigFactory; -use Drupal\Core\Logger\LogMessageParserInterface; -use Drupal\Core\Logger\RfcLoggerTrait; -use Psr\Log\LoggerInterface; - -/** - * Logs events in the watchdog database table. - */ -class WebformDevelLog implements LoggerInterface { - - use RfcLoggerTrait; - - /** - * The configuration factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - - /** - * The message's placeholders parser. - * - * @var \Drupal\Core\Logger\LogMessageParserInterface - */ - protected $parser; - - /** - * Constructs a SysLog object. - * - * @param \Drupal\Core\Config\ConfigFactory $config_factory - * The configuration factory object. - * @param \Drupal\Core\Logger\LogMessageParserInterface $parser - * The parser to use when extracting message variables. - */ - public function __construct(ConfigFactory $config_factory, LogMessageParserInterface $parser) { - $this->configFactory = $config_factory; - - $this->config = $config_factory->get('webform_devel.settings'); - $this->parser = $parser; - } - - /** - * {@inheritdoc} - */ - public function log($level, $message, array $context = []) { - // Never display log messages on the command line. - if (php_sapi_name() == 'cli') { - return; - } - - // Never display log messages if the 'system.logging' 'error_level' - // is set to 'hide'. - if ($this->configFactory->get('system.logging')->get('error_level') === 'hide') { - return; - } - - $debug = $this->configFactory->get('webform_devel.settings')->get('logger.debug') ?: 0; - if ($debug == 1 && in_array($context['channel'], ['theme', 'php'])) { - // Populate the message placeholders and then replace them in the message. - $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context); - $message = empty($message_placeholders) ? $message : strtr($message, $message_placeholders); - $build = ['#markup' => $message]; - // IMPORTANT: Do not inject the renderer into WebformDevelLog because - // it will cause... - // "LogicException: The database connection is not serializable." errors - // for all Ajax callbacks. - // @see \Drupal\Core\Render\Renderer - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), ($level <= 3) ? 'error' : 'warning'); - } - } - -} diff --git a/web/modules/webform/modules/webform_devel/src/WebformDevelSchema.php b/web/modules/webform/modules/webform_devel/src/WebformDevelSchema.php index 0e36ea72fb..c19dca3a10 100644 --- a/web/modules/webform/modules/webform_devel/src/WebformDevelSchema.php +++ b/web/modules/webform/modules/webform_devel/src/WebformDevelSchema.php @@ -2,7 +2,6 @@ namespace Drupal\webform_devel; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Render\Element\Email as EmailElement; @@ -225,7 +224,7 @@ protected function getOptionsMaxlength(array $element) { $options = OptGroup::flattenOptions($element['#options']); $maxlength = 0; foreach ($options as $option_value => $option_text) { - $maxlength = max(Unicode::strlen($option_value), $maxlength); + $maxlength = max(mb_strlen($option_value), $maxlength); } // Check element w/ other value maxlength. diff --git a/web/modules/webform/modules/webform_devel/webform_devel.drush.inc b/web/modules/webform/modules/webform_devel/webform_devel.drush.inc index 3884e3159c..f95f2cf844 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.drush.inc +++ b/web/modules/webform/modules/webform_devel/webform_devel.drush.inc @@ -46,15 +46,17 @@ function webform_devel_drush_help($section) { /** * Implements drush_hook_COMMAND(). + * + * @see \Drupal\webform_devel\Commands\WebformDevelCommands::drush_webform_devel_reset() */ function drush_webform_devel_reset() { if (!drush_confirm(dt("Are you sure you want reset the Webform module's user data and saved state?"))) { return drush_user_abort(); } - drush_print(dt('Resetting message closed via State API...')); + drush_print(dt('Resetting message closed via State API…')); \Drupal::state()->delete('webform.element.message'); - drush_print(dt('Resetting message closed via User Data...')); + drush_print(dt('Resetting message closed via User Data…')); \Drupal::service('user.data')->delete('webform', NULL, 'webform.element.message'); } diff --git a/web/modules/webform/modules/webform_devel/webform_devel.info.yml b/web/modules/webform/modules/webform_devel/webform_devel.info.yml index b55e7658ba..be11b487b8 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.info.yml +++ b/web/modules/webform/modules/webform_devel/webform_devel.info.yml @@ -7,8 +7,8 @@ dependencies: - 'devel:devel' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_devel/webform_devel.links.task.yml b/web/modules/webform/modules/webform_devel/webform_devel.links.task.yml index 73c4db7dcd..947e3f931e 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.links.task.yml +++ b/web/modules/webform/modules/webform_devel/webform_devel.links.task.yml @@ -1,9 +1,3 @@ -entity.webform.export_form: - title: 'Export' - route_name: entity.webform.export_form - parent_id: devel.entities:webform.devel_tab - weight: 100 - entity.webform.schema_form: title: 'Webform' route_name: entity.webform.schema_form diff --git a/web/modules/webform/modules/webform_devel/webform_devel.module b/web/modules/webform/modules/webform_devel/webform_devel.module index a349e9ac8b..3d870e15eb 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.module +++ b/web/modules/webform/modules/webform_devel/webform_devel.module @@ -17,7 +17,6 @@ function webform_devel_entity_type_alter(array &$entity_types) { /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */ $entity_type = $entity_types['webform']; $handlers = $entity_type->getHandlerClasses(); - $handlers['form']['export'] = 'Drupal\webform_devel\Form\WebformDevelEntityExportForm'; $handlers['form']['schema'] = 'Drupal\webform_devel\Form\WebformDevelEntitySchemaForm'; $entity_type->setHandlerClass('form', $handlers['form']); } @@ -33,42 +32,6 @@ function webform_devel_form_config_single_export_form_alter(&$form, FormStateInt $form['config_name']['#ajax']['callback'] = '_webform_form_config_single_export_form_update_export'; } -/** - * Implements hook_form_FORM_ID_alter(). - */ -function webform_devel_form_webform_admin_config_advanced_form_alter(&$form, FormStateInterface $form_state) { - $form['logger'] = [ - '#type' => 'details', - '#title' => t('Logging and errors'), - '#open' => TRUE, - '#tree' => TRUE, - ]; - $form['logger']['debug'] = [ - '#type' => 'radios', - '#title' => t("Display debugging notices and errors on screen"), - '#default_value' => \Drupal::config('webform_devel.settings')->get('logger.debug') ? '1' : '0', - '#description' => t("Checking 'Yes' will display PHP and theme notices onscreen."), - '#options' => [ - '1' => t('Yes'), - '0' => t('No'), - ], - '#options_display' => 'side_by_side', - '#required' => TRUE, - ]; - $form['#submit'][] = '_webform_devel_form_webform_admin_config_advanced_form_submit'; -} - -/** - * Submit callback webform devel module settings. - */ -function _webform_devel_form_webform_admin_config_advanced_form_submit(&$form, FormStateInterface $form_state) { - $values = $form_state->getValues(); - $values['logger']['debug'] = (boolean) $values['logger']['debug']; - \Drupal::configFactory()->getEditable('webform_devel.settings') - ->set('logger', $values['logger']) - ->save(); -} - /** * Handles switching the export textarea and tidies exported MSK configuration. * @@ -88,7 +51,7 @@ function _webform_form_config_single_export_form_update_export($form, FormStateI // Read the raw data for this config name, encode it, and display it. $value = Yaml::encode(\Drupal::service('config.storage')->read($name)); - // Tidy all only MSK exported configuration...for now. + // Tidy all only MSK exported configuration…for now. if (strpos($name, 'webform') === 0) { $value = WebformYaml::tidy($value); } diff --git a/web/modules/webform/modules/webform_devel/webform_devel.routing.yml b/web/modules/webform/modules/webform_devel/webform_devel.routing.yml index a8735f3bc4..a2395df807 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.routing.yml +++ b/web/modules/webform/modules/webform_devel/webform_devel.routing.yml @@ -1,11 +1,3 @@ -entity.webform.export_form: - path: '/admin/structure/webform/manage/{webform}/export' - defaults: - _entity_form: 'webform.export' - _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' - requirements: - _permission: 'access devel information' - entity.webform.schema_form: path: '/admin/structure/webform/manage/{webform}/schema' defaults: diff --git a/web/modules/webform/modules/webform_devel/webform_devel.services.yml b/web/modules/webform/modules/webform_devel/webform_devel.services.yml index a0f323ac76..6e8dd87966 100644 --- a/web/modules/webform/modules/webform_devel/webform_devel.services.yml +++ b/web/modules/webform/modules/webform_devel/webform_devel.services.yml @@ -2,9 +2,3 @@ services: webform_devel.schema: class: Drupal\webform_devel\WebformDevelSchema arguments: ['@plugin.manager.element_info', '@plugin.manager.webform.element'] - - logger.webform_devel_log: - class: Drupal\webform_devel\Logger\WebformDevelLog - arguments: ['@config.factory', '@logger.log_message_parser'] - tags: - - { name: logger } diff --git a/web/modules/webform/modules/webform_editorial/webform_editorial.info.yml b/web/modules/webform/modules/webform_editorial/webform_editorial.info.yml index c7901e0cd7..d2cbaf3a16 100644 --- a/web/modules/webform/modules/webform_editorial/webform_editorial.info.yml +++ b/web/modules/webform/modules/webform_editorial/webform_editorial.info.yml @@ -7,8 +7,8 @@ hidden: true dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_editorial/webform_editorial.links.menu.yml b/web/modules/webform/modules/webform_editorial/webform_editorial.links.menu.yml index 72e3acdce3..7bb2a26bed 100644 --- a/web/modules/webform/modules/webform_editorial/webform_editorial.links.menu.yml +++ b/web/modules/webform/modules/webform_editorial/webform_editorial.links.menu.yml @@ -45,4 +45,3 @@ webform_editorial.drush: parent: webform_editorial.index route_name: webform_editorial.drush weight: 40 - diff --git a/web/modules/webform/modules/webform_example_composite/config/install/webform.webform.webform_example_composite.yml b/web/modules/webform/modules/webform_example_composite/config/install/webform.webform.webform_example_composite.yml index 2035bdb4ec..2682fe3235 100644 --- a/web/modules/webform/modules/webform_example_composite/config/install/webform.webform.webform_example_composite.yml +++ b/web/modules/webform/modules/webform_example_composite/config/install/webform.webform.webform_example_composite.yml @@ -6,8 +6,10 @@ dependencies: - webform_example_composite open: null close: null +weight: 0 uid: null template: false +archive: false id: webform_example_composite title: 'Example: Webform Composite' description: 'An example of a custom Webform composite.' @@ -29,6 +31,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -36,6 +39,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -51,22 +55,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -77,6 +96,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -95,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -150,4 +172,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_example_composite/src/Tests/WebformExampleCompositeTest.php b/web/modules/webform/modules/webform_example_composite/src/Tests/WebformExampleCompositeTest.php index 7ea2b835f4..0504d9737c 100644 --- a/web/modules/webform/modules/webform_example_composite/src/Tests/WebformExampleCompositeTest.php +++ b/web/modules/webform/modules/webform_example_composite/src/Tests/WebformExampleCompositeTest.php @@ -27,18 +27,18 @@ public function testWebformExampleComposite() { $webform = Webform::load('webform_example_composite'); // Check form element rendering. - $this->drupalGet('webform/webform_example_composite'); + $this->drupalGet('/webform/webform_example_composite'); // NOTE: // This is a very lazy but easy way to check that the element is rendering // as expected. $this->assertRaw('<label for="edit-webform-example-composite-first-name">First name</label>'); - $this->assertRaw('<input data-webform-composite-id="webform-example-composite--14--first_name" data-drupal-selector="edit-webform-example-composite-first-name" type="text" id="edit-webform-example-composite-first-name" name="webform_example_composite[first_name]" value="" size="60" maxlength="255" class="form-text" />'); + $this->assertFieldById('edit-webform-example-composite-first-name'); $this->assertRaw('<label for="edit-webform-example-composite-last-name">Last name</label>'); - $this->assertRaw('<input data-webform-composite-id="webform-example-composite--14--last_name" data-drupal-selector="edit-webform-example-composite-last-name" type="text" id="edit-webform-example-composite-last-name" name="webform_example_composite[last_name]" value="" size="60" maxlength="255" class="form-text" />'); + $this->assertFieldById('edit-webform-example-composite-last-name'); $this->assertRaw('<label for="edit-webform-example-composite-date-of-birth">Date of birth</label>'); - $this->assertRaw('<input type="date" data-drupal-selector="edit-webform-example-composite-date-of-birth" data-drupal-date-format="Y-m-d" id="edit-webform-example-composite-date-of-birth" name="webform_example_composite[date_of_birth]" value="" class="form-date" data-drupal-states="{"enabled":{"[data-webform-composite-id=\u0022webform-example-composite--14--first_name\u0022]":{"filled":true},"[data-webform-composite-id=\u0022webform-example-composite--14--last_name\u0022]":{"filled":true}}}" />'); + $this->assertFieldById('edit-webform-example-composite-date-of-birth'); $this->assertRaw('<label for="edit-webform-example-composite-gender">Gender</label>'); - $this->assertRaw('<select data-drupal-selector="edit-webform-example-composite-gender" id="edit-webform-example-composite-gender" name="webform_example_composite[gender]" class="form-select" data-drupal-states="{"enabled":{"[data-webform-composite-id=\u0022webform-example-composite--14--first_name\u0022]":{"filled":true},"[data-webform-composite-id=\u0022webform-example-composite--14--last_name\u0022]":{"filled":true}}}"><option value="" selected="selected">- None -</option><option value="Male">Male</option><option value="Female">Female</option><option value="Transgender">Transgender</option></select>'); + $this->assertFieldById('edit-webform-example-composite-gender'); // Check webform element submission. $edit = [ @@ -61,10 +61,10 @@ public function testWebformExampleComposite() { ]); $this->assertEqual($webform_submission->getElementData('webform_example_composite_multiple'), [ [ - 'first_name' => 'Jane', - 'last_name' => 'Doe', - 'gender' => 'Female', - 'date_of_birth' => '1920-12-01', + 'first_name' => 'Jane', + 'last_name' => 'Doe', + 'gender' => 'Female', + 'date_of_birth' => '1920-12-01', ], ]); } diff --git a/web/modules/webform/modules/webform_example_composite/webform_example_composite.info.yml b/web/modules/webform/modules/webform_example_composite/webform_example_composite.info.yml index f316c405a8..01aac62753 100644 --- a/web/modules/webform/modules/webform_example_composite/webform_example_composite.info.yml +++ b/web/modules/webform/modules/webform_example_composite/webform_example_composite.info.yml @@ -6,8 +6,8 @@ package: 'Webform example' dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_example_element/config/install/webform.webform.webform_example_element.yml b/web/modules/webform/modules/webform_example_element/config/install/webform.webform.webform_example_element.yml index a54ce36e27..0ef4727063 100644 --- a/web/modules/webform/modules/webform_example_element/config/install/webform.webform.webform_example_element.yml +++ b/web/modules/webform/modules/webform_example_element/config/install/webform.webform.webform_example_element.yml @@ -6,8 +6,10 @@ dependencies: - webform_example_element open: null close: null +weight: 0 uid: null template: false +archive: false id: webform_example_element title: 'Example: Webform Element' description: 'An example of a custom Webform element.' @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,6 +38,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_example_element/src/Plugin/WebformElement/WebformExampleElement.php b/web/modules/webform/modules/webform_example_element/src/Plugin/WebformElement/WebformExampleElement.php index 81c461bd97..0690f0dddd 100644 --- a/web/modules/webform/modules/webform_example_element/src/Plugin/WebformElement/WebformExampleElement.php +++ b/web/modules/webform/modules/webform_example_element/src/Plugin/WebformElement/WebformExampleElement.php @@ -54,7 +54,6 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // @see \Drupal\webform_example_element\Element\WebformExampleElement::processWebformElementExample } - /** * {@inheritdoc} */ diff --git a/web/modules/webform/modules/webform_example_element/src/Tests/WebformExampleElementTest.php b/web/modules/webform/modules/webform_example_element/src/Tests/WebformExampleElementTest.php index d8becefcbe..e2c23a7112 100644 --- a/web/modules/webform/modules/webform_example_element/src/Tests/WebformExampleElementTest.php +++ b/web/modules/webform/modules/webform_example_element/src/Tests/WebformExampleElementTest.php @@ -27,7 +27,7 @@ public function testWebformExampleElement() { $webform = Webform::load('webform_example_element'); // Check form element rendering. - $this->drupalGet('webform/webform_example_element'); + $this->drupalGet('/webform/webform_example_element'); // NOTE: // This is a very lazy but easy way to check that the element is rendering // as expected. diff --git a/web/modules/webform/modules/webform_example_element/webform_example_element.info.yml b/web/modules/webform/modules/webform_example_element/webform_example_element.info.yml index b655916a4c..ca079fab93 100644 --- a/web/modules/webform/modules/webform_example_element/webform_example_element.info.yml +++ b/web/modules/webform/modules/webform_example_element/webform_example_element.info.yml @@ -6,8 +6,8 @@ package: 'Webform example' dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_example_handler/config/install/webform.webform.webform_example_handler.yml b/web/modules/webform/modules/webform_example_handler/config/install/webform.webform.webform_example_handler.yml new file mode 100644 index 0000000000..15542ccd94 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/config/install/webform.webform.webform_example_handler.yml @@ -0,0 +1,188 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_example_handler + module: + - webform_example_handler +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: webform_example_handler +title: 'Example: Webform Handler' +description: 'An example of a custom Webform handler.' +category: Example +elements: | + value: + '#type': textfield + '#title': Value + '#required': true + '#description': 'Enter a value to displayed in a custom message.' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + example: + id: example + label: Example + handler_id: example + status: true + conditions: { } + weight: 0 + settings: + message: 'You entered: <code>[webform_submission:values:value]</code>' + debug: false diff --git a/web/modules/webform/modules/webform_example_handler/config/schema/webform_example_handler.schema.yml b/web/modules/webform/modules/webform_example_handler/config/schema/webform_example_handler.schema.yml new file mode 100644 index 0000000000..4f19f662c2 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/config/schema/webform_example_handler.schema.yml @@ -0,0 +1,10 @@ +webform.handler.example: + type: mapping + label: Example + mapping: + message: + label: Message + type: string + debug: + type: boolean + label: 'Enable debugging' diff --git a/web/modules/webform/modules/webform_example_handler/src/Plugin/WebformHandler/ExampleWebformHandler.php b/web/modules/webform/modules/webform_example_handler/src/Plugin/WebformHandler/ExampleWebformHandler.php new file mode 100644 index 0000000000..3a2acc11b8 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/src/Plugin/WebformHandler/ExampleWebformHandler.php @@ -0,0 +1,281 @@ +<?php + +namespace Drupal\webform_example_handler\Plugin\WebformHandler; + +use Drupal\Component\Utility\Xss; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Render\Markup; +use Drupal\webform\Plugin\WebformHandlerBase; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionConditionsValidatorInterface; +use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform\WebformTokenManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Webform example handler. + * + * @WebformHandler( + * id = "example", + * label = @Translation("Example"), + * category = @Translation("Example"), + * description = @Translation("Example of a webform submission handler."), + * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_SINGLE, + * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_IGNORED, + * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_REQUIRED, + * ) + */ +class ExampleWebformHandler extends WebformHandlerBase { + + /** + * The token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerChannelFactoryInterface $logger_factory, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, WebformSubmissionConditionsValidatorInterface $conditions_validator, WebformTokenManagerInterface $token_manager) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $logger_factory, $config_factory, $entity_type_manager, $conditions_validator); + $this->tokenManager = $token_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('logger.factory'), + $container->get('config.factory'), + $container->get('entity_type.manager'), + $container->get('webform_submission.conditions_validator'), + $container->get('webform.token_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'message' => 'This is a custom message.', + 'debug' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + // Message. + $form['message'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Message settings'), + ]; + $form['message']['message'] = [ + '#type' => 'textfield', + '#title' => $this->t('Message to be displayed when form is completed'), + '#default_value' => $this->configuration['message'], + '#required' => TRUE, + ]; + + // Development. + $form['development'] = [ + '#type' => 'details', + '#title' => $this->t('Development settings'), + ]; + $form['development']['debug'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable debugging'), + '#description' => $this->t('If checked, every handler method invoked will be displayed onscreen to all users.'), + '#return_value' => TRUE, + '#default_value' => $this->configuration['debug'], + ]; + + return $this->setSettingsParents($form); + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $this->configuration['message'] = $form_state->getValue('message'); + $this->configuration['debug'] = (bool) $form_state->getValue('debug'); + } + + /** + * {@inheritdoc} + */ + public function alterElements(array &$elements, WebformInterface $webform) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function overrideSettings(array &$settings, WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function alterForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + if ($value = $form_state->getValue('element')) { + $form_state->setErrorByName('element', $this->t('The element must be empty. You entered %value.', ['%value' => $value])); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function confirmForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + $message = $this->configuration['message']; + $message = $this->replaceTokens($message, $this->getWebformSubmission()); + $this->messenger()->addStatus(Markup::create(Xss::filter($message)), FALSE); + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function preCreate(array $values) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function postCreate(WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function postLoad(WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function preDelete(WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function postDelete(WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function preSave(WebformSubmissionInterface $webform_submission) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + $this->debug(__FUNCTION__, $update ? 'update' : 'insert'); + } + + /** + * {@inheritdoc} + */ + public function preprocessConfirmation(array &$variables) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function createHandler() { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function updateHandler() { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function deleteHandler() { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function createElement($key, array $element) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function updateElement($key, array $element, array $original_element) { + $this->debug(__FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function deleteElement($key, array $element) { + $this->debug(__FUNCTION__); + } + + /** + * Display the invoked plugin method to end user. + * + * @param string $method_name + * The invoked method name. + * @param string $context1 + * Additional parameter passed to the invoked method name. + */ + protected function debug($method_name, $context1 = NULL) { + if (!empty($this->configuration['debug'])) { + $t_args = [ + '@id' => $this->getHandlerId(), + '@class_name' => get_class($this), + '@method_name' => $method_name, + '@context1' => $context1, + ]; + $this->messenger()->addWarning($this->t('Invoked @id: @class_name:@method_name @context1', $t_args), TRUE); + } + } + +} diff --git a/web/modules/webform/modules/webform_example_handler/templates/webform-handler-example-summary.html.twig b/web/modules/webform/modules/webform_example_handler/templates/webform-handler-example-summary.html.twig new file mode 100644 index 0000000000..aada6293d2 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/templates/webform-handler-example-summary.html.twig @@ -0,0 +1,20 @@ +{# +/** + * @file + * Default theme implementation for a summary of an example webform handler. + * + * Available variables: + * - settings: The current configuration for this email handler: + * - message: The message. + * - debug: Debugging flag. + * - handler: The handler information, including: + * - id: The handler plugin id. + * - handler_id: The handler id. + * - label: The handler label. + * - description: The handler description. + * + * @ingroup themeable + */ +#} +{% if settings.debug %}<b class="color-error">{{ 'Debugging is enabled'|t }}</b><br />{% endif %} +<b>Message:</b> {{ settings.message }} diff --git a/web/modules/webform/modules/webform_example_handler/webform_example_handler.info.yml b/web/modules/webform/modules/webform_example_handler/webform_example_handler.info.yml new file mode 100644 index 0000000000..00d7521857 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/webform_example_handler.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Handler Example' +type: module +description: 'Provides an example of a webform handler.' +package: 'Webform example' +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_example_handler/webform_example_handler.module b/web/modules/webform/modules/webform_example_handler/webform_example_handler.module new file mode 100644 index 0000000000..5da7371c88 --- /dev/null +++ b/web/modules/webform/modules/webform_example_handler/webform_example_handler.module @@ -0,0 +1,17 @@ +<?php + +/** + * @file + * Provides an example of a webform handler. + */ + +/** + * Implements hook_theme(). + */ +function webform_example_handler_theme() { + return [ + 'webform_handler_example_summary' => [ + 'variables' => ['settings' => NULL, 'handler' => []], + ], + ]; +} diff --git a/web/modules/webform/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml b/web/modules/webform/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml index 53727b5cef..af74c02866 100644 --- a/web/modules/webform/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml +++ b/web/modules/webform/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml @@ -6,8 +6,10 @@ dependencies: - webform_example_remote_post open: null close: null +weight: 0 uid: null template: false +archive: false id: example_remote_post title: 'Example: Remote post' description: 'An example of a webform submission posted to a remote server.' @@ -45,6 +47,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -52,6 +55,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -67,22 +71,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -93,6 +112,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -102,6 +122,7 @@ settings: confirmation_title: '' confirmation_message: | <p>Your confirmation number is [webform_submission:values:confirmation_number].</p> + confirmation_url: '' confirmation_attributes: { } confirmation_back: true @@ -112,9 +133,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -167,6 +190,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: remote_post: id: remote_post @@ -200,20 +227,24 @@ handlers: confirmation_number: confirmation_number custom_data: | custom_all: true + custom_options: '' debug: true - completed_url: /webform_example_remote_post/completed + completed_url: '[site:url]webform_example_remote_post/completed' completed_custom_data: | custom_completed: true - updated_url: /webform_example_remote_post/updated + + updated_url: '[site:url]webform_example_remote_post/updated' updated_custom_data: | custom_updated: true - deleted_url: /webform_example_remote_post/deleted + + deleted_url: '[site:url]webform_example_remote_post/deleted' deleted_custom_data: | custom_deleted: true + draft_url: '' draft_custom_data: '' converted_url: '' converted_custom_data: '' message: '' - messages: { } + messages: { } diff --git a/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.info.yml b/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.info.yml index 9f7717c6a0..444b354aaf 100644 --- a/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.info.yml +++ b/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.info.yml @@ -4,10 +4,11 @@ description: 'Provides an example of a webform submission posted to a remote ser package: 'Webform example' # core: 8.x dependencies: + - 'token:token' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.module b/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.module deleted file mode 100644 index fd013c46d1..0000000000 --- a/web/modules/webform/modules/webform_example_remote_post/webform_example_remote_post.module +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -/** - * @file - * Provides an example of a webform submission posted to a remote server. - */ - -use Drupal\Core\Url; - -/** - * Implements hook_webform_load(). - * - * Using hook_webform_load() instead of hook_install() to make sure the - * remote post URLs are set to the correct base URL and path. - */ -function webform_example_remote_post_webform_load(array $entities) { - if (isset($entities['example_remote_post']) && PHP_SAPI !== 'cli' && !in_array(\Drupal::routeMatch()->getRouteName(), ['system.modules_list', 'system.modules_list_confirm'])) { - // Reset remote post URL to the current base URL and base path. - /** @var \Drupal\webform\WebformInterface $webform */ - $webform = $entities['example_remote_post']; - /** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $handler */ - $handler = $webform->getHandler('remote_post'); - - $configuration = $handler->getConfiguration(); - $states = ['completed', 'updated', 'deleted']; - foreach ($states as $state) { - // The 'webform_example_remote_post.remote_post' will not be available - // during installation so wrap Url::fromRoute() in try/catch. - try { - if ($configuration['settings'][$state . '_url'] === "/webform_example_remote_post/$state") { - $configuration['settings'][$state . '_url'] = Url::fromRoute('webform_example_remote_post.remote_post', ['type' => $state], ['absolute' => TRUE])->toString(); - } - } - catch (\Exception $exception) { - return; - } - } - $handler->setConfiguration($configuration); - } -} diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements.yml index 3ea265c845..e80c61c848 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements.yml @@ -6,13 +6,64 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_computed_elements title: 'Example: Computed' description: 'Examples of a computed elements.' category: Example elements: | + calculation: + '#title': Calculation + '#type': webform_wizard_page + '#open': true + calculation_section: + '#type': webform_section + '#title': 'Basic adding calculation using a ''computed twig'' element' + container: + '#type': container + '#attributes': + class: + - 'container-inline clearfix' + a: + '#type': number + '#title': a + '#title_display': invisible + '#placeholder': a + markup: + '#type': webform_markup + '#markup': ' + ' + b: + '#type': number + '#title': b + '#title_display': invisible + '#placeholder': b + markup_01: + '#type': webform_markup + '#markup': ' = ' + c: + '#type': webform_computed_twig + '#title': c + '#title_display': invisible + '#ajax': true + '#template': '{% if data.a|length and data.b|length %}{{ data.a + data.b }}{% else %}c{% endif %}' + message_warning: + '#type': webform_message + '#states': + visible: + ':input[name="c"]': + value: c + '#message_type': warning + '#message_message': 'Please enter <strong>a</strong> and <strong>b</strong> to perform a basic adding calculation.' + message_status: + '#type': webform_message + '#message_message': 'Thank you for entering <strong>a</strong> and <strong>b</strong> to perform basic adding calculation.' + '#states': + visible: + ':input[name="c"]': + '!value': c information: '#title': 'Your Information' '#type': webform_wizard_page @@ -36,9 +87,9 @@ elements: | '#open': true computed: '#type': webform_computed_twig - '#title': 'Computed' + '#title': Computed '#title_display': hidden - '#value': | + '#template': | {% set attributes = create_attribute() %} <h2{{ attributes.setAttribute('id', 'custom').setAttribute('style', 'color:' ~ data.color) }}> Hello {{ data.first_name }} {{ data.last_name }}!!! @@ -52,6 +103,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -59,6 +111,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -74,22 +127,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -100,6 +168,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -118,9 +187,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -173,4 +244,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements_ajax.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements_ajax.yml new file mode 100644 index 0000000000..4daba14b22 --- /dev/null +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_computed_elements_ajax.yml @@ -0,0 +1,269 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_computed_elements_ajax +title: 'Example: Computed Ajax' +description: 'Example of a computed element using Ajax.' +category: Example +elements: | + message_warning: + '#type': webform_message + '#states': + visible: + ':input[name="c"]': + value: c + '#message_type': warning + '#message_message': 'Please enter <strong>a</strong> and <strong>b</strong> to perform a basic adding calculation.' + message_status: + '#type': webform_message + '#message_message': 'Thank you for entering <strong>a</strong> and <strong>b</strong> to perform a basic adding calculation.' + '#states': + visible: + ':input[name="c"]': + '!value': c + calculation: + '#type': fieldset + '#title': 'Basic adding calculation' + container: + '#type': container + '#attributes': + class: + - container-inline + - calculation + a: + '#type': number + '#title': a + '#title_display': invisible + '#placeholder': a + '#required': true + markup_add: + '#type': webform_markup + '#markup': ' + ' + b: + '#type': number + '#title': b + '#title_display': invisible + '#placeholder': b + '#required': true + markup_equals: + '#type': webform_markup + '#markup': ' = ' + c: + '#type': webform_computed_twig + '#title': c + '#title_display': invisible + '#ajax': true + '#template': '{% if data.a|length and data.b|length %}{{ data.a + data.b }}{% else %}c{% endif %}' + user_information: + '#type': fieldset + '#title': 'User information' + user: + '#type': webform_entity_select + '#title': User + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: false + filter: + type: _none + user_ud: + '#type': webform_computed_token + '#title': 'User: ID' + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:user:entity:uid:clear]' + user_display_name: + '#type': webform_computed_token + '#title': 'User: Display name' + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:user:entity:display-name:clear]' + user_mail: + '#type': webform_computed_token + '#title': 'User: Email' + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:user:entity:mail:clear]' + user_url: + '#type': webform_computed_token + '#title': 'User: URL' + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:user:entity:url:clear]' + user_created: + '#type': webform_computed_token + '#title': 'User: Created' + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:user:entity:created:clear]' +css: | + .calculation { + font-size: 2em; + } + .calculation input { + width: 4em; + text-align: right; + } + +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: true + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_element_states.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_element_states.yml index cea91135ad..ebb2450198 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_element_states.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_element_states.yml @@ -6,8 +6,10 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_element_states title: 'Example: Elements: Condition Logic' description: 'Examples of elements using conditional logic.' @@ -65,11 +67,11 @@ elements: | 1: One 2: Two 3: Three - other: Other... + other: Other… select_other: '#type': textfield '#attributes': - placeholder: 'Enter other...' + placeholder: 'Enter other…' '#states': visible: ':input[name="select"]': @@ -92,7 +94,7 @@ elements: | select_multiple_other: '#type': textfield '#attributes': - placeholder: 'Enter other...' + placeholder: 'Enter other…' '#states': visible: - ':input[name="select_multiple"]': @@ -117,11 +119,11 @@ elements: | 1: One 2: Two 3: Three - other: Other... + other: Other… radios_other: '#type': textfield '#attributes': - placeholder: 'Enter other...' + placeholder: 'Enter other…' '#states': visible: ':input[name="radios"]': @@ -197,6 +199,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -204,6 +207,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -219,22 +223,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -245,6 +264,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -263,9 +283,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -318,4 +340,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_flexbox_layout.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_flexbox_layout.yml index 2e630fb852..ff1d19559c 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_flexbox_layout.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_flexbox_layout.yml @@ -6,8 +6,10 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_flexbox_layout title: 'Example: Flexbox layout' description: 'Examples of multiple column webform layout using <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">Flexbox</a>.' @@ -43,7 +45,7 @@ elements: | - day - year gender: - '#type': radios + '#type': webform_radios_other '#title': Gender '#options': gender '#options_display': two_columns @@ -90,7 +92,7 @@ elements: | '#options': state_province_names zip: '#type': textfield - '#title': Zip + '#title': ZIP address_line_4: country: '#type': select @@ -123,6 +125,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -130,6 +133,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -145,22 +149,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -171,6 +190,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -189,9 +209,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -244,4 +266,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_input_masks.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_input_masks.yml index 43b1a9cbfc..33521b63cf 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_input_masks.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_input_masks.yml @@ -6,8 +6,10 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_input_masks title: 'Example: Input Masks' description: 'Examples of elements with <a href="https://github.com/RobinHerbots/jquery.inputmask">input masks</a>.' @@ -23,7 +25,7 @@ elements: | '#input_mask': '(999) 999-9999' zip: '#type': textfield - '#title': 'Zip code' + '#title': 'ZIP Code' '#input_mask': '99999[-9999]' ssn: '#type': textfield @@ -41,10 +43,10 @@ elements: | '#type': textfield '#title': Email '#input_mask': '''alias'': ''email''' - date: + datetime: '#type': textfield - '#title': 'Date (mm/dd/yyyy)' - '#input_mask': '''alias'': ''mm/dd/yyyy''' + '#title': 'Date time (2007-06-09''T''17:46:21)' + '#input_mask': '''alias'': ''datetime''' currency: '#type': textfield '#title': Currency @@ -75,6 +77,14 @@ elements: | '#type': textfield '#title': 'VIN (Vehicle identification number)' '#input_mask': '''alias'': ''vin''' + uppercase: + '#type': textfield + '#title': UPPERCASE + '#input_mask': '''casing'': ''upper''' + lowercase: + '#type': textfield + '#title': lowercase + '#input_mask': '''casing'': ''lower''' css: '' javascript: '' settings: @@ -83,6 +93,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -90,6 +101,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -105,22 +117,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -131,6 +158,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -149,9 +177,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -204,4 +234,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_style_guide.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_style_guide.yml index 97e5023188..fca3cc2601 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_style_guide.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_style_guide.yml @@ -6,8 +6,10 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_style_guide title: 'Example: Style Guide' description: 'Style Guide containing examples of all elements and layouts.' @@ -79,18 +81,9 @@ elements: | '#title': 'Checkboxes with descriptions' '#options_description_display': description '#options': - one: 'One -- This is a descripion.' - two: 'Two -- This is a descripion.' - three: 'Three -- This is a descripion.' - checkboxes_icheck: - '#type': checkboxes - '#title': 'Checkboxes (iCheck)' - '#options_display': side_by_side - '#options': - one: One - two: Two - three: Three - '#icheck': minimal + one: 'One -- This is a description.' + two: 'Two -- This is a description.' + three: 'Three -- This is a description.' radios: '#type': radios '#title': Radios @@ -99,15 +92,6 @@ elements: | one: One two: Two three: Three - radios_icheck: - '#type': radios - '#title': 'Radios (iCheck)' - '#options_display': side_by_side - '#options': - one: One - two: Two - three: Three - '#icheck': minimal date_elements: '#type': details '#title': 'Date elements' @@ -133,6 +117,15 @@ elements: | '#type': details '#title': 'Advanced elements' '#open': true + email: + '#type': email + '#title': Email + email_multiple: + '#type': webform_email_multiple + '#title': 'Email multiple' + email_confirm: + '#type': webform_email_confirm + '#title': Email tel: '#type': tel '#title': Telephone @@ -140,6 +133,7 @@ elements: | '#type': tel '#title': 'Telephone (International)' '#international': true + '#telephone_validation_format': '0' url: '#type': url '#title': URL @@ -236,16 +230,6 @@ elements: | webform_terms_of_service: '#type': webform_terms_of_service '#terms_content': 'These are the terms of service.' - webform_toggle: - '#type': webform_toggle - '#title': Toggle - webform_toggles: - '#type': webform_toggles - '#title': Toggles - '#options': - one: One - two: Two - three: Three webform_likert: '#type': webform_likert '#title': Likert @@ -344,8 +328,8 @@ elements: | '#type': webform_address '#title': Address '#flexbox': false - webform_location: - '#type': webform_location + webform_location_geocomplete: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true @@ -366,7 +350,7 @@ elements: | '#type': textfield '#title': 'Last name' gender: - '#type': select + '#type': select_other '#options': gender '#title': Gender martial_status: @@ -379,7 +363,7 @@ elements: | '#title': 'Employment status' age: '#type': number - '#title': 'Age' + '#title': Age form_elements: '#type': details '#title': 'Form elements' @@ -389,12 +373,12 @@ elements: | '#title': 'Form element' '#description': '{description}' '#size': 5 - '#field_prefix': '$' + '#field_prefix': $ '#field_suffix': '.00' form_element_required: '#type': textfield '#title': 'Form element (Required)' - '#required': TRUE + '#required': true '#default_value': '{value}' '#attributes': class: @@ -408,6 +392,12 @@ elements: | '#type': textfield '#title': 'Form element (Input mask: Phone)' '#input_mask': '(999) 999-9999' + '#test': '' + form_element_input_hide: + '#type': textfield + '#title': 'Form element (Input hiding)' + '#input_hide': true + '#default_value': '{value}' title_display_before: '#type': textfield '#title': 'Title displayed before' @@ -477,9 +467,9 @@ elements: | '#wrapper_attributes': class: - 'container-inline clearfix' - divider: + dividers: '#type': details - '#title': 'Divider' + '#title': Dividers '#open': true horizontal_rule_dotted_medium: '#type': webform_horizontal_rule @@ -534,7 +524,7 @@ elements: | '#message_close': true flexbox: '#type': details - '#title': 'Flexbox' + '#title': Flexbox '#open': true webform_flexbox: '#type': webform_flexbox @@ -551,6 +541,14 @@ elements: | '#type': textfield '#title': 'Element (Flex: 3)' '#flex': 3 + contrib: + '#type': details + '#title': 'Contrib modules' + '#open': true + address: + '#type': address + '#title': Address + '#description': '@see <a href="https://www.drupal.org/project/address">https://www.drupal.org/project/address</a>' css: '' javascript: '' settings: @@ -559,6 +557,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -566,6 +565,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -581,22 +581,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -607,6 +622,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -625,9 +641,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -680,4 +698,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml index 2ea60bd0e0..f28cb08527 100644 --- a/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml +++ b/web/modules/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml @@ -6,8 +6,10 @@ dependencies: - webform_examples open: null close: null +weight: 0 uid: null template: false +archive: false id: example_wizard title: 'Example: Wizard' description: 'Example of a multiple step ''wizard'' webform.' @@ -26,7 +28,7 @@ elements: | '#title': 'Last Name' '#type': textfield gender: - '#type': radios + '#type': webform_radios_other '#title': Gender '#options': gender contact: @@ -61,6 +63,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -68,6 +71,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -83,22 +87,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: true wizard_start_label: '' + wizard_preview_link: true wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -109,6 +128,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: true @@ -127,9 +147,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -182,4 +204,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_examples/webform_examples.info.yml b/web/modules/webform/modules/webform_examples/webform_examples.info.yml index bbef013ce6..4d18dd1ada 100644 --- a/web/modules/webform/modules/webform_examples/webform_examples.info.yml +++ b/web/modules/webform/modules/webform_examples/webform_examples.info.yml @@ -6,8 +6,8 @@ package: 'Webform example' dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_advanced.yml b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_advanced.yml new file mode 100644 index 0000000000..ad3c3535b6 --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_advanced.yml @@ -0,0 +1,365 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples_accessibility +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_accessibility_advanced +title: 'Example: Accessibility Advanced' +description: 'Advanced webform accessibility example.' +category: 'Example: Accessibility' +elements: | + email_elements: + '#type': details + '#title': 'Email elements' + '#open': true + email: + '#type': email + '#title': Email + email_multiple: + '#type': webform_email_multiple + '#title': 'Email multiple' + email_confirm: + '#type': webform_email_confirm + '#title': Email + option_elements: + '#type': details + '#title': 'Option elements' + '#open': true + select_select2: + '#type': select + '#title': Select2 + '#options': + one: One + two: Two + three: Three + '#select2': true + select_select2_multiple: + '#type': select + '#title': 'Select2 multiple' + '#options': + one: One + two: Two + three: Three + '#select2': true + '#multiple': true + webform_buttons: + '#type': webform_buttons + '#title': Buttons + '#options': + one: One + two: Two + three: Three + webform_image_select: + '#type': webform_image_select + '#title': 'Image select' + '#show_label': true + '#images': + kitten_1: + text: 'Cute Kitten 1' + src: 'http://placekitten.com/220/200' + kitten_2: + text: 'Cute Kitten 2' + src: 'http://placekitten.com/180/200' + kitten_3: + text: 'Cute Kitten 3' + src: 'http://placekitten.com/130/200' + webform_likert: + '#type': webform_likert + '#title': Likert + '#questions': + q1: 'Please answer question 1?' + q2: 'How about now answering question 2?' + q3: 'Finally, here is question 3?' + '#answers': + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + webform_autocomplete: + '#type': webform_autocomplete + '#title': Autocomplete + '#autocomplete_items': country_names + other_elements: + '#type': details + '#title': 'Other elements' + '#open': true + webform_select_other: + '#type': webform_select_other + '#title': 'Select other' + '#options': + one: One + two: Two + three: Three + webform_radios_other: + '#type': webform_radios_other + '#title': 'Radios other' + '#options': + one: One + two: Two + three: Three + webform_checkboxes_other: + '#type': webform_checkboxes_other + '#title': 'Checkboxes other' + '#options': + one: One + two: Two + three: Three + webform_buttons_other: + '#type': webform_buttons_other + '#title': 'Buttons other' + '#options': + one: One + two: Two + three: Three + input_mask_elements: + '#type': details + '#title': 'Input mask elements' + '#open': true + phone: + '#type': textfield + '#title': Phone + '#input_mask': '(999) 999-9999' + input_hide_elements: + '#type': details + '#title': 'Input hiding elements' + '#open': true + textfield_input_hide: + '#type': textfield + '#title': 'Textfield with input hiding' + '#input_hide': true + '#default_value': '{value}' + email_input_hide: + '#type': email + '#title': 'Email with input hiding' + '#input_hide': true + '#default_value': example@example.com + widget_elements: + '#type': details + '#title': 'Widget elements' + '#open': true + tel_international: + '#type': tel + '#title': 'Telephone (International)' + '#international': true + '#telephone_validation_format': '0' + webform_rating: + '#type': webform_rating + '#title': Rating + webform_signature: + '#type': webform_signature + '#title': Signature + webform_terms_of_service: + '#type': webform_terms_of_service + '#terms_content': 'These are the terms of service.' + table_elements: + '#type': details + '#title': 'Table elements' + '#open': true + tableselect: + '#type': tableselect + '#title': Tableselect + '#options': + one: One + two: Two + three: Three + webform_table_sort: + '#type': webform_table_sort + '#title': 'Table sort' + '#options': + one: One + two: Two + three: Three + webform_tableselect_sort: + '#type': webform_tableselect_sort + '#title': 'Tableselect sort' + '#options': + one: One + two: Two + three: Three + composite_elements: + '#type': details + '#title': 'Composite elements' + '#open': true + address: + '#title': Address + '#type': webform_address + multiple_elements: + '#type': details + '#title': 'Multiple elements' + '#open': true + textfield: + '#title': 'Text field' + '#type': textfield + '#multiple': true + webform_custom_composite: + '#type': webform_custom_composite + '#title': 'Custom composite' + '#element': + first_name: + '#type': textfield + '#title': 'First name' + last_name: + '#type': textfield + '#title': 'Last name' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_basic.yml b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_basic.yml new file mode 100644 index 0000000000..475ba2e0ed --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_basic.yml @@ -0,0 +1,281 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples_accessibility +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_accessibility_basic +title: 'Example: Accessibility Basic' +description: 'Basic webform accessibility example.' +category: 'Example: Accessibility' +elements: | + text_elements: + '#type': details + '#title': 'Text elements' + '#open': true + textfield: + '#title': 'Text field' + '#type': textfield + textarea: + '#title': Textarea + '#type': textarea + option_elements: + '#type': details + '#title': 'Option elements' + '#open': true + select: + '#title': 'Select one' + '#type': select + '#options': + option_1: 'Option 1' + option_2: 'Option 2' + option_3: 'Option 3' + select_multiple: + '#title': 'Select multiple' + '#type': select + '#multiple': true + '#options': + option_1: 'Option 1' + option_2: 'Option 2' + option_3: 'Option 3' + radios: + '#title': Radios + '#type': radios + '#options': + option_1: 'Option 1' + option_2: 'Option 2' + option_3: 'Option 3' + checkboxes: + '#title': Checkboxes + '#type': checkboxes + '#options': + option_1: 'Option 1' + option_2: 'Option 2' + option_3: 'Option 3' + checkbox: + '#type': checkbox + '#title': Checkbox + '#description': 'This is a description' + file_elements: + '#type': details + '#title': 'File elements' + '#open': true + managed_file: + '#type': managed_file + '#title': 'Managed single file' + managed_file_multiple: + '#type': managed_file + '#title': 'Managed multiple file' + '#multiple': true + date_elements: + '#type': details + '#title': 'Date elements' + '#open': true + date: + '#type': date + '#title': Date + datetime: + '#type': datetime + '#title': Date/time + datelist: + '#type': datelist + '#title': 'Date list' + date_datepicker: + '#type': date + '#title': 'Date picker' + '#datepicker': true + '#date_date_format': 'D, m/d/Y' + webform_time: + '#type': webform_time + '#title': Time + advanced_elements: + '#type': details + '#title': 'Advanced elements' + '#open': true + color: + '#type': color + '#title': Color + tel: + '#type': tel + '#title': Telephone + url: + '#type': url + '#title': URL + search: + '#type': search + '#title': Search + number: + '#type': number + '#title': Number + '#min': 0 + '#max': 10 + '#step': 1 + range: + '#type': range + '#title': Range + '#min': 0 + '#max': 100 + '#step': 1 + '#output': right + '#output__field_prefix': $ + '#output__field_suffix': '.00' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_containers.yml b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_containers.yml new file mode 100644 index 0000000000..19b687ac87 --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_containers.yml @@ -0,0 +1,206 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples_accessibility +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_accessibility_containers +title: 'Example: Accessibility Containers' +description: 'Container webform accessibility example.' +category: 'Example: Accessibility' +elements: | + form_element: + '#type': textfield + '#title': 'Form element' + '#placeholder': 'This is a placeholder' + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + fieldset: + '#type': fieldset + '#title': Fieldset + '#placeholder': 'This is a placeholder' + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + details: + '#type': details + '#title': details + '#placeholder': 'This is a placeholder' + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#open': true + section: + '#type': webform_section + '#title': Section + '#help': 'This is help text' + '#required': true + '#description': 'This is a description' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_labels.yml b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_labels.yml new file mode 100644 index 0000000000..a725c40541 --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_labels.yml @@ -0,0 +1,336 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples_accessibility +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_accessibility_labels +title: 'Example: Accessibility Labels & Descriptions' +description: 'Example of webform label and description accessibility.' +category: 'Example: Accessibility' +elements: | + form_elements: + '#type': details + '#title': 'Form elements' + '#open': true + form_element: + '#type': textfield + '#title': 'Form element' + '#placeholder': 'This is a placeholder' + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + datelist_element: + '#type': datelist + '#title': Datelist + '#placeholder': 'This is a placeholder' + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#field_prefix': prefix + '#field_suffix': suffix + element_title: + '#type': details + '#title': 'Element title' + '#open': true + title_display_before: + '#type': textfield + '#title': 'Title displayed before' + '#title_display': before + title_display_after: + '#type': textfield + '#title': 'Title displayed after' + '#title_display': after + title_display_inline: + '#type': textfield + '#title': 'Title displayed inline' + '#title_display': inline + '#description': 'This is a description.' + title_display_inline_composite: + '#type': radios + '#title': 'Title displayed inline composite' + '#title_display': inline + '#description': 'This is a description.' + '#options': + one: One + two: Two + three: Three + element_description: + '#type': details + '#title': 'Element description' + '#open': true + description_display_after: + '#type': textfield + '#title': 'Description displayed after' + '#description': 'This is a description.' + description_display_before: + '#type': textfield + '#title': 'Description displayed before' + '#description': 'This is a description.' + '#description_display': before + description_display_tooltip: + '#type': textfield + '#title': 'Description displayed in tooltip' + '#description': 'This is a description.' + '#description_display': tooltip + element_help: + '#type': details + '#title': 'Element help' + '#open': true + help: + '#type': textfield + '#title': Help + '#help': 'This is help.' + element_more: + '#type': details + '#title': 'Element more' + '#open': true + more: + '#type': textfield + '#title': More + '#more': 'This is more text.' + fieldset_elements: + '#type': details + '#title': 'Fieldset elements' + '#open': true + fieldset: + '#type': fieldset + '#title': Fieldset + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + fieldset_description: + '#type': fieldset + '#title': 'Fieldset description' + '#description': 'This is a description.' + fieldset_title_invisible: + '#type': fieldset + '#title': 'Fieldset description invisible' + '#description': 'This is a description.' + '#title_display': invisible + fieldset_help: + '#type': fieldset + '#title': 'Fieldset help' + '#help': 'This is help text.' + fieldset_more: + '#type': fieldset + '#title': 'Fieldset more' + '#more': 'This is more text' + details_elements: + '#type': details + '#title': 'Details elements' + '#open': true + details: + '#type': details + '#title': details + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#open': true + details_description: + '#type': details + '#title': 'details description' + '#description': 'This is a description.' + '#open': true + details_description_before: + '#type': details + '#title': 'details description before' + '#description': 'This is a description.' + '#description_display': before + '#open': true + details_description_before_textfield: + '#type': textfield + '#title': 'Details description before textfield' + details_description_invisible: + '#type': details + '#title': 'details description invisible' + '#description': 'This is a description.' + '#description_display': invisible + '#open': true + details_help: + '#type': details + '#title': 'details help' + '#help': 'This is help text.' + '#open': true + details_more: + '#type': details + '#title': 'details more' + '#more': 'This is more text' + '#open': true + section_elements: + '#type': details + '#title': 'Section elements' + '#open': true + section: + '#type': webform_section + '#title': Section + '#help': 'This is help text' + '#required': true + '#description': 'This is a description' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_wizard.yml b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_wizard.yml new file mode 100644 index 0000000000..2249f2d8ef --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/config/install/webform.webform.example_accessibility_wizard.yml @@ -0,0 +1,211 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples_accessibility +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: example_accessibility_wizard +title: 'Example: Accessibility Wizard' +description: 'Wizard webform accessibility example.' +category: 'Example: Accessibility' +elements: | + '#attributes': + data-current-page: '[webform_submission:current-page]' + information: + '#title': 'Your Information' + '#type': webform_wizard_page + '#open': true + first_name: + '#title': 'First Name' + '#type': textfield + last_name: + '#title': 'Last Name' + '#type': textfield + gender: + '#type': webform_radios_other + '#title': Gender + '#options': gender + contact: + '#title': 'Contact Information' + '#type': webform_wizard_page + '#open': true + email: + '#title': Email + '#type': email + phone: + '#title': Phone + '#type': tel + contact_via_phone: + '#type': radios + '#title': 'Can we contact you via phone?' + '#options': yes_no + feedback: + '#title': 'Your Feedback' + '#type': webform_wizard_page + '#open': true + comments: + '#type': textarea + actions: + '#type': webform_actions + '#title': 'Submit button(s)' + '#submit__label': Apply +css: '' +javascript: '' +settings: + ajax: true + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: true + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: true + wizard_start_label: '' + wizard_preview_link: true + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 2 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: true + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_examples_accessibility/css/webform_examples_accessibility.css b/web/modules/webform/modules/webform_examples_accessibility/css/webform_examples_accessibility.css new file mode 100644 index 0000000000..fff4e81883 --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/css/webform_examples_accessibility.css @@ -0,0 +1,52 @@ +/** + * @file + * Webform examples accessibility styles. + */ + +/** + * Show fieldsets. + */ +[role=main] fieldset { + border: 2px dashed red !important; + padding: 4px !important; +} + +/** + * Show visually hidden (labels). + */ +[role=main] .visually-hidden { + border: 2px dashed blue !important; + color: blue !important; + padding: 4px !important; + position: relative !important; + clip: auto !important; + overflow: visible !important; + height: auto !important; + width: auto !important; +} + +/** + * Show aria-labels. + */ +[role=main] [aria-label]:after { + content: "[aria-label]: " attr(aria-label); + border: 2px dashed green !important; + padding: 4px !important; + position: relative !important; + font-size: 0.8em; + font-weight: normal; + color: green; +} + +/** + * Show titles. + */ +[role=main] [title]:after { + content: "[title]: " attr(title); + border: 2px dashed green !important; + padding: 4px !important; + position: relative !important; + font-size: 0.8em; + font-weight: normal; + color: green; +} diff --git a/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.info.yml b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.info.yml new file mode 100644 index 0000000000..c764c2d07f --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Examples Accessibility' +type: module +description: 'Provides example webforms for reviewing and testing accessibility.' +package: 'Webform example' +# core: 8.x +dependencies: + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.libraries.yml b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.libraries.yml new file mode 100644 index 0000000000..84b34ab01b --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.libraries.yml @@ -0,0 +1,5 @@ +webform_examples_accessibility: + version: VERSION + css: + theme: + css/webform_examples_accessibility.css: {} diff --git a/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.module b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.module new file mode 100644 index 0000000000..b35c237c2b --- /dev/null +++ b/web/modules/webform/modules/webform_examples_accessibility/webform_examples_accessibility.module @@ -0,0 +1,132 @@ +<?php + +/** + * @file + * Provides example webforms for reviewing and testing accessibility. + */ + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\webform\Utility\WebformArrayHelper; + +/** + * Implements hook_page_attachments(). + */ +function webform_examples_accessibility_page_attachments(array &$attachments) { + // Attach accessibility library which shows all fieldsets and labels. + if (\Drupal::request()->query->get('accessibility') == '1') { + $attachments['#attached']['library'][] = 'webform_examples_accessibility/webform_examples_accessibility'; + } +} + +/** + * Implements hook_webform_submission_form_alter(). + * + * Adds button to disable/enable HTML client-side validation without have + * to change any webform settings. + * + * The link is only applicable to webform ids that are prefix with examples_accessibility_*. + */ +function webform_examples_accessibility_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + if (strpos($form['#webform_id'], 'example_accessibility_') !== 0 + && !preg_match('/^issue_\d+$/', $form['#webform_id']) + && strpos($form['#webform_id'], 'test_') !== 0) { + return; + } + + $form['accessibility'] = [ + '#suffix' => '<hr/>', + '#weight' => -1000, + ]; + + /****************************************************************************/ + // Accessibility. + /****************************************************************************/ + + // Get query without ajax parameters. + $query = \Drupal::request()->query->all(); + unset($query['ajax_form'], $query['_wrapper_format']); + + $accessibility = (\Drupal::request()->query->get('accessibility') == '1') ? TRUE : FALSE; + + $form['accessibility']['accessibility'] = [ + '#type' => 'link', + '#title' => $accessibility ? t('Hide accessibility') : t('Show accessibility'), + '#url' => Url::fromRoute('<current>', [], ['query' => ['accessibility' => $accessibility ? 0 : 1] + $query]), + ]; + + /****************************************************************************/ + // Required. + /****************************************************************************/ + + $form['accessibility'][] = ['#markup' => ' | ']; + + if (\Drupal::request()->query->get('required') == '1') { + $required = TRUE; + } + elseif (\Drupal::request()->query->get('required') == '0') { + $required = FALSE; + } + else { + $required = NULL; + } + + $form['accessibility']['required'] = [ + '#type' => 'link', + '#title' => $required ? t('Disabled required') : t('Enable required'), + '#url' => Url::fromRoute('<current>', [], ['query' => ['required' => $required ? 0 : 1] + $query]), + ]; + + if ($required !== NULL) { + $elements = &WebformArrayHelper::flattenAssoc($form); + foreach ($elements as &$element) { + if (is_array($element) && isset($element['#type'])) { + $element['#required'] = $required; + } + } + } + + /****************************************************************************/ + // No validate. + /****************************************************************************/ + + $form['accessibility'][] = ['#markup' => ' | ']; + + if (\Drupal::request()->query->get('novalidate') == '1') { + $form['#attributes']['novalidate'] = TRUE; + $novalidate = TRUE; + } + else { + unset($form['#attributes']['novalidate']); + $novalidate = FALSE; + } + + $form['accessibility']['novalidate'] = [ + '#type' => 'link', + '#title' => $novalidate ? t('Enable client-side validation') : t('Disable client-side validation'), + '#url' => Url::fromRoute('<current>', [], ['query' => ['novalidate' => $novalidate ? 0 : 1] + $query]), + ]; + + /****************************************************************************/ + // Inline form error. + /****************************************************************************/ + + if (\Drupal::moduleHandler()->moduleExists('inline_form_errors')) { + $form['accessibility'][] = ['#markup' => ' | ']; + + if (\Drupal::request()->query->get('disable_inline_form_errors') == '1') { + $form['#disable_inline_form_errors'] = TRUE; + $disable_inline_form_errors = TRUE; + } + else { + unset($form['#disable_inline_form_errors']); + $disable_inline_form_errors = FALSE; + } + + $form['accessibility']['disable_inline_form_errors'] = [ + '#type' => 'link', + '#title' => $disable_inline_form_errors ? t('Enable inline form errors') : t('Disable inline form errors'), + '#url' => Url::fromRoute('<current>', [], ['query' => ['disable_inline_form_errors' => $disable_inline_form_errors ? 0 : 1] + $query]), + ]; + } +} diff --git a/web/modules/webform/modules/webform_image_select/config/schema/webform_image_select.schema.yml b/web/modules/webform/modules/webform_image_select/config/schema/webform_image_select.schema.yml index 3e0bab240e..dc311cae5b 100644 --- a/web/modules/webform/modules/webform_image_select/config/schema/webform_image_select.schema.yml +++ b/web/modules/webform/modules/webform_image_select/config/schema/webform_image_select.schema.yml @@ -1,16 +1,16 @@ -webform_image_select.webform_image_select_images.*: +'webform_image_select.webform_image_select_images.*': type: config_entity - label: 'Images' + label: Images mapping: id: type: string label: 'Machine name' label: type: label - label: 'Label' + label: Label category: type: label - label: 'Category' + label: Category images: type: text label: 'Images (YAML)' diff --git a/web/modules/webform/modules/webform_image_select/css/webform_image_select.element.css b/web/modules/webform/modules/webform_image_select/css/webform_image_select.element.css index 5654f45554..c171acfdc6 100644 --- a/web/modules/webform/modules/webform_image_select/css/webform_image_select.element.css +++ b/web/modules/webform/modules/webform_image_select/css/webform_image_select.element.css @@ -14,6 +14,18 @@ html.js .js-webform-image-select { opacity: 0; } +.webform-image-select-filter .field-suffix { + margin-left: 10px; +} + .thumbnails.image_picker_selector p { margin: 0; } + +.thumbnails.image_picker_selector .thumbnail.focused { + border: 1px solid #0088cc; +} + +.thumbnails.image_picker_selector .thumbnail.selected { + color: #fff; +} diff --git a/web/modules/webform/modules/webform_image_select/js/webform_image_select.element.js b/web/modules/webform/modules/webform_image_select/js/webform_image_select.element.js index 72eb938a87..74d3e07591 100644 --- a/web/modules/webform/modules/webform_image_select/js/webform_image_select.element.js +++ b/web/modules/webform/modules/webform_image_select/js/webform_image_select.element.js @@ -25,6 +25,7 @@ $('.js-webform-image-select', context).once('webform-image-select').each(function () { var $select = $(this); + var isMultiple = $select.attr('multiple'); // Apply image data to options. var images = JSON.parse($select.attr('data-images')); @@ -50,6 +51,72 @@ } $select.imagepicker(options); + + // Add very basic accessibility to the image picker by + // enabling tabbing and toggling via the spacebar. + // @see https://github.com/rvera/image-picker/issues/108 + + // Block select menu from being tabbed. + $select.attr('tabindex', '-1'); + + if (isMultiple) { + $select.next('.image_picker_selector').attr('role', 'radiogroup'); + } + + var $thumbnail = $select.next('.image_picker_selector').find('.thumbnail'); + $thumbnail + // Allow thumbnail to be tabbed. + .prop('tabindex', '0') + .attr('role', isMultiple ? 'checkbox' : 'radio') + .each(function () { + var alt = $(this).find('img').attr('alt'); + // Cleanup alt, set title, and fix aria. + if (alt) { + alt = alt.replace(/<\/?[^>]+(>|$)/g, ''); + $(this).find('img').attr('alt', alt); + $(this).attr('title', alt); + } + + // Aria hide caption since the 'title' attribute will be read aloud. + $(this).find('p').attr('aria-hidden', true); + }) + .on('focus', function (event) { + $(this).addClass('focused'); + }) + .on('blur', function (event) { + $(this).removeClass('focused'); + }) + .on('keydown', function (event) { + if (event.which === 32) { + // Space. + $(this).click(); + event.preventDefault(); + } + else if (event.which === 37 || event.which === 38) { + // Left or Up. + var $prev = $(this).parent(); + do { + $prev = $prev.prev(); + } + while ($prev.length && $prev.is(':hidden')); + $prev.find('.thumbnail').focus(); + event.preventDefault(); + } + else if (event.which === 39 || event.which === 40) { + // Right or Down. + var $next = $(this).parent(); + do { + $next = $next.next(); + } + while ($next.length && $next.is(':hidden')); + $next.find('.thumbnail').focus(); + event.preventDefault(); + } + }) + .on('click', function (event) { + var selected = $(this).hasClass('selected'); + $(this).attr('aria-checked', selected); + }); }); } }; diff --git a/web/modules/webform/modules/webform_image_select/src/Access/WebformImageSelectAccess.php b/web/modules/webform/modules/webform_image_select/src/Access/WebformImageSelectAccess.php index a80ae4d338..108013aeec 100644 --- a/web/modules/webform/modules/webform_image_select/src/Access/WebformImageSelectAccess.php +++ b/web/modules/webform/modules/webform_image_select/src/Access/WebformImageSelectAccess.php @@ -23,7 +23,8 @@ class WebformImageSelectAccess { * The access result. */ public static function checkImagesSourceAccess(WebformImageSelectImagesInterface $webform_image_select_images, AccountInterface $account) { - return AccessResult::allowedIf($webform_image_select_images->access('update', $account) && $account->hasPermission('edit webform source')); + return $webform_image_select_images->access('update', $account, TRUE) + ->andIf(AccessResult::allowedIfHasPermission($account, 'edit webform source')); } } diff --git a/web/modules/webform/modules/webform_image_select/src/Controller/WebformImageSelectImagesController.php b/web/modules/webform/modules/webform_image_select/src/Controller/WebformImageSelectImagesController.php new file mode 100644 index 0000000000..09dd3bc3af --- /dev/null +++ b/web/modules/webform/modules/webform_image_select/src/Controller/WebformImageSelectImagesController.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\webform_image_select\Controller; + +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Controller\ControllerBase; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * Provides route responses for webform images options. + */ +class WebformImageSelectImagesController extends ControllerBase { + + /** + * Returns response for the webform options autocompletion. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request object containing the search string. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * A JSON response containing the autocomplete suggestions. + */ + public function autocomplete(Request $request) { + $q = $request->query->get('q'); + + $webform_images_storage = $this->entityTypeManager()->getStorage('webform_image_select_images'); + + $query = $webform_images_storage->getQuery() + ->range(0, 10) + ->sort('label'); + + // Query title and id. + $or = $query->orConditionGroup() + ->condition('id', $q, 'CONTAINS') + ->condition('label', $q, 'CONTAINS'); + $query->condition($or); + + $entity_ids = $query->execute(); + + if (empty($entity_ids)) { + return new JsonResponse([]); + } + $webform_images = $webform_images_storage->loadMultiple($entity_ids); + + $matches = []; + foreach ($webform_images as $webform_image) { + $value = new FormattableMarkup('@label (@id)', ['@label' => $webform_image->label(), '@id' => $webform_image->id()]); + $matches[] = ['value' => $value, 'label' => $value]; + } + + return new JsonResponse($matches); + } + +} diff --git a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelect.php b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelect.php index cccda438e4..21267f07e5 100644 --- a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelect.php +++ b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelect.php @@ -3,10 +3,11 @@ namespace Drupal\webform_image_select\Element; use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\Select; -use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformElementHelper; /** * Provides a webform element for a selecting an image. @@ -23,6 +24,11 @@ public function getInfo() { $info['#images'] = []; $info['#images_randomize'] = FALSE; $info['#show_label'] = FALSE; + $info['#filter'] = FALSE; + $info['#filter__placeholder'] = NULL; + $info['#filter__singular'] = NULL; + $info['#filter__plural'] = NULL; + $info['#filter__no_result'] = NULL; return $info; } @@ -38,6 +44,67 @@ public static function processSelect(&$element, FormStateInterface $form_state, $element['#attributes']['data-show-label'] = 'data-show-label'; } + // Add label filter. + if ($element['#show_label'] && $element['#filter']) { + $field_prefix = (isset($element['#field_prefix'])) ? $element['#field_prefix'] : NULL; + + $wrapper_class = 'js-' . Html::getClass($element['#name'] . '-filter'); + $element['#wrapper_attributes']['class'][] = $wrapper_class; + $singular = (!empty($element['#filter__singular'])) ? $element['#filter__singular'] : t('image'); + $plural = (!empty($element['#filter__plural'])) ? $element['#filter__plural'] : t('images'); + $count = count($element['#images']); + $element['#field_prefix'] = [ + 'filter' => [ + '#type' => 'search', + '#id' => $element['#id'] . '-filter', + '#name' => $element['#name'] . '_filter', + '#title' => t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => (!empty($element['#filter__placeholder'])) ? $element['#filter__placeholder'] : t('Filter images by label'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-focus' => 'false', + 'data-item-singlular' => $singular, + 'data-item-plural' => $plural, + 'data-summary' => ".$wrapper_class .webform-image-select-summary", + 'data-no-results' => ".$wrapper_class .webform-image-select-no-results", + 'data-element' => ".$wrapper_class .thumbnails", + 'data-source' => ".thumbnail p", + 'data-parent' => 'li', + 'data-selected' => '.selected', + 'title' => t('Enter a keyword to filter by.'), + ], + '#wrapper_attributes' => ['class' => ['webform-image-select-filter']], + '#field_suffix' => [ + 'info' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#attributes' => ['class' => ['webform-image-select-summary']], + 'content' => [ + '#markup' => t('@count @items', [ + '@count' => $count, + '@items' => ($count === 1) ? $singular : $plural, + ]), + ], + ], + 'no_results' => [ + '#type' => 'webform_message', + '#attributes' => ['style' => 'display:none', 'class' => ['webform-image-select-no-results']], + '#message_message' => (!empty($element['#filter__no_results'])) ? $element['#filter__no_results'] : t('No images found.'), + '#message_type' => 'info', + ], + ], + ], + ]; + + if ($field_prefix) { + $element['#field_prefix']['field_prefix'] = (is_array($element['#field_prefix'])) + ? $element['#field_prefix'] + : ['#markup' => $element['#field_prefix']]; + } + } + // Set limit. if ($element['#multiple'] && $element['#multiple'] > 1) { $element['#attributes']['data-limit'] = $element['#multiple']; @@ -53,6 +120,9 @@ public static function processSelect(&$element, FormStateInterface $form_state, // Attach library. $element['#attached']['library'][] = 'webform_image_select/webform_image_select.element'; + if ($element['#show_label'] && $element['#filter']) { + $element['#attached']['library'][] = 'webform/webform.filter'; + } return parent::processSelect($element, $form_state, $complete_form); } @@ -66,16 +136,21 @@ public static function processSelect(&$element, FormStateInterface $form_state, public static function setOptions(array &$element) { // Randomize images. if (!empty($element['#images_randomize'])) { - $element['#images'] = array_values(WebformArrayHelper::shuffle($element['#images'])); + $element['#images'] = WebformElementHelper::randomize($element['#images']); } // Convert #images to #options and make sure images are keyed by value. - if (empty($element['#options']) && !empty($element['#images'])) { + if (empty($element['#options'])) { $options = []; foreach ($element['#images'] as $value => &$image) { - // Apply XSS filter to image text. - $image['text'] = Xss::filter($image['text']); - $options[$value] = $image['text']; + if (isset($image['text'])) { + // Apply XSS filter to image text. + $image['text'] = Xss::filter($image['text']); + $options[$value] = $image['text']; + } + else { + $options[$value] = $value; + } } $element['#options'] = $options; } diff --git a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectElementImages.php b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectElementImages.php index 23e3b7f2a5..7d98d3d6a8 100644 --- a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectElementImages.php +++ b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectElementImages.php @@ -85,7 +85,7 @@ public static function processWebformImageSelectElementImages(&$element, FormSta '#type' => 'select', '#description' => t('Please select <a href=":href">predefined images</a> or enter custom image.', $t_args), '#options' => [ - self::CUSTOM_OPTION => t('Custom images...'), + self::CUSTOM_OPTION => t('Custom images…'), ] + $webform_images, '#attributes' => [ 'class' => [$class_name], diff --git a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectImages.php b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectImages.php index bb500036b5..9e72c55ccc 100644 --- a/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectImages.php +++ b/web/modules/webform/modules/webform_image_select/src/Element/WebformImageSelectImages.php @@ -24,8 +24,9 @@ public function getInfo() { '#input' => TRUE, '#label' => t('image'), '#labels' => t('images'), - '#empty_items' => 5, - '#add_more' => 1, + '#min_items' => 3, + '#empty_items' => 1, + '#add_more_items' => 1, '#process' => [ [$class, 'processWebformImageSelectImages'], ], @@ -63,7 +64,7 @@ public static function processWebformImageSelectImages(&$element, FormStateInter // Wrap this $element in a <div> that handle #states. WebformElementHelper::fixStatesWrapper($element); - $properties = ['#label', '#labels', '#empty_items', '#add_more']; + $properties = ['#label', '#labels', '#min_items', '#empty_items', '#add_more_items']; $element['images'] = array_intersect_key($element, array_combine($properties, $properties)) + [ '#type' => 'webform_multiple', @@ -77,26 +78,28 @@ public static function processWebformImageSelectImages(&$element, FormStateInter 'value' => [ '#type' => 'textfield', '#title' => t('Image value'), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter value'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter value…'), '#error_no_message' => TRUE, + '#attributes' => ['class' => ['js-webform-options-sync']], ], 'text' => [ '#type' => 'textfield', '#title' => t('Image text'), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter text'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter text…'), '#error_no_message' => TRUE, ], 'src' => [ '#type' => 'textfield', '#title' => t('Image src'), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter image src'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter image src…'), '#error_no_message' => TRUE, ], ], '#error_no_message' => TRUE, + '#add_more_input_label' => t('more images'), '#default_value' => (isset($element['#default_value'])) ? $element['#default_value'] : [], ]; @@ -116,6 +119,8 @@ public static function processWebformImageSelectImages(&$element, FormStateInter ]; } + $element['#attached']['library'][] = 'webform/webform.element.options.admin'; + return $element; } diff --git a/web/modules/webform/modules/webform_image_select/src/Entity/WebformImageSelectImages.php b/web/modules/webform/modules/webform_image_select/src/Entity/WebformImageSelectImages.php index d65b87d8b0..3950a6aaa2 100644 --- a/web/modules/webform/modules/webform_image_select/src/Entity/WebformImageSelectImages.php +++ b/web/modules/webform/modules/webform_image_select/src/Entity/WebformImageSelectImages.php @@ -15,6 +15,13 @@ * @ConfigEntityType( * id = "webform_image_select_images", * label = @Translation("Webform images"), + * label_collection = @Translation("Images"), + * label_singular = @Translation("images"), + * label_plural = @Translation("images"), + * label_count = @PluralTranslation( + * singular = "@count images", + * plural = "@count images", + * ), * handlers = { * "storage" = "\Drupal\webform_image_select\WebformImageSelectImagesStorage", * "access" = "Drupal\webform_image_select\WebformImageSelectImagesAccessControlHandler", @@ -146,9 +153,8 @@ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) * {@inheritdoc} */ public static function getElementImages(array &$element) { - // If element already has #images return them. - if (is_array($element['#images'])) { + if (isset($element['#images']) && is_array($element['#images'])) { return $element['#images']; } @@ -167,7 +173,7 @@ public static function getElementImages(array &$element) { $images = []; } - // Alter iamges using hook_webform_image_select_images_alter() + // Alter images using hook_webform_image_select_images_alter() // and/or hook_webform_image_select_images_WEBFORM_IMAGE_SELECT_IMAGES_ID_alter() hook. // @see webform.api.php \Drupal::moduleHandler()->alter('webform_image_select_images_' . $id, $images, $element); diff --git a/web/modules/webform/modules/webform_image_select/src/Form/WebformImageSelectImagesFilterForm.php b/web/modules/webform/modules/webform_image_select/src/Form/WebformImageSelectImagesFilterForm.php new file mode 100644 index 0000000000..84ee7e2444 --- /dev/null +++ b/web/modules/webform/modules/webform_image_select/src/Form/WebformImageSelectImagesFilterForm.php @@ -0,0 +1,88 @@ +<?php + +namespace Drupal\webform_image_select\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides the webform image select images filter form. + */ +class WebformImageSelectImagesFilterForm extends FormBase { + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_image_select_images_filter_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $search = NULL, $category = NULL, array $categories = []) { + $form['#attributes'] = ['class' => ['webform-filter-form']]; + $form['filter'] = [ + '#type' => 'details', + '#title' => $this->t('Filter images'), + '#open' => TRUE, + '#attributes' => ['class' => ['container-inline']], + ]; + $form['filter']['search'] = [ + '#type' => 'search', + '#title' => $this->t('Keyword'), + '#title_display' => 'invisible', + '#autocomplete_route_name' => 'entity.webform_image_select_images.autocomplete', + '#placeholder' => $this->t('Filter by title or images'), + '#size' => 45, + '#default_value' => $search, + ]; + $form['filter']['category'] = [ + '#type' => 'select', + '#title' => $this->t('Category'), + '#title_display' => 'invisible', + '#options' => $categories, + '#empty_option' => ($category) ? $this->t('Show all images') : $this->t('Filter by category'), + '#default_value' => $category, + ]; + $form['filter']['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Filter'), + ]; + if (!empty($search) || !empty($category)) { + $form['filter']['reset'] = [ + '#type' => 'submit', + '#submit' => ['::resetForm'], + '#value' => $this->t('Reset'), + ]; + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $query = [ + 'search' => trim($form_state->getValue('search')), + 'category' => trim($form_state->getValue('category')), + ]; + $form_state->setRedirect($this->getRouteMatch()->getRouteName(), $this->getRouteMatch()->getRawParameters()->all(), [ + 'query' => $query , + ]); + } + + /** + * Resets the filter selection. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function resetForm(array &$form, FormStateInterface $form_state) { + $form_state->setRedirect($this->getRouteMatch()->getRouteName(), $this->getRouteMatch()->getRawParameters()->all()); + } + +} diff --git a/web/modules/webform/modules/webform_image_select/src/Plugin/WebformElement/WebformImageSelect.php b/web/modules/webform/modules/webform_image_select/src/Plugin/WebformElement/WebformImageSelect.php index 20a40a0d67..731d1dab0f 100644 --- a/web/modules/webform/modules/webform_image_select/src/Plugin/WebformElement/WebformImageSelect.php +++ b/web/modules/webform/modules/webform_image_select/src/Plugin/WebformElement/WebformImageSelect.php @@ -38,6 +38,11 @@ public function getDefaultProperties() { $properties['images'] = []; $properties['images_randomize'] = FALSE; $properties['show_label'] = FALSE; + $properties['filter'] = FALSE; + $properties['filter__placeholder'] = $this->t('Filter images by label'); + $properties['filter__singlular'] = $this->t('image'); + $properties['filter__plural'] = $this->t('images'); + $properties['filter__no_results'] = $this->t('No images found.'); return $properties; } @@ -45,7 +50,13 @@ public function getDefaultProperties() { * {@inheritdoc} */ public function getTranslatableProperties() { - return array_merge(parent::getTranslatableProperties(), ['images']); + return array_merge(parent::getTranslatableProperties(), [ + 'images', + 'filter__placeholder', + 'filter__singlular', + 'filter__plural', + 'filter__no_results', + ]); } /** @@ -53,9 +64,7 @@ public function getTranslatableProperties() { */ public function initialize(array &$element) { // Set element images. - if (isset($element['#images'])) { - $element['#images'] = WebformImageSelectImages::getElementImages($element); - } + $element['#images'] = WebformImageSelectImages::getElementImages($element); WebformImageSelectElement::setOptions($element); @@ -228,6 +237,41 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('If checked, the image text will be displayed below each image.'), '#return_value' => TRUE, ]; + $form['options']['filter'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Include filter by label'), + '#description' => $this->t('If checked, users will be able search/filter images by their labels.'), + '#return_value' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="properties[show_label]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['options']['filter_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="properties[filter]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['options']['filter_container']['filter__placeholder'] = [ + '#type' => 'textfield', + '#title' => $this->t('Filter placeholder label'), + ]; + $form['options']['filter_container']['filter__singlular'] = [ + '#type' => 'textfield', + '#title' => $this->t('Filter single item label'), + ]; + $form['options']['filter_container']['filter__plural'] = [ + '#type' => 'textfield', + '#title' => $this->t('Filter plural items label'), + ]; + $form['options']['filter_container']['filter__no_results'] = [ + '#type' => 'textfield', + '#title' => $this->t('Filter no results label'), + ]; return $form; } diff --git a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementImagesTest.php b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementImagesTest.php index 1c1211ff33..0e5d2b8979 100644 --- a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementImagesTest.php +++ b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementImagesTest.php @@ -52,6 +52,14 @@ public function testElementOptions() { kitten_4: text: 'Cute Kitten 4' src: 'http://placekitten.com/270/200'"); + + // Check unique key validation with image src. + $edit = [ + 'webform_image_select_images[images][items][0][src]' => 'src01', + 'webform_image_select_images[images][items][1][src]' => 'src02', + ]; + $this->drupalPostForm('webform/test_element_images', $edit, t('Submit')); + $this->assertRaw("The <em class=\"placeholder\">Image value</em> '' is already in use. It must be unique."); } } diff --git a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementTest.php b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementTest.php index 1236f6de72..656373a514 100644 --- a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementTest.php +++ b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectElementTest.php @@ -23,16 +23,22 @@ class WebformImageSelectElementTest extends WebformElementTestBase { * Test webform image select element. */ public function testImageSelect() { - $this->drupalGet('webform/test_element_image_select'); + $this->drupalGet('/webform/test_element_image_select'); // Check rendering of image select with required. $this->assertRaw('<select data-drupal-selector="edit-image-select-default" data-images="{"kitten_1":{"text":"Cute Kitten 1","src":"http:\/\/placekitten.com\/220\/200"},"kitten_2":{"text":"Cute Kitten 2","src":"http:\/\/placekitten.com\/180\/200"},"kitten_3":{"text":"Cute Kitten 3","src":"http:\/\/placekitten.com\/130\/200"},"kitten_4":{"text":"Cute Kitten 4","src":"http:\/\/placekitten.com\/270\/200"}}" class="webform-image-select js-webform-image-select form-select required" id="edit-image-select-default" name="image_select_default" required="required" aria-required="true">'); // Check rendering of image select with limit. - $this->assertRaW('<select data-drupal-selector="edit-image-select-limit" data-limit="2" data-images="{"kitten_1":{"text":"Cute Kitten 1","src":"http:\/\/placekitten.com\/220\/200"},"kitten_2":{"text":"Cute Kitten 2","src":"http:\/\/placekitten.com\/180\/200"},"kitten_3":{"text":"Cute Kitten 3","src":"http:\/\/placekitten.com\/130\/200"},"kitten_4":{"text":"Cute Kitten 4","src":"http:\/\/placekitten.com\/270\/200"}}" class="webform-image-select js-webform-image-select form-select" multiple="multiple" name="image_select_limit[]" id="edit-image-select-limit">'); + $this->assertRaw('<select data-drupal-selector="edit-image-select-limit" data-limit="2" data-images="{"kitten_1":{"text":"Cute Kitten 1","src":"http:\/\/placekitten.com\/220\/200"},"kitten_2":{"text":"Cute Kitten 2","src":"http:\/\/placekitten.com\/180\/200"},"kitten_3":{"text":"Cute Kitten 3","src":"http:\/\/placekitten.com\/130\/200"},"kitten_4":{"text":"Cute Kitten 4","src":"http:\/\/placekitten.com\/270\/200"}}" class="webform-image-select js-webform-image-select form-select" multiple="multiple" name="image_select_limit[]" id="edit-image-select-limit">'); // Check rendering of image select with HTML markup and XSS test. - $this->assertRaW('<select data-drupal-selector="edit-image-select-html" data-show-label="data-show-label" data-images="{"\u003C1\u003E":{"text":"Cute Kitten 1","src":"http:\/\/placekitten.com\/220\/200"},"\u00222\u0022":{"text":"Cute \u003Cem\u003EKitten\u003C\/em\u003E 2","src":"http:\/\/placekitten.com\/180\/200"},"\u00263":{"text":"Cute Kitten 3","src":"http:\/\/placekitten.com\/130\/200"},"4":{"text":"Cute Kitten 4 alert(\u0022XSS\u0022);","src":"http:\/\/placekitten.com\/270\/200"}}" class="webform-image-select js-webform-image-select form-select" id="edit-image-select-html" name="image_select_html"><option value="" selected="selected">- None -</option><option value="<1>">'); + $this->assertRaw('<select data-drupal-selector="edit-image-select-html" data-show-label="data-show-label" data-images="{"\u003C1\u003E":{"text":"Cute Kitten 1","src":"http:\/\/placekitten.com\/220\/200"},"\u00222\u0022":{"text":"Cute \u003Cem\u003EKitten\u003C\/em\u003E 2","src":"http:\/\/placekitten.com\/180\/200"},"\u00263":{"text":"Cute Kitten 3","src":"http:\/\/placekitten.com\/130\/200"},"4":{"text":"Cute Kitten 4 alert(\u0022XSS\u0022);","src":"http:\/\/placekitten.com\/270\/200"}}" class="webform-image-select js-webform-image-select form-select" id="edit-image-select-html" name="image_select_html"><option value="" selected="selected">- None -</option><option value="<1>">'); + + // Check rendering with filter. + $this->assertRaw('<input class="webform-form-filter-text form-search" data-focus="false" data-item-singlular="animal" data-item-plural="animals" data-summary=".js-image-select-filter-custom-filter .webform-image-select-summary" data-no-results=".js-image-select-filter-custom-filter .webform-image-select-no-results" data-element=".js-image-select-filter-custom-filter .thumbnails" data-source=".thumbnail p" data-parent="li" data-selected=".selected" title="Enter a keyword to filter by." type="search" id="edit-image-select-filter-custom-filter" name="image_select_filter_custom_filter" size="30" maxlength="128" placeholder="Find an animal" />'); + $this->assertRaw('<span class="field-suffix"><span class="webform-image-select-summary">8 animals</span>'); + $this->assertRaw('<div style="display:none" class="webform-image-select-no-results webform-message js-webform-message">'); + $this->assertRaw('No animals found.'); // Check preview. $webform = Webform::load('test_element_image_select'); diff --git a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectImagesTest.php b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectImagesTest.php index da175eed78..39ffb5ea40 100644 --- a/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectImagesTest.php +++ b/web/modules/webform/modules/webform_image_select/src/Tests/WebformImageSelectImagesTest.php @@ -21,21 +21,19 @@ class WebformImageSelectImagesTest extends WebformTestBase { */ public static $modules = ['webform_image_select', 'webform_image_select_test']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests webform image select images entity. */ public function testWebformImageSelectImages() { - $this->drupalLogin($this->normalUser); + $normal_user = $this->drupalCreateUser(); + + $admin_user = $this->drupalCreateUser([ + 'administer webform', + ]); + + /**************************************************************************/ + + $this->drupalLogin($normal_user); // Check get element images. $kittens = Yaml::decode("kitten_1: @@ -94,22 +92,22 @@ public function testWebformImageSelectImages() { $this->assertFalse($webform_images->getImages()); // Check admin user access denied. - $this->drupalGet('admin/structure/webform/config/images/manage'); + $this->drupalGet('/admin/structure/webform/config/images/manage'); $this->assertResponse(403); - $this->drupalGet('admin/structure/webform/config/images/manage/add'); + $this->drupalGet('/admin/structure/webform/config/images/manage/add'); $this->assertResponse(403); - $this->drupalGet('admin/structure/webform/config/images/manage/animals/edit'); + $this->drupalGet('/admin/structure/webform/config/images/manage/animals/edit'); $this->assertResponse(403); // Check admin user access. - $this->drupalLogin($this->adminWebformUser); - $this->drupalGet('admin/structure/webform/config/images/manage'); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/structure/webform/config/images/manage'); $this->assertResponse(200); - $this->drupalGet('admin/structure/webform/config/images/manage/add'); + $this->drupalGet('/admin/structure/webform/config/images/manage/add'); $this->assertResponse(200); // Check image altered message. - $this->drupalGet('admin/structure/webform/config/images/manage/animals/edit'); + $this->drupalGet('/admin/structure/webform/config/images/manage/animals/edit'); $this->assertRaw('The <em class="placeholder">Cute Animals</em> images are being altered by the <em class="placeholder">Webform Image Select Test</em> module.'); // Check hook_webform_image_select_images_alter(). diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesAccessControlHandler.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesAccessControlHandler.php index a88c6b2280..17395f29dd 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesAccessControlHandler.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesAccessControlHandler.php @@ -18,7 +18,7 @@ class WebformImageSelectImagesAccessControlHandler extends EntityAccessControlHa * {@inheritdoc} */ public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { - return AccessResult::allowedIf($account->hasPermission('administer webform'))->cachePerPermissions(); + return AccessResult::allowedIfHasPermission($account, 'administer webform'); } } diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesDeleteForm.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesDeleteForm.php index 85458cc398..b777008bb4 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesDeleteForm.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesDeleteForm.php @@ -2,60 +2,68 @@ namespace Drupal\webform_image_select; -use Drupal\Core\Entity\EntityDeleteForm; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; -use Drupal\webform\Form\WebformDialogFormTrait; +use Drupal\webform\Form\WebformConfigEntityDeleteFormBase; /** * Provides a delete webform images select images form. */ -class WebformImageSelectImagesDeleteForm extends EntityDeleteForm { - - use WebformDialogFormTrait; +class WebformImageSelectImagesDeleteForm extends WebformConfigEntityDeleteFormBase { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove configuration'), + $this->t('Affect any elements which use these images'), + ], + ], + ]; + } + /** + * {@inheritdoc} + */ + public function getDetails() { /** @var \Drupal\webform\WebformOptionsInterface $webform_options */ $webform_options = $this->entity; /** @var \Drupal\webform_image_select\WebformImageSelectImagesStorageInterface $webform_images_storage */ $webform_images_storage = $this->entityTypeManager->getStorage('webform_image_select_images'); - // Display warning that options is used by webforms. - $t_args = ['%title' => $webform_options->label()]; + $t_args = [ + '%label' => $this->getEntity()->label(), + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + ]; + + $details = []; if ($used_by_webforms = $webform_images_storage->getUsedByWebforms($webform_options)) { - $form['used_by_composite_elements'] = [ - '#type' => 'webform_message', - '#message_message' => [ + $details['used_by_composite_elements'] = [ + 'title' => [ + '#markup' => $this->t('%label is used by the below webform(s).', $t_args), + ], + 'list' => [ '#theme' => 'item_list', - '#title' => $this->t('%title is used by the below webform(s).', $t_args), '#items' => $used_by_webforms, ], - '#message_type' => 'warning', - '#weight' => -100, ]; } - $form['confirm'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Yes, I want to delete these webform images.'), - '#required' => TRUE, - '#weight' => 10, - ]; - - return $this->buildDialogConfirmForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function getRedirectUrl() { - return Url::fromRoute('entity.webform_image_select_images.collection'); + if ($details) { + return [ + '#type' => 'details', + '#title' => $this->t('Webforms affected'), + ] + $details; + } + else { + return []; + } } } diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesForm.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesForm.php index 8cfd58b453..276ce39cb1 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesForm.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesForm.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; +use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\Utility\WebformArrayHelper; @@ -99,7 +100,7 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Images'), '#title_display' => 'invisible', '#empty_options' => 10, - '#add_more' => 10, + '#add_more_items' => 10, '#default_value' => $this->getImages(), ]; } @@ -120,13 +121,28 @@ public function form(array $form, FormStateInterface $form_state) { '%module_names' => WebformArrayHelper::toString($module_names), '@module' => new PluralTranslatableMarkup(count($module_names), $this->t('module'), $this->t('modules')), ]; - drupal_set_message($this->t('The %title images are being altered by the %module_names @module.', $t_args), 'warning'); + $this->messenger()->addWarning($this->t('The %title images are being altered by the %module_names @module.', $t_args)); } } return parent::form($form, $form_state); } + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + + // Open delete button in a modal dialog. + if (isset($actions['delete'])) { + $actions['delete']['#attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, $actions['delete']['#attributes']['class']); + WebformDialogHelper::attachLibraries($actions['delete']); + } + + return $actions; + } + /** * Get options. * @@ -154,7 +170,9 @@ public function save(array $form, FormStateInterface $form_state) { ]; $this->logger('webform_image_select')->notice('Images @label saved.', $context); - drupal_set_message($this->t('Images %label saved.', ['%label' => $images->label()])); + $this->messenger()->addStatus($this->t('Images %label saved.', [ + '%label' => $images->label(), + ])); $form_state->setRedirect('entity.webform_image_select_images.collection'); } diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesInterface.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesInterface.php index d42e17ed5e..2f4426889c 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesInterface.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesInterface.php @@ -34,7 +34,7 @@ public function getImages(); * A webform image select element. * * @return array - * An associative array of iamge. + * An associative array of images. */ public static function getElementImages(array &$element); diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesListBuilder.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesListBuilder.php index d554a7ca44..a43dc187f6 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesListBuilder.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesListBuilder.php @@ -4,9 +4,14 @@ use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Url; use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform_image_select\Entity\WebformImageSelectImages; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; /** * Defines a class to build a listing of webform image select images entities. @@ -15,29 +20,109 @@ */ class WebformImageSelectImagesListBuilder extends ConfigEntityListBuilder { + /** + * Search keys. + * + * @var string + */ + protected $keys; + + /** + * Search category. + * + * @var string + */ + protected $category; + + /** + * Constructs a new WebformImageSelectImagesListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, RequestStack $request_stack) { + parent::__construct($entity_type, $storage); + $this->request = $request_stack->getCurrentRequest(); + + $this->keys = $this->request->query->get('search'); + $this->category = $this->request->query->get('category'); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('request_stack') + ); + } + /** * {@inheritdoc} */ public function render() { + // Handler autocomplete redirect. + if ($this->keys && preg_match('#\(([^)]+)\)$#', $this->keys, $match)) { + if ($webform_images = $this->getStorage()->load($match[1])) { + return new RedirectResponse($webform_images->toUrl()->setAbsolute(TRUE)->toString()); + } + } + $build = []; + // Filter form. + $build['filter_form'] = $this->buildFilterForm(); + // Display info. - if ($total = $this->getStorage()->getQuery()->count()->execute()) { - $build['info'] = [ - '#markup' => $this->formatPlural($total, '@total images', '@total images', ['@total' => $total]), - '#prefix' => '<div>', - '#suffix' => '</div>', - ]; - } + $build['info'] = $this->buildInfo(); + // Table. $build += parent::render(); + $build['table']['#sticky'] = TRUE; + // Attachments. $build['#attached']['library'][] = 'webform/webform.tooltip'; $build['#attached']['library'][] = 'webform/webform.admin.dialog'; return $build; } + /** + * Build the filter form. + * + * @return array + * A render array representing the filter form. + */ + protected function buildFilterForm() { + $categories = $this->getStorage()->getCategories(); + return \Drupal::formBuilder()->getForm('\Drupal\webform_image_select\Form\WebformImageSelectImagesFilterForm', $this->keys, $this->category, $categories); + } + + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { + $total = $this->getQuery($this->keys, $this->category)->count()->execute(); + if (!$total) { + return []; + } + + return [ + '#markup' => $this->formatPlural($total, '@total images', '@total images', ['@total' => $total]), + '#prefix' => '<div>', + '#suffix' => '</div>', + ]; + } + /** * {@inheritdoc} */ @@ -88,13 +173,13 @@ public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { /** * Build images for a webform image select images entity. * - * @param \Drupal\webform_image_select\WebformImageSelectImagesInterface $webform_images + * @param \Drupal\webform_image_select\WebformImageSelectImagesInterface $entity * A webform image select images entity. * * @return array * Images for a webform image select images entity. */ - protected function buildImages(EntityInterface $entity) { + protected function buildImages(WebformImageSelectImagesInterface $entity) { $element = ['#images' => $entity->id()]; $images = WebformImageSelectImages::getElementImages($element); if (!$images) { @@ -137,7 +222,7 @@ protected function buildUsedBy(WebformImageSelectImagesInterface $webform_images '#type' => 'link', '#title' => $title, '#url' => Url::fromRoute('entity.webform.canonical', ['webform' => $id]), - '#suffix' => '</br>' + '#suffix' => '</br>', ]; } return [ @@ -146,4 +231,56 @@ protected function buildUsedBy(WebformImageSelectImagesInterface $webform_images ]; } + /** + * {@inheritdoc} + */ + public function buildOperations(EntityInterface $entity) { + return parent::buildOperations($entity) + [ + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + } + + /** + * Get the base entity query filtered by search and category. + * + * @param string $keys + * (optional) Search key. + * @param string $category + * (optional) Category. + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * An entity query. + */ + protected function getQuery($keys = '', $category = '') { + $query = $this->getStorage()->getQuery(); + + // Filter by key(word). + if ($keys) { + $or = $query->orConditionGroup() + ->condition('id', $keys, 'CONTAINS') + ->condition('title', $keys, 'CONTAINS') + ->condition('images', $keys, 'CONTAINS'); + $query->condition($or); + } + + // Filter by category. + if ($category) { + $query->condition('category', $category); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + protected function getEntityIds() { + $header = $this->buildHeader(); + $query = $this->getQuery($this->keys, $this->category); + $query->tableSort($header); + $query->pager($this->limit); + return $query->execute(); + } + } diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorage.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorage.php index 6295a4a7b4..3697288d86 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorage.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorage.php @@ -2,8 +2,8 @@ namespace Drupal\webform_image_select; -use Drupal\Component\Serialization\Yaml; use Drupal\Core\Config\Entity\ConfigEntityStorage; +use Drupal\Core\Serialization\Yaml; /** * Storage controller class for "webform_image_select_images" configuration entities. @@ -64,8 +64,8 @@ public function getUsedByWebforms(WebformImageSelectImagesInterface $webform_ima $config = $this->configFactory->get($webform_config_name); $element_data = Yaml::encode($config->get('elements')); if (preg_match_all('/images\'\: ([a-z_]+)/', $element_data, $matches)) { - $webform_id = $config->get('id'); - $webform_title = $config->get('title'); + $webform_id = $config->get('id'); + $webform_title = $config->get('title'); foreach ($matches[1] as $options_id) { $this->usedByWebforms[$options_id][$webform_id] = $webform_title; } diff --git a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorageInterface.php b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorageInterface.php index 5675d4b9b1..f4c910875e 100644 --- a/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorageInterface.php +++ b/web/modules/webform/modules/webform_image_select/src/WebformImageSelectImagesStorageInterface.php @@ -36,4 +36,5 @@ public function getImages(); * A list of webform that use the specified webform images. */ public function getUsedByWebforms(WebformImageSelectImagesInterface $webform_images); + } diff --git a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_image_select.yml b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_image_select.yml index 253db8c0a4..180f502a4d 100644 --- a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_image_select.yml +++ b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_image_select.yml @@ -6,8 +6,10 @@ dependencies: - webform_image_select_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_image_select title: 'Test: Element: Image' description: 'Test Image select element.' @@ -30,6 +32,23 @@ elements: | text: 'Cute Kitten 4' src: 'http://placekitten.com/270/200' '#required': true + image_select_randomize: + '#type': webform_image_select + '#title': image_select_randomize + '#images': + kitten_1: + text: 'Cute Kitten 1' + src: 'http://placekitten.com/220/200' + kitten_2: + text: 'Cute Kitten 2' + src: 'http://placekitten.com/180/200' + kitten_3: + text: 'Cute Kitten 3' + src: 'http://placekitten.com/130/200' + kitten_4: + text: 'Cute Kitten 4' + src: 'http://placekitten.com/270/200' + '#images_randomize': true image_select_multiple: '#type': webform_image_select '#title': image_select_multiple @@ -127,6 +146,23 @@ elements: | '#type': webform_image_select '#title': image_select_animals '#images': animals + image_select_filter: + '#type': webform_image_select + '#title': image_select_filter + '#images': animals + '#show_label': true + '#filter': true + image_select_filter_custom: + '#type': webform_image_select + '#title': image_select_filter_custom + '#multiple': true + '#images': animals + '#show_label': true + '#filter': true + '#filter__placeholder': 'Find an animal' + '#filter__singular': 'animal' + '#filter__plural': 'animals' + '#filter__no_results': 'No animals found.' css: '' javascript: '' settings: @@ -135,6 +171,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -142,6 +179,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -157,22 +195,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -183,6 +236,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -196,12 +250,16 @@ settings: confirmation_back: true confirmation_back_label: '' confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -250,4 +308,12 @@ access: roles: { } users: { } permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_images.yml b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_images.yml index 65313979d5..ea5c508b55 100644 --- a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_images.yml +++ b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/config/install/webform.webform.test_element_images.yml @@ -6,8 +6,10 @@ dependencies: - webform_image_select_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_images title: 'Test: Element: Images' description: 'Test webform image select images element.' @@ -42,12 +44,12 @@ elements: | '#open': true webform_image_select_element_images_entity: '#type': webform_image_select_element_images - '#title': 'webform_image_select_element_images_entity' + '#title': webform_image_select_element_images_entity '#required': true '#default_value': kittens webform_image_select_element_images_custom: '#type': webform_image_select_element_images - '#title': 'webform_image_select_element_images_custom' + '#title': webform_image_select_element_images_custom '#required': true '#default_value': kitten_1: @@ -70,6 +72,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -77,6 +80,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -92,22 +96,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -118,6 +137,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -131,12 +151,16 @@ settings: confirmation_back: true confirmation_back_label: '' confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -185,6 +209,14 @@ access: roles: { } users: { } permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/webform_image_select_test.info.yml b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/webform_image_select_test.info.yml index 5516da677b..c31de6c23f 100644 --- a/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/webform_image_select_test.info.yml +++ b/web/modules/webform/modules/webform_image_select/tests/modules/webform_image_select_test/webform_image_select_test.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform_image_select:webform_image_select' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_image_select/webform_image_select.info.yml b/web/modules/webform/modules/webform_image_select/webform_image_select.info.yml index 9965338da1..31bb622e9d 100644 --- a/web/modules/webform/modules/webform_image_select/webform_image_select.info.yml +++ b/web/modules/webform/modules/webform_image_select/webform_image_select.info.yml @@ -6,8 +6,8 @@ package: Webform dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_image_select/webform_image_select.module b/web/modules/webform/modules/webform_image_select/webform_image_select.module index 8506331900..cdec386dd6 100644 --- a/web/modules/webform/modules/webform_image_select/webform_image_select.module +++ b/web/modules/webform/modules/webform_image_select/webform_image_select.module @@ -6,6 +6,9 @@ */ use Drupal\Core\Url; +use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Form\FormStateInterface; /** * Implements hook_webform_libraries_info(). @@ -24,3 +27,57 @@ function webform_image_select_webform_libraries_info() { ]; return $libraries; } + +/** + * Implements hook_menu_local_tasks_alter(). + */ +function webform_image_select_menu_local_tasks_alter(&$data, $route_name) { + // Change config entities 'Translate *' tab to be just label 'Translate'. + if (isset($data['tabs'][0]["config_translation.local_tasks:entity.webform_image_select_images.config_translation_overview"]['#link']['title'])) { + $data['tabs'][0]["config_translation.local_tasks:entity.webform_image_select_images.config_translation_overview"]['#link']['title'] = t('Translate'); + } +} + +/******************************************************************************/ +// Lingotek integration. +/******************************************************************************/ + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function webform_image_select_form_config_translation_add_form_alter(&$form, FormStateInterface $form_state, $is_new = TRUE) { + // Manually apply YAML editor to text field that store YAML data. + // @see webform_form_config_translation_add_form_alter() + // @see _webform_form_config_translate_add_form_alter_yaml_element() + foreach ($form['config_names'] as $config_name => &$config_element) { + if (strpos($config_name, 'webform_image_select.webform_image_select_images.') === 0) { + _webform_form_config_translate_add_form_alter_yaml_element($config_element['images']); + } + } +} + +/** + * Implements hook_lingotek_config_entity_document_upload(). + */ +function webform_image_select_lingotek_config_entity_document_upload(array &$source_data, ConfigEntityInterface &$entity, &$url) { + switch ($entity->getEntityTypeId()) { + case 'webform_image_select_images'; + // Convert images YAML string to an associative array. + $source_data['images'] = Yaml::decode($source_data['images']); + break; + } +} + +/** + * Implements hook_lingotek_config_entity_translation_presave(). + */ +function webform_image_select_lingotek_config_entity_translation_presave(ConfigEntityInterface &$translation, $langcode, &$data) { + switch ($translation->getEntityTypeId()) { + case 'webform_image_select_images'; + /** @var \Drupal\webform_image_select\WebformImageSelectImagesInterface $translation */ + // Convert images associative array back to YAML string. + $translation->setImages($data['images']); + $data['images'] = Yaml::encode($data['images']); + break; + } +} diff --git a/web/modules/webform/modules/webform_image_select/webform_image_select.routing.yml b/web/modules/webform/modules/webform_image_select/webform_image_select.routing.yml index 6f092a709e..cccb2ea29b 100644 --- a/web/modules/webform/modules/webform_image_select/webform_image_select.routing.yml +++ b/web/modules/webform/modules/webform_image_select/webform_image_select.routing.yml @@ -6,6 +6,13 @@ entity.webform_image_select_images.collection: requirements: _permission: 'administer webform' +entity.webform_image_select_images.autocomplete: + path: '/admin/structure/webform/config/images/autocomplete' + defaults: + _controller: '\Drupal\webform_image_select\Controller\WebformImageSelectImagesController::autocomplete' + requirements: + _permission: 'administer webform' + entity.webform_image_select_images.add_form: path: '/admin/structure/webform/config/images/manage/add' defaults: diff --git a/web/modules/webform/modules/webform_node/config/optional/field.field.node.webform.webform.yml b/web/modules/webform/modules/webform_node/config/optional/field.field.node.webform.webform.yml index 96fd427217..ab21c8fb12 100644 --- a/web/modules/webform/modules/webform_node/config/optional/field.field.node.webform.webform.yml +++ b/web/modules/webform/modules/webform_node/config/optional/field.field.node.webform.webform.yml @@ -14,13 +14,14 @@ label: Webform description: 'Select the webform that you would like to attach to this node.' required: false translatable: false -default_value: { } +default_value: + - default_data: '' + status: open + open: '' + close: '' + target_uuid: '' default_value_callback: '' settings: - default_data: '' - status: open - open: '' - close: '' handler: 'default:webform' handler_settings: { } field_type: webform diff --git a/web/modules/webform/modules/webform_node/css/webform_node.entity_references.css b/web/modules/webform/modules/webform_node/css/webform_node.entity_references.css index fa4dae9eda..cbc3861d36 100644 --- a/web/modules/webform/modules/webform_node/css/webform_node.entity_references.css +++ b/web/modules/webform/modules/webform_node/css/webform_node.entity_references.css @@ -15,7 +15,7 @@ } /** - * If dropbutton is in a contextual region we need to move it to the left to + * If dropbutton is in a contextual region we need to move it to the left to * prevent it from overlapping with the pencil icon. */ .js .contextual-region .webform-node-entity-references .dropbutton-widget { diff --git a/web/modules/webform/modules/webform_node/src/Access/WebformNodeAccess.php b/web/modules/webform/modules/webform_node/src/Access/WebformNodeAccess.php index 405bc2429e..0bc7fecfc9 100644 --- a/web/modules/webform/modules/webform_node/src/Access/WebformNodeAccess.php +++ b/web/modules/webform/modules/webform_node/src/Access/WebformNodeAccess.php @@ -14,6 +14,34 @@ */ class WebformNodeAccess { + /** + * Check whether the user can access a node's webform drafts. + * + * @param string $operation + * Operation being performed. + * @param string $entity_access + * Entity access rule that needs to be checked. + * @param \Drupal\node\NodeInterface $node + * A node. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkWebformDraftsAccess($operation, $entity_access, NodeInterface $node, AccountInterface $account) { + $access_result = static::checkAccess($operation, $entity_access, $node, NULL, $account); + if ($access_result->isAllowed()) { + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ + $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); + $webform = $entity_reference_manager->getWebform($node); + return WebformEntityAccess::checkDraftsAccess($webform, $node); + } + else { + return $access_result; + } + } + /** * Check whether the user can access a node's webform results. * @@ -121,6 +149,12 @@ public static function checkWebformSubmissionAccess($operation, $entity_access, case 'webform_submission_resend': return WebformSubmissionAccess::checkResendAccess($webform_submission, $account); + + case 'webform_submission_duplicate': + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ + $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); + $webform = $entity_reference_manager->getWebform($node); + return WebformEntityAccess::checkWebformSettingValue($webform, 'submission_user_duplicate', TRUE); } return $access_result; @@ -154,8 +188,11 @@ protected static function checkAccess($operation, $entity_access, NodeInterface } // Check that the webform submission was created via the webform node. - if ($webform_submission && $webform_submission->getSourceEntity() != $node) { - return AccessResult::forbidden(); + if ($webform_submission) { + $source_node = $webform_submission->getSourceEntity(); + if (!$source_node || $source_node->id() !== $node->id()) { + return AccessResult::forbidden(); + } } // Check the node operation. diff --git a/web/modules/webform/modules/webform_node/src/Controller/WebformNodeReferencesListController.php b/web/modules/webform/modules/webform_node/src/Controller/WebformNodeReferencesListController.php index 9528cd814d..f0a04a17b7 100644 --- a/web/modules/webform/modules/webform_node/src/Controller/WebformNodeReferencesListController.php +++ b/web/modules/webform/modules/webform_node/src/Controller/WebformNodeReferencesListController.php @@ -113,18 +113,18 @@ public function listing(WebformInterface $webform) { * The node type storage class. * @param \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $field_config_storage * The field config storage class. - * @param \Drupal\webform\WebformSubmissionStorageInterface $webform_submsision_storage + * @param \Drupal\webform\WebformSubmissionStorageInterface $webform_submission_storage * The webform submission storage class. * @param \Drupal\webform\WebformEntityReferenceManagerInterface $webform_entity_reference_manager * The webform entity reference manager. */ - public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, ConfigEntityStorageInterface $node_type_storage, ConfigEntityStorageInterface $field_config_storage, WebformSubmissionStorageInterface $webform_submsision_storage, WebformEntityReferenceManagerInterface $webform_entity_reference_manager) { + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, ConfigEntityStorageInterface $node_type_storage, ConfigEntityStorageInterface $field_config_storage, WebformSubmissionStorageInterface $webform_submission_storage, WebformEntityReferenceManagerInterface $webform_entity_reference_manager) { parent::__construct($entity_type, $storage); $this->dateFormatter = $date_formatter; $this->nodeTypeStorage = $node_type_storage; $this->fieldConfigStorage = $field_config_storage; - $this->submissionStorage = $webform_submsision_storage; + $this->submissionStorage = $webform_submission_storage; $this->webformEntityReferenceManager = $webform_entity_reference_manager; $this->nodeTypes = []; @@ -208,17 +208,10 @@ public function buildHeader() { 'data' => $this->t('Webform status'), 'class' => [RESPONSIVE_PRIORITY_LOW], ]; - $header['results_total'] = [ - 'data' => $this->t('Total Results'), + $header['results'] = [ + 'data' => $this->t('Results'), 'class' => [RESPONSIVE_PRIORITY_MEDIUM], ]; - $header['results_operations'] = [ - 'data' => $this->t('Operations'), - 'class' => [RESPONSIVE_PRIORITY_MEDIUM], - ]; - $header['operations'] = [ - 'data' => '', - ]; return $header + parent::buildHeader(); } @@ -240,13 +233,29 @@ public function buildRow(EntityInterface $entity) { $row['changed'] = $this->dateFormatter->format($entity->getChangedTime(), 'short'); $row['node_status'] = $entity->isPublished() ? $this->t('Published') : $this->t('Not published'); $row['webform_status'] = $this->getWebformStatus($entity); - $row['results_total'] = $this->submissionStorage->getTotal($this->webform, $entity); - $row['results_operations']['data'] = [ - '#type' => 'operations', - '#links' => $this->getDefaultOperations($entity, 'results'), - '#prefix' => '<div class="webform-dropbutton">', - '#suffix' => '</div>', - ]; + + $result_total = $this->submissionStorage->getTotal($this->webform, $entity); + $results_access = $entity->access('submission_view_any'); + $results_disabled = $this->webform->isResultsDisabled(); + if ($results_disabled || !$results_access) { + $row['results'] = $result_total; + } + else { + $route_parameters = [ + 'node' => $entity->id(), + ]; + $row['results'] = [ + 'data' => [ + '#type' => 'link', + '#title' => $result_total, + '#attributes' => [ + 'aria-label' => $this->formatPlural($result_total, '@count result for @label', '@count results for @label', ['@label' => $entity->label()]), + ], + '#url' => Url::fromRoute('entity.node.webform.results_submissions', $route_parameters), + ], + ]; + } + $row['operations']['data'] = $this->buildOperations($entity); return $row + parent::buildRow($entity); } @@ -297,38 +306,48 @@ protected function getWebformStatus(EntityInterface $entity) { /** * {@inheritdoc} */ - public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { + public function buildOperations(EntityInterface $entity) { + $build = [ + '#type' => 'operations', + '#links' => $this->getOperations($entity), + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + + return $build; + } + + /** + * {@inheritdoc} + */ + public function getDefaultOperations(EntityInterface $entity) { $route_parameters = [ 'node' => $entity->id(), ]; - if ($type == 'results') { - $operations = []; - if ($entity->access('submission_view_any')) { - $operations['submissions'] = [ - 'title' => $this->t('Submissions'), - 'url' => Url::fromRoute('entity.node.webform.results_submissions', $route_parameters), - ]; - $operations['export'] = [ - 'title' => $this->t('Download'), - 'url' => Url::fromRoute('entity.node.webform.results_export', $route_parameters), - ]; - } - if ($entity->access('submission_delete_any')) { - $operations['clear'] = [ - 'title' => $this->t('Clear'), - 'url' => Url::fromRoute('entity.node.webform.results_clear', $route_parameters), - ]; - } + $operations = []; + if ($entity->access('update')) { + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => $this->ensureDestination($entity->toUrl('edit-form')), + ]; } - else { - $operations = parent::getDefaultOperations($entity); - if ($entity->access('submission_update_any')) { - $operations['test'] = [ - 'title' => $this->t('Test'), - 'weight' => 21, - 'url' => Url::fromRoute('entity.node.webform.test_form', $route_parameters), - ]; - } + if ($entity->access('view')) { + $operations['view'] = [ + 'title' => $this->t('View'), + 'url' => $this->ensureDestination($entity->toUrl('canonical')), + ]; + } + if ($entity->access('submission_view_any') && !$this->webform->isResultsDisabled()) { + $operations['results'] = [ + 'title' => $this->t('Results'), + 'url' => Url::fromRoute('entity.node.webform.results_submissions', $route_parameters), + ]; + } + if ($entity->access('delete')) { + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => $this->ensureDestination($entity->toUrl('delete-form')), + ]; } return $operations; } @@ -339,6 +358,8 @@ public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { public function render() { $build = parent::render(); + $build['table']['#sticky'] = TRUE; + // Customize the empty message. $build['table']['#empty'] = $this->t('There are no webform node references.'); @@ -366,7 +387,6 @@ public function render() { } $build['#attached']['library'][] = 'webform_node/webform_node.references'; - return $build; } diff --git a/web/modules/webform/modules/webform_node/src/Controller/WebformNodeSubmissionLogController.php b/web/modules/webform/modules/webform_node/src/Controller/WebformNodeSubmissionLogController.php deleted file mode 100644 index d20a4b789b..0000000000 --- a/web/modules/webform/modules/webform_node/src/Controller/WebformNodeSubmissionLogController.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -namespace Drupal\webform_node\Controller; - -use Drupal\Core\Entity\EntityInterface; -use Drupal\webform\WebformInterface; -use Drupal\webform\WebformSubmissionInterface; -use Drupal\webform\Controller\WebformSubmissionLogController; - -/** - * Returns responses for webform submission log routes. - */ -class WebformNodeSubmissionLogController extends WebformSubmissionLogController { - - /** - * Wrapper that allows the $node to be used as $source_entity. - */ - public function overview(WebformInterface $webform = NULL, WebformSubmissionInterface $webform_submission = NULL, EntityInterface $node = NULL) { - return parent::overview($webform, $webform_submission, $node); - } - -} diff --git a/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessPermissionsTest.php b/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessPermissionsTest.php new file mode 100644 index 0000000000..a41569fa70 --- /dev/null +++ b/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessPermissionsTest.php @@ -0,0 +1,123 @@ +<?php + +namespace Drupal\webform_node\Tests\Access; + +use Drupal\webform\Entity\Webform; +use Drupal\webform_node\Tests\WebformNodeTestBase; + +/** + * Tests for webform node access permissions. + * + * @group WebformNode + */ +class WebformNodeAccessPermissionsTest extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_node']; + + /** + * Tests webform node access permissions. + * + * @see \Drupal\webform\Tests\Access\WebformAccessPermissionTest::testWebformSubmissionAccessPermissions + */ + public function testAccessPermissions() { + global $base_path; + + // Create webform node that references the contact webform. + $webform = Webform::load('contact'); + $node = $this->createWebformNode('contact'); + $nid = $node->id(); + + // Own webform submission user. + $submission_own_account = $this->drupalCreateUser([ + 'view own webform submission', + 'edit own webform submission', + 'delete own webform submission', + 'access webform submission user', + ]); + + // Any webform submission user. + $submission_any_account = $this->drupalCreateUser([ + 'view any webform submission', + 'edit any webform submission', + 'delete any webform submission', + ]); + + /**************************************************************************/ + // Own submission permissions (authenticated). + /**************************************************************************/ + + $this->drupalLogin($submission_own_account); + + $edit = ['subject' => '{subject}', 'message' => '{message}']; + $sid_1 = $this->postNodeSubmission($node, $edit); + + // Check view own previous submission message. + $this->drupalGet("node/{$nid}"); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}node/{$nid}/webform/submissions/{$sid_1}\">View your previous submission</a>."); + + // Check 'view own submission' permission. + $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}"); + $this->assertResponse(200); + + // Check 'edit own submission' permission. + $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}/edit"); + $this->assertResponse(200); + + // Check 'delete own submission' permission. + $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}/delete"); + $this->assertResponse(200); + + $sid_2 = $this->postNodeSubmission($node, $edit); + + // Check view own previous submissions message. + $this->drupalGet("node/{$nid}"); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}node/{$nid}/webform/submissions\">View your previous submissions</a>"); + + // Check view own previous submissions. + $this->drupalGet("node/{$nid}/webform/submissions"); + $this->assertResponse(200); + $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submissions/{$sid_1}"); + $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submissions/{$sid_2}"); + + // Check submission user duplicate returns access denied. + $this->drupalGet("node/{$nid}/webform/submissions/{$sid_2}/duplicate"); + $this->assertResponse(403); + + // Enable submission user duplicate. + $webform->setSetting('submission_user_duplicate', TRUE); + $webform->save(); + + // Check submission user duplicate returns access allows. + $this->drupalGet("node/{$nid}/webform/submissions/{$sid_2}/duplicate"); + $this->assertResponse(200); + + // Check webform results access denied. + $this->drupalGet("node/{$nid}/webform/results/submissions"); + $this->assertResponse(403); + + /**************************************************************************/ + // Any submission permissions. + /**************************************************************************/ + + // Login as any user. + $this->drupalLogin($submission_any_account); + + // Check webform results access allowed. + $this->drupalGet("node/{$nid}/webform/results/submissions"); + $this->assertResponse(200); + $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submission/{$sid_1}"); + $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submission/{$sid_2}"); + + // Check webform submission access allowed. + $this->drupalGet("node/{$nid}/webform/submission/{$sid_1}"); + $this->assertResponse(200); + } + +} diff --git a/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessRulesTest.php b/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessRulesTest.php new file mode 100644 index 0000000000..d9a18794c7 --- /dev/null +++ b/web/modules/webform/modules/webform_node/src/Tests/Access/WebformNodeAccessRulesTest.php @@ -0,0 +1,123 @@ +<?php + +namespace Drupal\webform_node\Tests\Access; + +use Drupal\webform\Entity\Webform; +use Drupal\webform_node\Tests\WebformNodeTestBase; + +/** + * Tests for webform node access rules. + * + * @group WebformNode + */ +class WebformNodeAccessRulesTest extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_node']; + + /** + * Tests webform node access rules. + * + * @see \Drupal\webform\Tests\WebformEntityAccessControlsTest::testAccessRules + */ + public function testAccessRules() { + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + $default_access_rules = $access_rules_manager->getDefaultAccessRules(); + + $webform = Webform::load('contact'); + + $node = $this->createWebformNode('contact'); + $nid = $node->id(); + + $account = $this->drupalCreateUser(['access content']); + $rid = $account->getRoles(TRUE)[0]; + $uid = $account->id(); + + /**************************************************************************/ + + // Log in normal user and get their rid. + $this->drupalLogin($account); + + // Add one submission to the Webform node. + $edit = [ + 'name' => '{name}', + 'email' => 'example@example.com', + 'subject' => '{subject}', + 'message' => '{message', + ]; + $sid = $this->postNodeSubmission($node, $edit); + + // Check create authenticated/anonymous access. + $webform->setAccessRules($default_access_rules)->save(); + $this->drupalGet('/node/' . $node->id()); + $this->assertFieldByName('name', $account->getAccountName()); + $this->assertFieldByName('email', $account->getEmail()); + + $access_rules = [ + 'create' => [ + 'roles' => [], + 'users' => [], + ], + ] + $default_access_rules; + $webform->setAccessRules($access_rules)->save(); + + // Check no access. + $this->drupalGet('/node/' . $node->id()); + $this->assertNoFieldByName('name', $account->getAccountName()); + $this->assertNoFieldByName('email', $account->getEmail()); + + $any_tests = [ + 'node/{node}/webform/results/submissions' => 'view_any', + 'node/{node}/webform/results/download' => 'view_any', + 'node/{node}/webform/results/clear' => 'purge_any', + 'node/{node}/webform/submission/{webform_submission}' => 'view_any', + 'node/{node}/webform/submission/{webform_submission}/text' => 'view_any', + 'node/{node}/webform/submission/{webform_submission}/yaml' => 'view_any', + 'node/{node}/webform/submission/{webform_submission}/edit' => 'update_any', + 'node/{node}/webform/submission/{webform_submission}/delete' => 'delete_any', + ]; + + // Check that all the test paths are access denied for authenticated. + foreach ($any_tests as $path => $permission) { + $path = str_replace('{node}', $nid, $path); + $path = str_replace('{webform_submission}', $sid, $path); + + $this->drupalGet($path); + $this->assertResponse(403, 'Webform returns access denied'); + } + + // Check access rules by role and user id. + foreach ($any_tests as $path => $permission) { + $path = str_replace('{node}', $nid, $path); + $path = str_replace('{webform_submission}', $sid, $path); + + // Check access rule via role. + $access_rules = [ + $permission => [ + 'roles' => [$rid], + 'users' => [], + ], + ] + $default_access_rules; + $webform->setAccessRules($access_rules)->save(); + $this->drupalGet($path); + $this->assertResponse(200, 'Webform allows access via role access rules'); + + // Check access rule via role. + $access_rules = [ + $permission => [ + 'roles' => [], + 'users' => [$uid], + ], + ] + $default_access_rules; + $webform->setAccessRules($access_rules)->save(); + $this->drupalGet($path); + $this->assertResponse(200, 'Webform allows access via user access rules'); + } + } + +} diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php deleted file mode 100644 index 2b8b075735..0000000000 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php +++ /dev/null @@ -1,197 +0,0 @@ -<?php - -namespace Drupal\webform_node\Tests; - -use Drupal\webform\Entity\Webform; - -/** - * Tests for webform node access rules. - * - * @group WebformNode - */ -class WebformNodeAccessTest extends WebformNodeTestBase { - - /** - * Modules to enable. - * - * @var array - */ - public static $modules = ['webform', 'webform_node']; - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - - /** - * Tests webform node access perimissions. - * - * @see \Drupal\webform\Tests\WebformSubmissionAccessTest::testWebformSubmissionAccessPermissions - */ - public function testAccessPermissions() { - global $base_path; - - // Create webform node that references the contact webform. - $node = $this->createWebformNode('contact'); - $nid = $node->id(); - - /**************************************************************************/ - // Own submission permissions (authenticated). - /**************************************************************************/ - - $this->drupalLogin($this->ownWebformSubmissionUser); - - $edit = ['subject' => '{subject}', 'message' => '{message}']; - $sid_1 = $this->postNodeSubmission($node, $edit); - - // Check view own previous submission message. - $this->drupalGet("node/{$nid}"); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}node/{$nid}/webform/submissions/{$sid_1}\">View your previous submission</a>."); - - // Check 'view own submission' permission. - $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}"); - $this->assertResponse(200); - - // Check 'edit own submission' permission. - $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}/edit"); - $this->assertResponse(200); - - // Check 'delete own submission' permission. - $this->drupalGet("node/{$nid}/webform/submissions/{$sid_1}/delete"); - $this->assertResponse(200); - - $sid_2 = $this->postNodeSubmission($node, $edit); - - // Check view own previous submissions message. - $this->drupalGet("node/{$nid}"); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}node/{$nid}/webform/submissions\">View your previous submissions</a>"); - - // Check view own previous submissions. - $this->drupalGet("node/{$nid}/webform/submissions"); - $this->assertResponse(200); - $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submissions/{$sid_1}"); - $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submissions/{$sid_2}"); - - // Check webform results access denied. - $this->drupalGet("node/{$nid}/webform/results/submissions"); - $this->assertResponse(403); - - /**************************************************************************/ - // Any submission permissions. - /**************************************************************************/ - - // Login as any user. - $this->drupalLogin($this->anyWebformSubmissionUser); - - // Check webform results access allowed. - $this->drupalGet("node/{$nid}/webform/results/submissions"); - $this->assertResponse(200); - $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submission/{$sid_1}"); - $this->assertLinkByHref("{$base_path}node/{$nid}/webform/submission/{$sid_2}"); - - // Check webform submission access allowed. - $this->drupalGet("node/{$nid}/webform/submission/{$sid_1}"); - $this->assertResponse(200); - } - - /** - * Tests webform node access rules. - * - * @see \Drupal\webform\Tests\WebformEntityAccessTest::testAccessRules - */ - public function testAccessRules() { - $webform = Webform::load('contact'); - $node = $this->createWebformNode('contact'); - $nid = $node->id(); - - // Log in normal user and get their rid. - $this->drupalLogin($this->normalUser); - $roles = $this->normalUser->getRoles(TRUE); - $rid = reset($roles); - $uid = $this->normalUser->id(); - - // Add one submission to the Webform node. - $edit = [ - 'name' => '{name}', - 'email' => 'example@example.com', - 'subject' => '{subject}', - 'message' => '{message', - ]; - $sid = $this->postNodeSubmission($node, $edit); - - // Check create authenticated/anonymous access. - $webform->setAccessRules(Webform::getDefaultAccessRules())->save(); - $this->drupalGet('node/' . $node->id()); - $this->assertFieldByName('name', $this->normalUser->getAccountName()); - $this->assertFieldByName('email', $this->normalUser->getEmail()); - - $access_rules = [ - 'create' => [ - 'roles' => [], - 'users' => [], - ], - ] + Webform::getDefaultAccessRules(); - $webform->setAccessRules($access_rules)->save(); - - // Check no access. - $this->drupalGet('node/' . $node->id()); - $this->assertNoFieldByName('name', $this->normalUser->getAccountName()); - $this->assertNoFieldByName('email', $this->normalUser->getEmail()); - - $any_tests = [ - 'node/{node}/webform/results/submissions' => 'view_any', - 'node/{node}/webform/results/download' => 'view_any', - 'node/{node}/webform/results/clear' => 'purge_any', - 'node/{node}/webform/submission/{webform_submission}' => 'view_any', - 'node/{node}/webform/submission/{webform_submission}/text' => 'view_any', - 'node/{node}/webform/submission/{webform_submission}/yaml' => 'view_any', - 'node/{node}/webform/submission/{webform_submission}/edit' => 'update_any', - 'node/{node}/webform/submission/{webform_submission}/delete' => 'delete_any', - ]; - - // Check that all the test paths are access denied for authenticated. - foreach ($any_tests as $path => $permission) { - $path = str_replace('{node}', $nid, $path); - $path = str_replace('{webform_submission}', $sid, $path); - - $this->drupalGet($path); - $this->assertResponse(403, 'Webform returns access denied'); - } - - // Check access rules by role and user id. - foreach ($any_tests as $path => $permission) { - $path = str_replace('{node}', $nid, $path); - $path = str_replace('{webform_submission}', $sid, $path); - - // Check access rule via role. - $access_rules = [ - $permission => [ - 'roles' => [$rid], - 'users' => [], - ], - ] + Webform::getDefaultAccessRules(); - $webform->setAccessRules($access_rules)->save(); - $this->drupalGet($path); - $this->assertResponse(200, 'Webform allows access via role access rules'); - - // Check access rule via role. - $access_rules = [ - $permission => [ - 'roles' => [], - 'users' => [$uid], - ], - ] + Webform::getDefaultAccessRules(); - $webform->setAccessRules($access_rules)->save(); - $this->drupalGet($path); - $this->assertResponse(200, 'Webform allows access via user access rules'); - } - } - -} diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeEntityReferenceTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeEntityReferenceTest.php index abb91fff2c..ee8cb51213 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeEntityReferenceTest.php +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeEntityReferenceTest.php @@ -34,7 +34,7 @@ public function testEntityReference() { $this->drupalLogin($this->rootUser); // Check that both webform A & B are being displayed. - $this->drupalGet('node/1'); + $this->drupalGet('/node/1'); $this->assertRaw('webform_test_multiple_a'); $this->assertRaw('textfield_a'); $this->assertRaw('webform_test_multiple_b'); @@ -42,59 +42,69 @@ public function testEntityReference() { /**************************************************************************/ - // Check test form A (A is the default because alphabetically sorting). - $this->drupalGet('node/1/webform/test'); - $this->assertRaw('textfield_a'); - $this->assertNoRaw('textfield_b'); - - // Check user data is NULL. - $this->assertNull($user_data->get('webform_node', $this->rootUser->id(), 1)); - - /**************************************************************************/ - - // Select webform B. - $this->drupalGet('node/1/webform/test'); - $this->clickLink('Test: Webform Node Multiple B'); - - // Check user data is set to webform B. - $this->assertEqual(['target_id' => 'webform_node_test_multiple_b'], $user_data->get('webform_node', $this->rootUser->id(), 1)); - - // Check test webform B. - $this->drupalGet('node/1/webform/test'); + // Check test form B (B is the default because its weight is -1). + $this->drupalGet('/node/1/webform/test'); $this->assertNoRaw('textfield_a'); $this->assertRaw('textfield_b'); // Check result webform B. - $this->drupalGet('node/1/webform/results/submissions'); + $this->drupalGet('/node/1/webform/results/submissions'); $this->assertNoRaw('textfield_a'); $this->assertRaw('textfield_b'); // Check export webform B. - $this->drupalGet('node/1/webform/results/download'); + $this->drupalGet('/node/1/webform/results/download'); $this->assertNoRaw('textfield_a'); $this->assertRaw('textfield_b'); + // Check user data is NULL. + $this->assertNull($user_data->get('webform_node', $this->rootUser->id(), 1)); + + /**************************************************************************/ + + // Select webform A. + $this->drupalGet('/node/1/webform/test'); + $this->clickLink('Test: Webform Node Multiple A'); + + // Check user data is set to webform A. + $this->assertEqual(['target_id' => 'webform_node_test_multiple_a'], $user_data->get('webform_node', $this->rootUser->id(), 1)); + + // Check test webform A. + $this->drupalGet('/node/1/webform/test'); + $this->assertRaw('textfield_a'); + $this->assertNoRaw('textfield_b'); + + // Check result webform A. + $this->drupalGet('/node/1/webform/results/submissions'); + $this->assertRaw('textfield_a'); + $this->assertNoRaw('textfield_b'); + + // Check export webform A. + $this->drupalGet('/node/1/webform/results/download'); + $this->assertRaw('textfield_a'); + $this->assertNoRaw('textfield_b'); + /**************************************************************************/ // Select webform A. - $this->drupalGet('node/1/webform/test'); + $this->drupalGet('/node/1/webform/test'); $this->clickLink('Test: Webform Node Multiple A'); // Check user data is set to webform A. $this->assertEqual(['target_id' => 'webform_node_test_multiple_a'], $user_data->get('webform_node', $this->rootUser->id(), 1)); // Check test webform A. - $this->drupalGet('node/1/webform/test'); + $this->drupalGet('/node/1/webform/test'); $this->assertRaw('textfield_a'); $this->assertNoRaw('textfield_b'); // Check result webform A. - $this->drupalGet('node/1/webform/results/submissions'); + $this->drupalGet('/node/1/webform/results/submissions'); $this->assertRaw('textfield_a'); $this->assertNoRaw('textfield_b'); // Check export webform A. - $this->drupalGet('node/1/webform/results/download'); + $this->drupalGet('/node/1/webform/results/download'); $this->assertRaw('textfield_a'); $this->assertNoRaw('textfield_b'); diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeReferencesTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeReferencesTest.php index 24060c330a..3d186f3541 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeReferencesTest.php +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeReferencesTest.php @@ -26,7 +26,7 @@ public function testReferences() { $this->drupalPlaceBlock('help_block'); // Check references tab's empty message. - $this->drupalGet('admin/structure/webform/manage/contact/references'); + $this->drupalGet('/admin/structure/webform/manage/contact/references'); $this->assertRaw('There are no webform node references.'); // Create webform node. @@ -34,7 +34,7 @@ public function testReferences() { $node->webform->target_id = 'contact'; $node->save(); - $this->drupalGet('admin/structure/webform/manage/contact/references'); + $this->drupalGet('/admin/structure/webform/manage/contact/references'); // Check references tab does not include empty message. $this->assertNoRaw('There are no webform node references.'); @@ -46,12 +46,12 @@ public function testReferences() { $this->assertRaw('<li><a href="' . $base_path . 'node/add/webform?webform_id=contact" class="button button-action" data-drupal-link-query="{"webform_id":"contact"}" data-drupal-link-system-path="node/add/webform">Add Webform</a></li>'); // Check node with prepopulated webform. - $this->drupalGet('node/add/webform', ['query' => ['webform_id' => 'contact']]); + $this->drupalGet('/node/add/webform', ['query' => ['webform_id' => 'contact']]); $this->assertFieldByName('webform[0][target_id]', 'contact'); // Check node without prepopulated webform warning. - $this->drupalGet('node/add/webform'); - $this->assertRaw('Webforms must first be <a href="' . $base_path . 'admin/structure/webform">created</a> before referencing them in the below form.'); + $this->drupalGet('/node/add/webform'); + $this->assertRaw('Webforms must first be <a href="' . $base_path . 'admin/structure/webform">created</a> before referencing them.'); } } diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php index ca66359a1f..17e234e23b 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php @@ -2,6 +2,7 @@ namespace Drupal\webform_node\Tests; +use Drupal\Component\Utility\Html; use Drupal\Core\Url; use Drupal\webform\Entity\Webform; use Drupal\webform\WebformInterface; @@ -34,10 +35,20 @@ public function setUp() { * Tests webform node results. */ public function testResults() { + $normal_user = $this->drupalCreateUser(); + + $admin_user = $this->drupalCreateUser([ + 'administer webform', + ]); + + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); - $this->createUsers(); + /**************************************************************************/ $webform = Webform::load('contact'); @@ -47,8 +58,8 @@ public function testResults() { /* Webform entity reference */ // Check access denied to webform results. - $this->drupalLogin($this->adminSubmissionUser); - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalLogin($admin_submission_user); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertResponse(403); // Set Node webform to the contact webform. @@ -59,7 +70,7 @@ public function testResults() { /* Submission management */ // Generate 3 node submissions and 3 webform submissions. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $node_sids = []; $webform_sids = []; for ($i = 1; $i <= 3; $i++) { @@ -84,27 +95,28 @@ public function testResults() { $this->assertEqual($submission_storage->getTotal($webform), 6); // Check webform node results. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); $node_route_parameters = ['node' => $node->id(), 'webform_submission' => $node_sids[1]]; $node_submission_url = Url::fromRoute('entity.node.webform_submission.canonical', $node_route_parameters); + $node_submission_title = $node->label() . ': Submission #' . $node_sids[1]; $webform_submission_route_parameters = ['webform' => 'contact', 'webform_submission' => $node_sids[1]]; $webform_submission_url = Url::fromRoute('entity.webform_submission.canonical', $webform_submission_route_parameters); - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertResponse(200); $this->assertRaw('<h1 class="page-title">' . $node->label() . '</h1>'); $this->assertNoRaw('<h1 class="page-title">' . $webform->label() . '</h1>'); - $this->assertRaw(('<a href="' . $node_submission_url->toString() . '">' . $node_sids[1] . '</a>')); + $this->assertRaw(('<a href="' . $node_submission_url->toString() . '" title="' . Html::escape($node_submission_title) . '" aria-label="' . Html::escape($node_submission_title) . '">' . $node_sids[1] . '</a>')); $this->assertNoRaw(('<a href="' . $webform_submission_url->toString() . '">' . $webform_sids[1] . '</a>')); // Check webform node title. - $this->drupalGet('node/' . $node->id() . '/webform/submission/' . $node_sids[1]); + $this->drupalGet('/node/' . $node->id() . '/webform/submission/' . $node_sids[1]); $this->assertRaw($node->label() . ': Submission #' . $node_sids[1]); - $this->drupalGet('node/' . $node->id() . '/webform/submission/' . $node_sids[2]); + $this->drupalGet('/node/' . $node->id() . '/webform/submission/' . $node_sids[2]); $this->assertRaw($node->label() . ': Submission #' . $node_sids[2]); // Check webform node navigation. - $this->drupalGet('node/' . $node->id() . '/webform/submission/' . $node_sids[1]); + $this->drupalGet('/node/' . $node->id() . '/webform/submission/' . $node_sids[1]); $node_route_parameters = ['node' => $node->id(), 'webform_submission' => $node_sids[2]]; $node_submission_url = Url::fromRoute('entity.node.webform_submission.canonical', $node_route_parameters); $this->assertRaw('<a href="' . $node_submission_url->toString() . '" rel="next" title="Go to next page">Next submission <b>›</b></a>'); @@ -114,7 +126,7 @@ public function testResults() { $webform->save(); // Check webform saved draft. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $edit = [ 'name' => "nodeDraft", 'email' => "nodeDraft@example.com", @@ -122,26 +134,26 @@ public function testResults() { 'message' => "Node draft message", ]; $this->drupalPostForm('node/' . $node->id(), $edit, t('Save Draft')); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertRaw('A partially-completed form was found. Please complete the remaining portions.'); - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertNoRaw('A partially-completed form was found. Please complete the remaining portions.'); /* Table customization */ // Check that access is denied to custom results table. - $this->drupalLogin($this->adminSubmissionUser); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); + $this->drupalLogin($admin_submission_user); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); $this->assertResponse(403); // Check that access is allowed to custom results table. - $this->drupalLogin($this->adminWebformUser); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); $this->assertResponse(200); // Check default node results table. - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); - $this->assertRaw('<th specifier="serial" aria-sort="descending" class="is-active">'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); + $this->assertRaw('<th specifier="created" class="priority-medium is-active" aria-sort="descending">'); $this->assertRaw('sort by Created'); $this->assertNoRaw('sort by Changed'); @@ -157,7 +169,7 @@ public function testResults() { $this->assertRaw('The customized table has been saved.'); // Check that the webform node's results table is now customized. - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertRaw('<th specifier="serial" aria-sort="ascending" class="is-active">'); $this->assertNoRaw('sort by Created'); $this->assertRaw('sort by Changed'); @@ -182,20 +194,20 @@ public function testResults() { // Check accessing results posted to any webform node. $this->drupalLogin($any_user); - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertResponse(200); // Check accessing results posted to own webform node. $this->drupalLogin($own_user); - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertResponse(403); $node->setOwnerId($own_user->id())->save(); - $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->drupalGet('/node/' . $node->id() . '/webform/results/submissions'); $this->assertResponse(200); // Check deleting webform node results. - $this->drupalPostForm('node/' . $node->id() . '/webform/results/clear', [], t('Clear')); + $this->drupalPostForm('node/' . $node->id() . '/webform/results/clear', ['confirm' => TRUE], t('Clear')); $this->assertEqual($submission_storage->getTotal($webform, $node), 0); $this->assertEqual($submission_storage->getTotal($webform), 3); } diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTest.php index eff3013d3d..796ad36ba8 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTest.php +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTest.php @@ -34,9 +34,6 @@ class WebformNodeTest extends WebformNodeTestBase { public function setUp() { parent::setUp(); - // Create users. - $this->createUsers(); - // Place webform test blocks. $this->placeWebformBlocks('webform_test_block_submission_limit'); } @@ -50,6 +47,10 @@ public function testNode() { /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); + $normal_user = $this->drupalCreateUser(); + + /**************************************************************************/ + // Check table names. $this->assertEqual($entity_reference_manager->getTableNames(), [ "node__webform" => 'webform', @@ -61,14 +62,14 @@ public function testNode() { /**************************************************************************/ // Check contact webform. - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertRaw('id="webform-submission-contact-node-' . $node->id() . '-add-form"'); $this->assertNoFieldByName('name', 'John Smith'); // Check contact webform with default data. $node->webform->default_data = "name: 'John Smith'"; $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertFieldByName('name', 'John Smith'); /**************************************************************************/ @@ -78,9 +79,9 @@ public function testNode() { // Check contact webform closed. $node->webform->status = WebformInterface::STATUS_CLOSED; $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertNoFieldByName('name', 'John Smith'); - $this->assertRaw('Sorry...This form is closed to new submissions.'); + $this->assertRaw('Sorry…This form is closed to new submissions.'); /* Confirmation inline (test_confirmation_inline) */ @@ -104,7 +105,7 @@ public function testNode() { $node->webform->open = date('Y-m-d\TH:i:s', strtotime('today +1 day')); $node->webform->close = ''; $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertRaw('This form has not yet been opened to submissions.'); $this->assertNoFieldByName('name', 'John Smith'); @@ -114,7 +115,7 @@ public function testNode() { $node->webform->open = date('Y-m-d\TH:i:s', strtotime('today -1 day')); $node->webform->close = ''; $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertNoRaw('This form has not yet been opened to submissions.'); $this->assertFieldByName('name'); @@ -124,8 +125,8 @@ public function testNode() { $node->webform->open = ''; $node->webform->close = date('Y-m-d\TH:i:s', strtotime('today -1 day')); $node->save(); - $this->drupalGet('node/' . $node->id()); - $this->assertRaw('Sorry...This form is closed to new submissions.'); + $this->drupalGet('/node/' . $node->id()); + $this->assertRaw('Sorry…This form is closed to new submissions.'); $this->assertNoFieldByName('name'); // Check scheduled and is open because open or close data was not set. @@ -134,8 +135,8 @@ public function testNode() { $node->webform->open = ''; $node->webform->close = ''; $node->save(); - $this->drupalGet('node/' . $node->id()); - $this->assertNoRaw('Sorry...This form is closed to new submissions.'); + $this->drupalGet('/node/' . $node->id()); + $this->assertNoRaw('Sorry…This form is closed to new submissions.'); $this->assertFieldByName('name'); // Check that changes to global message clear the cache. @@ -144,13 +145,13 @@ public function testNode() { $node->webform->open = ''; $node->webform->close = date('Y-m-d\TH:i:s', strtotime('today -1 day')); $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); \Drupal::configFactory() ->getEditable('webform.settings') ->set('settings.default_form_close_message', '{Custom closed message}') ->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertRaw('{Custom closed message}'); /**************************************************************************/ @@ -176,7 +177,7 @@ public function testNode() { ]); $limit_form->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); // Check submission limit blocks. $this->assertRaw('0 user + source entity submission(s)'); @@ -185,10 +186,10 @@ public function testNode() { $this->assertRaw('3 webform + source entity limit'); // Create submission as authenticated user. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $this->postNodeSubmission($node); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); // Check per source entity user limit. $this->assertNoFieldByName('op', 'Submit'); @@ -208,9 +209,9 @@ public function testNode() { $this->postNodeSubmission($node); $this->drupalLogout(); - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); // Check per source entity total limit. $this->assertNoFieldByName('op', 'Submit'); @@ -239,7 +240,7 @@ public function testNode() { $source_entity_options = ['query' => ['source_entity_type' => 'node', 'source_entity_id' => $node->id()]]; // Check default data from source entity using query string. - $this->drupalGet('webform/contact', $source_entity_options); + $this->drupalGet('/webform/contact', $source_entity_options); $this->assertFieldByName('name', '{name}'); // Check prepopulating source entity using query string. @@ -282,7 +283,7 @@ public function testNode() { $node->save(); // Check 'Register' link. - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertLink('Register'); // Check that link include source_entity_type and source_entity_id. diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTestBase.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTestBase.php index 584d9efd14..96fa5bbd13 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTestBase.php +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTestBase.php @@ -48,12 +48,16 @@ protected function postNodeSubmission(NodeInterface $node, array $edit = [], $su * * @param string $webform_id * A webform id. + * @param array $settings + * (optional) An associative array of settings for the node, as used in + * entity_create(). * * @return \Drupal\node\NodeInterface * A webform node. */ - protected function createWebformNode($webform_id) { - $node = $this->drupalCreateNode(['type' => 'webform']); + protected function createWebformNode($webform_id, array $settings = []) { + $settings += ['type' => 'webform']; + $node = $this->drupalCreateNode($settings); $node->webform->target_id = $webform_id; $node->webform->status = WebformInterface::STATUS_OPEN; $node->webform->open = ''; diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTranslationTest.php b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTranslationTest.php new file mode 100644 index 0000000000..1a7baea2ec --- /dev/null +++ b/web/modules/webform/modules/webform_node/src/Tests/WebformNodeTranslationTest.php @@ -0,0 +1,38 @@ +<?php + +namespace Drupal\webform_node\Tests; + +/** + * Tests for webform node translation. + * + * @group WebformNode + */ +class WebformNodeTranslationTest extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_node', 'webform_node_test_translation']; + + /** + * Tests webform node translation. + */ + public function testNodeTranslation() { + $node = $this->createWebformNode('webform_node_test_translation', ['title' => 'English node']); + + // Check computed token uses the English title. + $this->drupalGet('/node/' . $node->id()); + $this->assertFieldByName('computed_token', 'English node'); + + // Create spanish node. + $node->addTranslation('es', ['title' => 'Spanish node'])->save(); + + // Check computed token uses the Spanish title. + $this->drupalGet('/es/node/' . $node->id()); + $this->assertNoFieldByName('computed_token', 'English node'); + $this->assertFieldByName('computed_token', 'Spanish node'); + } + +} diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_a.yml b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_a.yml index 79d17fe86b..6a80f24b1e 100644 --- a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_a.yml +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_a.yml @@ -6,8 +6,10 @@ dependencies: - webform_node_test_multiple open: null close: null +weight: 0 uid: null template: false +archive: false id: webform_node_test_multiple_a title: 'Test: Webform Node Multiple A' description: 'Test Webform Node Multiple A' @@ -16,6 +18,7 @@ elements: | textfield_a: '#title': textfield_a '#type': textfield + css: '' javascript: '' settings: @@ -24,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_b.yml b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_b.yml index cbc93ed18d..c93f3b9977 100644 --- a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_b.yml +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/config/install/webform.webform.webform_node_test_multiple_b.yml @@ -6,8 +6,10 @@ dependencies: - webform_node_test_multiple open: null close: null +weight: -1 uid: null template: false +archive: false id: webform_node_test_multiple_b title: 'Test: Webform Node Multiple B' description: 'Test Webform Node Multiple B' @@ -16,6 +18,7 @@ elements: | textfield_b: '#title': textfield_b '#type': textfield + css: '' javascript: '' settings: @@ -24,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/webform_node_test_multiple.info.yml b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/webform_node_test_multiple.info.yml index 3b59ee5b3b..207e8b5ad8 100644 --- a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/webform_node_test_multiple.info.yml +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_multiple/webform_node_test_multiple.info.yml @@ -13,8 +13,8 @@ dependencies: - 'webform:webform' - 'webform:webform_node' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/config/install/webform.webform.webform_node_test_translation.yml b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/config/install/webform.webform.webform_node_test_translation.yml new file mode 100644 index 0000000000..41a5c678f6 --- /dev/null +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/config/install/webform.webform.webform_node_test_translation.yml @@ -0,0 +1,176 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_node_test_translation +open: null +close: null +weight: 0 +uid: 1 +template: false +archive: false +id: webform_node_test_translation +title: webform_node_test_translation +description: '' +category: '' +elements: | + computed_token: + '#type': webform_computed_token + '#title': computed_token + '#value': '[webform_submission:node:title]' + +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.info.yml b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.info.yml new file mode 100644 index 0000000000..276fba229d --- /dev/null +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.info.yml @@ -0,0 +1,16 @@ +name: 'Webform node module translation test' +type: module +description: 'Support module for webform node that enabled translations for webform content type.' +package: Testing +# core: 8.x +dependencies: + - 'webform:webform' + - 'webform:webform_node' + - 'webform:webform_test_translation' + - 'drupal:content_translation' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.install b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.install new file mode 100644 index 0000000000..6fd10d5a20 --- /dev/null +++ b/web/modules/webform/modules/webform_node/tests/modules/webform_node_test_translation/webform_node_test_translation.install @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the webform node test translation module. + */ + +use Drupal\Core\Form\FormState; +use Drupal\language\Entity\ContentLanguageSettings; + +/** + * Implements hook_install(). + */ +function webform_node_test_translation_install() { + // Initialize webform node content type translation. + // @see \Drupal\language\Form\ContentLanguageSettingsForm::submitForm + $config = ContentLanguageSettings::loadByEntityTypeBundle('node', 'webform'); + $config->setDefaultLangcode('en') + ->setLanguageAlterable(TRUE) + ->save(); + + // Initialize webform node content type title field translation. + // + // Below $values are from… + // $values = $form_state->getValues(); + // var_export($values['settings']['node']['webform']); exit; + // + // within content_translation_form_language_content_settings_submit(). + // + // @see /admin/config/regional/content-language + // @see content_translation_form_language_content_settings_submit() + $values = [ + 'entity_types' => ['node' => 'node'], + 'settings' => [ + 'node' => [ + 'webform' => [ + 'settings' => [ + 'language' => [ + 'langcode' => 'site_default', + 'language_alterable' => '1', + ], + 'content_translation' => [ + 'untranslatable_fields_hide' => '1', + ], + ], + 'fields' => [ + 'title' => 1, + ], + 'translatable' => 1, + ], + ], + ], + ]; + + module_load_include('inc', 'content_translation', 'content_translation.admin'); + $form_state = new FormState(); + $form_state->setValues($values); + content_translation_form_language_content_settings_submit([], $form_state); +} diff --git a/web/modules/webform/modules/webform_node/tests/src/Kernel/WebformNodeUninstallTest.php b/web/modules/webform/modules/webform_node/tests/src/Kernel/WebformNodeUninstallTest.php index f675bf3e35..e1c056ae02 100644 --- a/web/modules/webform/modules/webform_node/tests/src/Kernel/WebformNodeUninstallTest.php +++ b/web/modules/webform/modules/webform_node/tests/src/Kernel/WebformNodeUninstallTest.php @@ -52,7 +52,7 @@ public function testWebformNodeUninstall() { $node = Node::create(['title' => $this->randomString(), 'type' => 'webform']); $node->save(); - // Check webform node module can't be ininstalled. + // Check webform node module can't be uninstalled. $validation_reasons = \Drupal::service('module_installer')->validateUninstall(['webform_node']); $this->assertEqual(['To uninstall Webform node, delete all content that has the Webform content type.'], $validation_reasons['webform_node']); diff --git a/web/modules/webform/modules/webform_node/webform_node.info.yml b/web/modules/webform/modules/webform_node/webform_node.info.yml index 9cab5844b8..4b6abc691b 100644 --- a/web/modules/webform/modules/webform_node/webform_node.info.yml +++ b/web/modules/webform/modules/webform_node/webform_node.info.yml @@ -11,8 +11,8 @@ dependencies: - 'drupal:user' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_node/webform_node.libraries.yml b/web/modules/webform/modules/webform_node/webform_node.libraries.yml index 75cd070987..386f0b1425 100644 --- a/web/modules/webform/modules/webform_node/webform_node.libraries.yml +++ b/web/modules/webform/modules/webform_node/webform_node.libraries.yml @@ -9,3 +9,5 @@ webform_node.references: css: theme: css/webform_node.references.css: {} + dependencies: + - webform/webform.admin.dropbutton diff --git a/web/modules/webform/modules/webform_node/webform_node.links.task.yml b/web/modules/webform/modules/webform_node/webform_node.links.task.yml index d383fa4fa3..b5e76f5082 100644 --- a/web/modules/webform/modules/webform_node/webform_node.links.task.yml +++ b/web/modules/webform/modules/webform_node/webform_node.links.task.yml @@ -34,12 +34,6 @@ entity.node.webform.results_clear: parent_id: entity.node.webform.results weight: 20 -entity.node.webform.results_log: - title: 'Log' - route_name: entity.node.webform.results_log - parent_id: entity.node.webform.results - weight: 30 - # Submission. entity.node.webform_submission.canonical: @@ -102,18 +96,6 @@ entity.node.webform_submission.resend_form: base_route: entity.node.webform_submission.canonical weight: 30 -entity.node.webform_submission.delete_form: - title: 'Delete' - route_name: entity.node.webform_submission.delete_form - base_route: entity.node.webform_submission.canonical - weight: 40 - -entity.node.webform_submission.log: - title: 'Log' - route_name: entity.node.webform_submission.log - base_route: entity.node.webform_submission.canonical - weight: 50 - # User Submission. entity.node.webform.user.submission: @@ -128,8 +110,8 @@ entity.node.webform.user.submission.edit: base_route: entity.node.webform.user.submission weight: 10 -entity.node.webform.user.submission.delete: - title: 'Delete' - route_name: entity.node.webform.user.submission.delete +entity.node.webform.user.submission.duplicate: + title: 'Duplicate' + route_name: entity.node.webform.user.submission.duplicate base_route: entity.node.webform.user.submission weight: 20 diff --git a/web/modules/webform/modules/webform_node/webform_node.module b/web/modules/webform/modules/webform_node/webform_node.module index 0211c34764..25e79ba95f 100644 --- a/web/modules/webform/modules/webform_node/webform_node.module +++ b/web/modules/webform/modules/webform_node/webform_node.module @@ -8,7 +8,6 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; @@ -26,57 +25,6 @@ function webform_node_entity_type_alter(array &$entity_types) { } } -/** - * Implements hook_help(). - */ -function webform_node_help($route_name, RouteMatchInterface $route_match) { - if ($route_name != 'node.add') { - return NULL; - } - - // Don't show the warning is the user can't create webforms. - if (!\Drupal::currentUser()->hasPermission('create webform')) { - return NULL; - } - - /** @var \Drupal\node\NodeTypeInterface $node_type */ - $node_type = $route_match->getParameter('node_type'); - - // Determine if the node type has webform (entity reference) field. - $has_webform_field = FALSE; - $field_configs = \Drupal::entityTypeManager()->getStorage('field_config')->loadByProperties(['entity_type' => 'node', 'bundle' => $node_type->id()]); - foreach ($field_configs as $field_config) { - if ($field_config->get('field_type') === 'webform') { - $has_webform_field = TRUE; - break; - } - } - - // Display warning message if webform query string parameter is missing. - if ($has_webform_field && !\Drupal::request()->get('webform_id')) { - $build = [ - '#type' => 'webform_message', - '#message_type' => 'warning', - '#message_close' => TRUE, - '#message_id' => 'webform_node.references', - '#message_storage' => WebformMessage::STORAGE_USER, - '#message_message' => t('Webforms must first be <a href=":href">created</a> before referencing them in the below form.', [':href' => Url::fromRoute('entity.webform.collection')->toString()]), - '#cache' => ['max-age' => 0], - ]; - } - elseif (\Drupal::request()->get('webform_id')) { - // If there is a webform query string parameter, then disable caching. - $build = ['#cache' => ['max-age' => 0]]; - } - else { - $build = []; - } - - // Makes sure if a webform field is added to a node type it is accounted for. - \Drupal::service('renderer')->addCacheableDependency($build, $node_type); - return $build; -} - /** * Implements hook_node_access(). */ @@ -157,6 +105,49 @@ function webform_node_node_delete(NodeInterface $node) { $entity_reference_manager->deleteUserWebformId($node); } +/** + * Implements hook_field_widget_WIDGET_TYPE_form_alter(). + */ +function webform_node_field_widget_webform_entity_reference_autocomplete_form_alter(&$element, FormStateInterface $form_state, $context) { + static $once; + if (!empty($once)) { + return; + } + $once = TRUE; + + // Make sure the 'target_id' is included. + if (!isset($element['target_id'])) { + return; + } + + // Display a warning message if webform query string parameter is missing. + if (empty($element['target_id']['#default_value'])) { + $element['target_id']['#attributes']['class'][] = 'js-target-id-webform-node-references'; + $element['webform_node_references'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_close' => TRUE, + '#message_id' => 'webform_node.references', + '#message_storage' => WebformMessage::STORAGE_USER, + '#message_message' => t('Webforms must first be <a href=":href">created</a> before referencing them.', [':href' => Url::fromRoute('entity.webform.collection')->toString()]), + '#cache' => ['max-age' => 0], + '#weight' => -10, + '#states' => [ + 'visible' => [ + '.js-target-id-webform-node-references' => ['value' => ''], + ], + ], + ]; + } +} + +/** + * Implements hook_field_widget_WIDGET_TYPE_form_alter(). + */ +function webform_node_field_widget_webform_entity_reference_select_form_alter(&$element, FormStateInterface $form_state, $context) { + webform_node_field_widget_webform_entity_reference_autocomplete_form_alter($element, $form_state, $context); +} + /** * Implements hook_preprocess_HOOK() for page title templates. */ diff --git a/web/modules/webform/modules/webform_node/webform_node.routing.yml b/web/modules/webform/modules/webform_node/webform_node.routing.yml index 71581efe42..eb135eb4f7 100644 --- a/web/modules/webform/modules/webform_node/webform_node.routing.yml +++ b/web/modules/webform/modules/webform_node/webform_node.routing.yml @@ -21,10 +21,11 @@ entity.node.webform.confirmation: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' entity.node.webform.user.submissions: - path: '/node/{node}/webform/submissions' + path: '/node/{node}/webform/submissions/{submission_view}' defaults: _entity_list: 'webform_submission' _title: 'Submissions' + submission_view: '' operation: '' entity_access: 'webform.submission_view_own' options: @@ -35,10 +36,11 @@ entity.node.webform.user.submissions: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' entity.node.webform.user.drafts: - path: '/node/{node}/webform/drafts' + path: '/node/{node}/webform/drafts/{submission_view}' defaults: _entity_list: 'webform_submission' _title: 'Drafts' + submission_view: '' operation: view entity_access: 'webform.submission_create' options: @@ -46,7 +48,7 @@ entity.node.webform.user.drafts: node: type: 'entity:node' requirements: - _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformDraftsAccess' entity.node.webform.user.submission: path: '/node/{node}/webform/submissions/{webform_submission}' @@ -91,6 +93,21 @@ entity.node.webform.user.submission.delete: requirements: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' +entity.node.webform.user.submission.duplicate: + path: '/node/{node}/webform/submissions/{webform_submission}/duplicate' + defaults: + _entity_form: 'webform_submission.duplicate' + _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' + duplicate: TRUE + operation: webform_submission_duplicate + entity_access: 'webform_submission.update' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + entity.node.webform.test_form: path: '/node/{node}/webform/test' defaults: @@ -106,10 +123,11 @@ entity.node.webform.test_form: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' entity.node.webform.results_submissions: - path: '/node/{node}/webform/results/submissions' + path: '/node/{node}/webform/results/submissions/{submission_view}' defaults: _entity_list: 'webform_submission' _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + submission_view: '' operation: webform_submission_view entity_access: 'webform.submission_view_any' options: @@ -175,22 +193,6 @@ entity.node.webform.results_clear: requirements: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformResultsAccess' -entity.node.webform.results_log: - path: '/node/{node}/webform/results/log' - defaults: - _controller: '\Drupal\webform_node\Controller\WebformNodeSubmissionLogController::overview' - _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' - operation: webform_submission_view - entity_access: 'webform.submission_view_any' - options: - _admin_route: TRUE - parameters: - node: - type: 'entity:node' - requirements: - _permission: 'access webform submission log' - _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformLogAccess' - entity.node.webform_submission.canonical: path: '/node/{node}/webform/submission/{webform_submission}' defaults: @@ -346,22 +348,6 @@ entity.node.webform_submission.delete_form: requirements: _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' -entity.node.webform_submission.log: - path: '/node/{node}/webform/submission/{webform_submission}/log' - defaults: - _controller: '\Drupal\webform\Controller\WebformSubmissionLogController::overview' - _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' - operation: webform_submission_view - entity_access: 'webform.submission_view_any' - options: - _admin_route: TRUE - parameters: - node: - type: 'entity:node' - requirements: - _permission: 'access webform submission log' - _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformLogAccess' - entity.node.webform.entity_reference.set: path: '/node/{node}/webform/change/{webform}' defaults: diff --git a/web/modules/webform/modules/webform_node/webform_node.tokens.inc b/web/modules/webform/modules/webform_node/webform_node.tokens.inc index 4896ee8434..be2aa115fa 100644 --- a/web/modules/webform/modules/webform_node/webform_node.tokens.inc +++ b/web/modules/webform/modules/webform_node/webform_node.tokens.inc @@ -42,7 +42,7 @@ function webform_node_tokens($type, $tokens, array $data, array $options, Bubble if ($type == 'webform_submission' && !empty($data['webform_submission'])) { /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $data['webform_submission']; - $source_entity = $webform_submission->getSourceEntity(); + $source_entity = $webform_submission->getSourceEntity(TRUE); if (!$source_entity || (!$source_entity instanceof NodeInterface)) { return $replacements; } diff --git a/web/modules/webform/modules/webform_scheduled_email/config/install/webform_scheduled_email.settings.yml b/web/modules/webform/modules/webform_scheduled_email/config/install/webform_scheduled_email.settings.yml new file mode 100644 index 0000000000..4ab1916e4b --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/config/install/webform_scheduled_email.settings.yml @@ -0,0 +1 @@ +schedule_type: date diff --git a/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.yml b/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.plugin.handler.schema.yml similarity index 86% rename from web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.yml rename to web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.plugin.handler.schema.yml index f5af571f03..aa481b914a 100644 --- a/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.yml +++ b/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.plugin.handler.schema.yml @@ -66,6 +66,9 @@ webform.handler.scheduled_email: exclude_empty: type: boolean label: 'Preview exclude empty elements' + exclude_empty_checkbox: + type: boolean + label: 'Exclude unselected checkboxes' html: type: boolean label: 'HTML' @@ -75,6 +78,9 @@ webform.handler.scheduled_email: twig: type: boolean label: 'Twig' + theme_name: + type: string + label: 'Theme name' debug: type: boolean label: 'Enable debugging' @@ -84,6 +90,12 @@ webform.handler.scheduled_email: days: type: integer label: 'After/before days' + ignore_past: + type: boolean + label: 'Prevent scheduling past actions' unschedule: type: boolean label: 'Unschedule email when draft or submission is saved' + test_send: + type: boolean + label: 'Immediately send email when testing this webform' diff --git a/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.settings.schema.yml b/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.settings.schema.yml new file mode 100644 index 0000000000..099a053e5b --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/config/schema/webform_scheduled_email.settings.schema.yml @@ -0,0 +1,7 @@ +webform_scheduled_email.settings: + type: config_object + label: 'Webform scheduled email settings' + mapping: + schedule_type: + type: string + label: 'Scheduled email type (date or date/time)' diff --git a/web/modules/webform/modules/webform_scheduled_email/drush.services.yml b/web/modules/webform/modules/webform_scheduled_email/drush.services.yml index 485cf8c1d6..2f99e3c2ef 100644 --- a/web/modules/webform/modules/webform_scheduled_email/drush.services.yml +++ b/web/modules/webform/modules/webform_scheduled_email/drush.services.yml @@ -1,6 +1,6 @@ services: webform_scheduled_email.manager.commands: class: \Drupal\webform_scheduled_email\Commands\WebformScheduledEmailCommands - arguments: ['@webform.cli_service'] + arguments: ['@webform_scheduled_email.manager'] tags: - { name: drush.command } diff --git a/web/modules/webform/modules/webform_scheduled_email/drush/webform_scheduled_email.drush.inc b/web/modules/webform/modules/webform_scheduled_email/drush/webform_scheduled_email.drush.inc index 953a6b8ca0..d53a6a56f1 100644 --- a/web/modules/webform/modules/webform_scheduled_email/drush/webform_scheduled_email.drush.inc +++ b/web/modules/webform/modules/webform_scheduled_email/drush/webform_scheduled_email.drush.inc @@ -63,6 +63,8 @@ function webform_scheduled_email_help($section) { * callback to prevent conflicts with webform_scheduled_email_cron(). * * @see webform_scheduled_email_cron() + * @see \Drupal\webform_scheduled_email\Commands\WebformScheduledEmailCommands::drush_webform_scheduled_email_cron_validate() + * @see \Drupal\webform_scheduled_email\Commands\WebformScheduledEmailCommands::drush_webform_scheduled_email_cron() */ function webform_scheduled_email_cron_process($webform_id = NULL, $handler_id = NULL) { $schedule_limit = drush_get_option('schedule_limit') ?: 1000; diff --git a/web/modules/webform/modules/webform_scheduled_email/src/Commands/WebformScheduledEmailCommands.php b/web/modules/webform/modules/webform_scheduled_email/src/Commands/WebformScheduledEmailCommands.php index 91add816b1..9c585a0f4e 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/Commands/WebformScheduledEmailCommands.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/Commands/WebformScheduledEmailCommands.php @@ -6,12 +6,31 @@ use Drush\Commands\DrushCommands; use Drupal\webform\Entity\Webform; use Drupal\webform_scheduled_email\Plugin\WebformHandler\ScheduleEmailWebformHandler; +use Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface; /** * Webform scheduled email commands for Drush 9.x. */ class WebformScheduledEmailCommands extends DrushCommands { + /** + * The webform scheduled email manager. + * + * @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface + */ + protected $manager; + + /** + * Constructs a WebformScheduledEmailController object. + * + * @param \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $manager + * The webform scheduled email manager. + */ + public function __construct(WebformScheduledEmailManagerInterface $manager) { + parent::__construct(); + $this->manager = $manager; + } + /** * @hook validate webform:scheduled-email:cron */ @@ -56,12 +75,12 @@ public function drush_webform_scheduled_email_cron_validate(CommandData $command * @option send_limit * The maximum number of emails to be sent. If set to 0 no emails will be sent. (Default 500) * @aliases wfsec + * + * @see webform_scheduled_email_cron_process() */ public function drush_webform_scheduled_email_cron($webform_id = NULL, $handler_id = NULL, array $options = ['schedule_limit' => 1000, 'send_limit' => 500]) { $webform = ($webform_id) ? Webform::load($webform_id) : NULL; - /** @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $webform_scheduled_email_manager */ - $webform_scheduled_email_manager = \Drupal::service('webform_scheduled_email.manager'); - $stats = $webform_scheduled_email_manager->cron( + $stats = $this->manager->cron( $webform, $handler_id, $options['schedule_limit'], diff --git a/web/modules/webform/modules/webform_scheduled_email/src/Controller/WebformScheduledEmailController.php b/web/modules/webform/modules/webform_scheduled_email/src/Controller/WebformScheduledEmailController.php index 6202050bce..80b5c0d8dd 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/Controller/WebformScheduledEmailController.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/Controller/WebformScheduledEmailController.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container) { * Runs cron task for webform scheduled email handler. * * @param \Drupal\webform\WebformInterface $webform - * The webform containg a scheduled email handler. + * The webform containing a scheduled email handler. * @param string|null $handler_id * A webform handler id. * @@ -53,7 +53,7 @@ public static function create(ContainerInterface $container) { */ public function cron(WebformInterface $webform, $handler_id) { $stats = $this->manager->cron($webform, $handler_id); - drupal_set_message($this->t($stats['_message'], $stats['_context'])); + $this->messenger()->addStatus($this->t($stats['_message'], $stats['_context'])); return new RedirectResponse($webform->toUrl('handlers')->toString()); } diff --git a/web/modules/webform/modules/webform_scheduled_email/src/Plugin/WebformHandler/ScheduleEmailWebformHandler.php b/web/modules/webform/modules/webform_scheduled_email/src/Plugin/WebformHandler/ScheduleEmailWebformHandler.php index 5f5a5cccf4..a7d625b26c 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/Plugin/WebformHandler/ScheduleEmailWebformHandler.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/Plugin/WebformHandler/ScheduleEmailWebformHandler.php @@ -2,7 +2,6 @@ namespace Drupal\webform_scheduled_email\Plugin\WebformHandler; -use Drupal\Component\Utility\Unicode; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; @@ -36,6 +35,8 @@ public function defaultConfiguration() { 'send' => '[date:html_date]', 'days' => '', 'unschedule' => FALSE, + 'ignore_past' => FALSE, + 'test_send' => FALSE, ]; } @@ -101,6 +102,7 @@ public function getSummary() { $summary['#status'] = [ '#type' => 'details', '#title' => $this->t('Scheduled email status (@total)', ['@total' => $stats['total']]), + '#help' => FALSE, '#description' => $build, ]; } @@ -111,6 +113,9 @@ public function getSummary() { * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $webform_scheduled_email_manager */ + $webform_scheduled_email_manager = \Drupal::service('webform_scheduled_email.manager'); + $webform = $this->getWebform(); // Get options, mail, and text elements as options (text/value). @@ -144,7 +149,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta // Send date/time. $send_options = [ '[date:html_date]' => $this->t('Current date'), - WebformOtherBase::OTHER_OPTION => $this->t('Custom date...'), + WebformOtherBase::OTHER_OPTION => $this->t('Custom @label…', ['@label' => $webform_scheduled_email_manager->getDateTypeLabel()]), (string) $this->t('Webform') => [ '[webform:open:html_date]' => $this->t('Open date'), '[webform:close:html_date]' => $this->t('Close date'), @@ -159,13 +164,16 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $send_options[(string) $this->t('Element')] = $date_element_options; } + $t_args = [ + '@format' => $webform_scheduled_email_manager->getDateFormatLabel(), + '@type' => $webform_scheduled_email_manager->getDateTypeLabel(), + ]; $form['scheduled']['send'] = [ '#type' => 'webform_select_other', '#title' => $this->t('Send email on'), '#options' => $send_options, - '#other__placeholder' => $this->t('YYYY-MM-DD'), - '#other__description' => $this->t('Enter a valid ISO date (YYYY-MM-DD) or token which returns a valid ISO date.'), - '#parents' => ['settings', 'send'], + '#other__placeholder' => $webform_scheduled_email_manager->getDateFormatLabel(), + '#other__description' => $this->t('Enter a valid ISO @type (@format) or token which returns a valid ISO @type.', $t_args), '#default_value' => $this->configuration['send'], ]; @@ -186,11 +194,19 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#empty_option' => $this->t('- None -'), '#options' => $days_options, '#default_value' => $this->configuration['days'], - '#other__option_label' => $this->t('Custom number of days...'), + '#other__option_label' => $this->t('Custom number of days…'), '#other__type' => 'number', '#other__field_suffix' => $this->t('days'), '#other__placeholder' => $this->t('Enter +/- days'), - '#parents' => ['settings', 'days'], + ]; + + // Ignore past. + $form['scheduled']['ignore_past'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Do not schedule email if the action should be triggered in the past.'), + '#description' => $this->t('You can use this setting to prevent an action to be scheduled if it should have been triggered in the past.'), + '#default_value' => $this->configuration['ignore_past'], + '#return_value' => TRUE, ]; // Unschedule. @@ -200,7 +216,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => $this->t('You can use this setting to unschedule a draft reminder, when submission has been completed.'), '#default_value' => $this->configuration['unschedule'], '#return_value' => TRUE, - '#parents' => ['settings', 'unschedule'], ]; // Queue all submissions. @@ -210,13 +225,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Schedule emails for all existing submissions'), '#description' => $this->t('Check schedule emails after submissions have been processed.'), '#return_value' => TRUE, - '#parents' => ['settings', 'queue'], ]; $form['scheduled']['queue_message'] = [ '#type' => 'webform_message', '#message_message' => $this->t('Please note all submissions will be rescheduled, including ones that have already received an email from this handler and submissions whose send date is in the past.'), '#message_type' => 'warning', - '#parents' => ['settings', 'queue_message'], '#states' => [ 'visible' => [ ':input[name="settings[queue]"]' => [ @@ -236,20 +249,29 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#theme' => 'item_list', '#items' => [ $this->t("Only one email can be scheduled per handler and submission."), + $this->t('Email will be rescheduled when a draft or submission is updated.'), $this->t("Multiple handlers can be used to schedule multiple emails."), $this->t('Deleting this handler will unschedule all scheduled emails.'), ['#markup' => $this->t('Scheduled emails are automatically sent starting at midnight using <a href=":href">cron</a>, which is executed at predefined interval.', [':href' => 'https://www.drupal.org/docs/7/setting-up-cron/overview'])], ], ]; - $form['scheduled']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['scheduled']['token_tree_link'] = $this->buildTokenTreeElement(); $form = parent::buildConfigurationForm($form, $form_state); // Change 'Send email' to 'Scheduled email'. $form['settings']['states']['#title'] = $this->t('Schedule email'); - return $form; + // Development. + $form['development']['test_send'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Immediately send email when testing a webform.'), + '#return_value' => TRUE, + '#default_value' => $this->configuration['test_send'], + ]; + + return $this->setSettingsParents($form); } /** @@ -258,6 +280,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { parent::validateConfigurationForm($form, $form_state); + /** @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $webform_scheduled_email_manager */ + $webform_scheduled_email_manager = \Drupal::service('webform_scheduled_email.manager'); + $values = $form_state->getValues(); // Cast days string to int. @@ -265,9 +290,15 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form // If token skip validation. if (!preg_match('/^\[[^]]+\]$/', $values['send'])) { + $date_format = $webform_scheduled_email_manager->getDateFormat(); // Validate custom 'send on' date. - if (!WebformDateHelper::isValidDateFormat($values['send'])) { - $form_state->setError($form['settings']['scheduled']['send'], $this->t('The %field date is required. Please enter a date in the format %format.', ['%field' => $this->t('Send on'), '%format' => 'YYYY-MM-DDDD'])); + if (WebformDateHelper::createFromFormat($date_format, $values['send']) === FALSE) { + $t_args = [ + '%field' => $this->t('Send on'), + '%format' => $webform_scheduled_email_manager->getDateFormatLabel(), + '@type' => $webform_scheduled_email_manager->getDateTypeLabel(), + ]; + $form_state->setError($form['settings']['scheduled']['send'], $this->t('The %field date is required. Please enter a @type in the format %format.', $t_args)); } } @@ -286,6 +317,25 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s } } + /** + * {@inheritdoc} + */ + public function alterForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { + // Display warning when test email will be sent immediately. + if (\Drupal::request()->isMethod('GET') + && $this->getWebform()->isTest() + && !empty($this->configuration['test_send'])) { + $t_args = ['%label' => $this->getLabel()]; + $form['scheduled_email_handler_test_send__' . $this->getHandlerId()] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('The %label email will be sent immediately upon submission.', $t_args), + '#message_type' => 'warning', + '#message_close' => TRUE, + '#weight' => -100, + ]; + } + } + /** * {@inheritdoc} */ @@ -349,11 +399,17 @@ protected function scheduleMessage(WebformSubmissionInterface $webform_submissio // Don't send the message if empty (aka To, CC, and BCC is empty). if (!$this->hasRecipient($webform_submission, $message)) { if ($this->configuration['debug']) { - drupal_set_message($this->t('%submission: Email <b>not sent</b> for %handler handler because a <em>To</em>, <em>CC</em>, or <em>BCC</em> email was not provided.', $t_args), 'warning'); + $this->messenger()->addWarning($this->t('%submission: Email <b>not sent</b> for %handler handler because a <em>To</em>, <em>CC</em>, or <em>BCC</em> email was not provided.', $t_args)); } return FALSE; } + // When testing send email immediately. + if ($this->getWebform()->isTest() && !empty($this->configuration['test_send'])) { + $this->sendMessage($webform_submission, $message); + return TRUE; + } + // Get send date. $send_iso_date = $webform_scheduled_email_manager->getSendDate($webform_submission, $this->handler_id); $t_args['%date'] = $send_iso_date; @@ -362,7 +418,7 @@ protected function scheduleMessage(WebformSubmissionInterface $webform_submissio // date. if (!$send_iso_date) { if ($this->configuration['debug']) { - drupal_set_message($this->t('%submission: Email <b>not scheduled</b> for %handler handler because %send is not a valid date/token.', $t_args), 'warning', TRUE); + $this->messenger()->addWarning($this->t('%submission: Email <b>not scheduled</b> for %handler handler because %send is not a valid date/token.', $t_args), TRUE); } $context = $t_args + [ 'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(), @@ -381,10 +437,12 @@ protected function scheduleMessage(WebformSubmissionInterface $webform_submissio WebformScheduledEmailManagerInterface::EMAIL_ALREADY_SCHEDULED => $this->t('Already Scheduled'), WebformScheduledEmailManagerInterface::EMAIL_SCHEDULED => $this->t('Scheduled'), WebformScheduledEmailManagerInterface::EMAIL_RESCHEDULED => $this->t('Rescheduled'), + WebformScheduledEmailManagerInterface::EMAIL_UNSCHEDULED => $this->t('Unscheduled'), + WebformScheduledEmailManagerInterface::EMAIL_IGNORED => $this->t('Ignored'), ]; - $t_args['@action'] = Unicode::strtolower($statuses[$status]); - drupal_set_message($this->t('%submission: Email <b>@action</b> by %handler handler to be sent on %date.', $t_args), 'warning', TRUE); + $t_args['@action'] = mb_strtolower($statuses[$status]); + $this->messenger()->addWarning($this->t('%submission: Email <b>@action</b> by %handler handler to be sent on %date.', $t_args), TRUE); $debug_message = $this->buildDebugMessage($webform_submission, $message); $debug_message['status'] = [ @@ -401,7 +459,7 @@ protected function scheduleMessage(WebformSubmissionInterface $webform_submissio '#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'], '#weight' => -10, ]; - drupal_set_message(\Drupal::service('renderer')->renderPlain($debug_message), 'warning', TRUE); + $this->messenger()->addWarning(\Drupal::service('renderer')->renderPlain($debug_message), TRUE); } return $status; @@ -423,7 +481,7 @@ protected function unscheduleMessage(WebformSubmissionInterface $webform_submiss '%submission' => $webform_submission->label(), '%handler' => $this->label(), ]; - drupal_set_message($this->t('%submission: Email <b>unscheduled</b> for %handler handler.', $t_args), 'warning', TRUE); + $this->messenger()->addWarning($this->t('%submission: Email <b>unscheduled</b> for %handler handler.', $t_args), TRUE); } } } diff --git a/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTest.php b/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTest.php index 5be4b17038..b20e5cc7a0 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTest.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTest.php @@ -29,8 +29,8 @@ public function testWebformScheduledEmail() { /** @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $scheduled_manager */ $scheduled_manager = \Drupal::service('webform_scheduled_email.manager'); - $yesterday = date('Y-m-d', strtotime('-1 days')); - $tomorrow = date('Y-m-d', strtotime('+1 days')); + $yesterday = date($scheduled_manager->getDateFormat(), strtotime('-1 days')); + $tomorrow = date($scheduled_manager->getDateFormat(), strtotime('+1 days')); /**************************************************************************/ // Submission scheduling. @@ -78,7 +78,7 @@ public function testWebformScheduledEmail() { $this->assertEqual($scheduled_manager->total(), 0); // Check schedule email for draft. - $draft_reminder = date('Y-m-d', strtotime('+14 days')); + $draft_reminder = date($scheduled_manager->getDateFormat(), strtotime('+14 days')); $sid = $this->postSubmission($webform_schedule, ['send' => 'draft_reminder'], 'Save Draft'); $this->assertText("Test: Handler: Test scheduled email: Submission #$sid: Email scheduled by Draft reminder handler to be sent on $draft_reminder."); $this->assertEqual($scheduled_manager->total(), 1); @@ -93,6 +93,28 @@ public function testWebformScheduledEmail() { $this->assertText("Test: Handler: Test scheduled email: Submission #$sid: Email not scheduled for Broken handler because [broken] is not a valid date/token."); $this->assertEqual($scheduled_manager->total($webform_schedule), 0); + /**************************************************************************/ + // Submission scheduling with date/time. + /**************************************************************************/ + + // Change schedule type to 'datetime'. + \Drupal::configFactory()->getEditable('webform_scheduled_email.settings') + ->set('schedule_type', 'datetime') + ->save(); + + // Check other +14 days with time. + $sid = $this->postSubmission($webform_schedule, ['send' => 'other', 'date[date]' => '2001-01-01', 'date[time]' => '02:00:00'], 'Save Draft'); + $webform_submission = WebformSubmission::load($sid); + $scheduled_email = $scheduled_manager->load($webform_submission, 'other'); + $this->assertText("Test: Handler: Test scheduled email: Submission #$sid: Email scheduled by Other handler to be sent on 2001-01-15 02:00:00."); + $this->assertEqual($scheduled_email->send, strtotime('2001-01-15 02:00:00')); + $this->assertEqual($scheduled_email->state, $scheduled_manager::SUBMISSION_SEND); + + // Change schedule type back to 'date'. + \Drupal::configFactory()->getEditable('webform_scheduled_email.settings') + ->set('schedule_type', 'date') + ->save(); + /**************************************************************************/ // Check deleting handler removes scheduled emails. // @todo Figure out why the below exception is occurring during tests only. @@ -154,6 +176,63 @@ public function testWebformScheduledEmail() { $this->assertEqual($scheduled_manager->waiting($webform_schedule), 6); $this->assertEqual($scheduled_manager->ready($webform_schedule), 0); + /**************************************************************************/ + // Webform scheduling with conditions. + /**************************************************************************/ + + // Purge all submissions. + $this->purgeSubmissions(); + + // Create 3 yesterday scheduled emails. + $this->postSubmission($webform_schedule, ['send' => 'yesterday']); + $this->postSubmission($webform_schedule, ['send' => 'yesterday']); + $this->postSubmission($webform_schedule, ['send' => 'yesterday']); + $this->assertEqual($scheduled_manager->total($webform_schedule), 3); + $this->assertEqual($scheduled_manager->stats(), [ + 'total' => 3, + 'waiting' => 0, + 'queued' => 0, + 'ready' => 3, + ]); + + // Add condition to only send yesterday email if 'value' is filled. + /** @var \Drupal\webform\Plugin\WebformHandlerInterface $yesterday_handler */ + $yesterday_handler = $webform_schedule->getHandler('yesterday'); + $conditions = ['enabled' => [':input[name="value"]' => ['filled' => TRUE]]]; + $yesterday_handler->setConditions($conditions); + // NOTE: Executing $webform_schedule->save() throws the below + // unexplainable error. + // + // TypeError: Argument 1 passed to + // Drupal\webform\WebformSubmissionConditionsValidator::validateConditions() + // must be of the type array, null given + // $webform_schedule->save() ; + // + // Check that 3 yesterday scheduled emails are skipped and removed. + $stats = $scheduled_manager->cron(); + $this->assertEqual($stats['skipped'], 3); + $this->assertEqual($scheduled_manager->stats(), [ + 'total' => 0, + 'waiting' => 0, + 'queued' => 0, + 'ready' => 0, + ]); + + // Clear yesterday conditions. + $yesterday_handler->setConditions([]); + + /**************************************************************************/ + // Ignore past scheduling. + /**************************************************************************/ + + // Purge all submissions. + $this->purgeSubmissions(); + + // Check last year email can't be scheduled. + $sid = $this->postSubmission($webform_schedule, ['send' => 'last_year']); + $this->assertEqual($scheduled_manager->total($webform_schedule), 0); + $this->assertRaw('<em class="placeholder">Test: Handler: Test scheduled email: Submission #' . $sid . '</em>: Email <b>ignored</b> by <em class="placeholder">Last year</em> handler to be sent on <em class="placeholder">2016-01-01</em>.'); + /**************************************************************************/ // Source entity scheduling. /**************************************************************************/ @@ -244,6 +323,25 @@ public function testWebformScheduledEmail() { // Run cron to trigger unscheduling. $scheduled_manager->cron(); $this->assertEqual($scheduled_manager->total(), 0); + + // Purge all submissions. + $this->purgeSubmissions(); + + /**************************************************************************/ + // Testing. + /**************************************************************************/ + + $this->drupalLogin($this->rootUser); + + // Check 'Other' email will be sent immediately message when testing. + $this->drupalGet('/webform/test_handler_scheduled_email/test'); + $this->assertRaw('The <em class="placeholder">Other</em> email will be sent immediately upon submission.'); + + // Check 'Other' email is sent immediately via testing. + $this->drupalPostForm('webform/test_handler_scheduled_email/test', ['send' => 'other', 'date[date]' => '2101-01-01'], t('Submit')); + $this->assertEqual($scheduled_manager->total(), 0); + $this->assertRaw('Webform submission from: </em> sent to <em class="placeholder">simpletest@example.com</em> from <em class="placeholder">Drupal</em> [<em class="placeholder">simpletest@example.com</em>'); + $this->assertRaw('Debug: Email: Other'); } /** diff --git a/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTranslationTest.php b/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTranslationTest.php new file mode 100644 index 0000000000..54a0e1ec69 --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/src/Tests/WebformScheduledEmailTranslationTest.php @@ -0,0 +1,56 @@ +<?php + +namespace Drupal\webform_scheduled_email\Tests; + +use Drupal\webform\Entity\Webform; +use Drupal\webform_node\Tests\WebformNodeTestBase; + +/** + * Tests for webform scheduled email handler translation. + * + * @group WebformScheduledEmail + */ +class WebformScheduledEmailTranslationTest extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_scheduled_email', 'webform_scheduled_email_test_translation']; + + /** + * Tests webform schedule email handler translation. + */ + public function testWebformScheduledEmailTranslation() { + $webform_schedule = Webform::load('test_handler_scheduled_translate'); + + /** @var \Drupal\webform_scheduled_email\WebformScheduledEmailManagerInterface $scheduled_manager */ + $scheduled_manager = \Drupal::service('webform_scheduled_email.manager'); + + /**************************************************************************/ + + // Scheduled English email. + $this->drupalPostForm('webform/' . $webform_schedule->id(), [], t('Submit')); + + // Send email. + $scheduled_manager->cron(); + + // Check that scheduled English email as sent in English. + $sent_email = $this->getLastEmail(); + $this->assertEqual($sent_email['subject'], 'English Subject'); + $this->assertEqual($sent_email['body'], 'English Body' . PHP_EOL); + + // Scheduled Spanish email. + $this->drupalPostForm('es/webform/' . $webform_schedule->id(), [], t('Submit')); + + // Send email. + $scheduled_manager->cron(); + + // Check that scheduled Spanish email as sent in Spanish. + $sent_email = $this->getLastEmail(); + $this->assertEqual($sent_email['subject'], 'Spanish Subject'); + $this->assertEqual($sent_email['body'], 'Spanish Body' . PHP_EOL); + } + +} diff --git a/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManager.php b/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManager.php index 3a7b959151..d77ef1ff47 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManager.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManager.php @@ -2,16 +2,18 @@ namespace Drupal\webform_scheduled_email; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\Delete as QueryDelete; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformTokenManagerInterface; -use Psr\Log\LoggerInterface; /** * Defines the webform scheduled email manager. @@ -22,6 +24,13 @@ class WebformScheduledEmailManager implements WebformScheduledEmailManagerInterf use StringTranslationTrait; + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + /** * The database connection. * @@ -29,6 +38,20 @@ class WebformScheduledEmailManager implements WebformScheduledEmailManagerInterf */ protected $database; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + /** * The configuration object factory. * @@ -50,13 +73,6 @@ class WebformScheduledEmailManager implements WebformScheduledEmailManagerInterf */ protected $submissionStorage; - /** - * Logger service. - * - * @var \Drupal\Core\Logger\LoggerChannelInterface - */ - protected $logger; - /** * The token manager. * @@ -67,23 +83,29 @@ class WebformScheduledEmailManager implements WebformScheduledEmailManagerInterf /** * Constructs a WebformScheduledEmailManager object. * + * @param \Drupal\Component\Datetime\TimeInterface $time + * The time service. * @param \Drupal\Core\Database\Connection $database * The database connection. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration object factory. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity manager. - * @param \Psr\Log\LoggerInterface $logger - * A logger instance. + * The entity type manager. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. */ - public function __construct(Connection $database, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, WebformTokenManagerInterface $token_manager) { + public function __construct(TimeInterface $time, Connection $database, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, EntityTypeManagerInterface $entity_type_manager, WebformTokenManagerInterface $token_manager) { + $this->time = $time; $this->database = $database; + $this->languageManager = $language_manager; $this->configFactory = $config_factory; + $this->loggerFactory = $logger_factory; $this->webformStorage = $entity_type_manager->getStorage('webform'); $this->submissionStorage = $entity_type_manager->getStorage('webform_submission'); - $this->logger = $logger; $this->tokenManager = $token_manager; } @@ -91,6 +113,34 @@ public function __construct(Connection $database, ConfigFactoryInterface $config // Scheduled message functions. /****************************************************************************/ + /** + * {@inheritdoc} + */ + public function getDateType() { + return $this->configFactory->get('webform_scheduled_email.settings')->get('schedule_type'); + } + + /** + * {@inheritdoc} + */ + public function getDateTypeLabel() { + return ($this->getDateType() === 'datetime') ? $this->t('date/time') : $this->t('date'); + } + + /** + * {@inheritdoc} + */ + public function getDateFormat() { + return ($this->getDateType() === 'datetime') ? 'Y-m-d H:i:s' : 'Y-m-d'; + } + + /** + * {@inheritdoc} + */ + public function getDateFormatLabel() { + return ($this->getDateType() === 'datetime') ? 'YYYY-MM-DD HH:MM:SS' : 'YYYY-MM-DD'; + } + /** * {@inheritdoc} */ @@ -133,6 +183,8 @@ public function getSendDate(WebformSubmissionInterface $webform_submission, $han // WORKAROUND: // Convert [*:html_date] to [*:custom:Y-m-d]. $send = preg_replace('/^\[(date|webform_submission:(?:[^:]+)):html_date\]$/', '[\1:custom:Y-m-d]', $send); + // Convert [*:html_datetime] to [*:custom:Y-m-d H:i:s]. + $send = preg_replace('/^\[(date|webform_submission:(?:[^:]+)):html_datetime\]$/', '[\1:custom:Y-m-d H:i:s]', $send); // Replace tokens. $send = $this->tokenManager->replace($send, $webform_submission); @@ -147,7 +199,8 @@ public function getSendDate(WebformSubmissionInterface $webform_submission, $han if ($days) { date_add($date, date_interval_create_from_date_string("$days days")); } - return date_format($date, 'Y-m-d'); + + return date_format($date, $this->getDateFormat()); } /****************************************************************************/ @@ -180,6 +233,12 @@ public function schedule(EntityInterface $entity, $handler_id) { return self::EMAIL_UNSCHEDULED; } + // Check if action should be triggered in the past. + if (!empty($handler_configuration['settings']['ignore_past']) && $send_timestamp < $this->time->getRequestTime()) { + $this->unschedule($webform_submission, $handler_id); + return self::EMAIL_IGNORED; + } + // Check recipient. if (!$handler->hasRecipient($webform_submission, $handler->getMessage($webform_submission))) { $this->unschedule($webform_submission, $handler_id); @@ -224,29 +283,18 @@ public function schedule(EntityInterface $entity, $handler_id) { return $status; } - // Log message in Drupal's log. + $channel = ($webform->hasSubmissionLog()) ? 'webform_submission' : 'webform'; $context = [ + '@title' => $webform_submission->label(), '@action' => $action, - '%submission' => $webform_submission->label(), - '%handler' => $handler->label(), - '%date' => $send_iso_date, + '@handler' => $handler->label(), + '@date' => $send_iso_date, 'link' => $webform_submission->toLink($this->t('View'))->toString(), + 'webform_submission' => $webform_submission, + 'handler_id' => $handler_id, + 'operation' => 'email ' . $action, ]; - $this->logger->notice('%submission: Email @action by %handler handler to be sent on %date.', $context); - - // Log scheduled email to the submission log table. - if ($webform->hasSubmissionLog()) { - $t_args = [ - '@action' => $action, - '@handler' => $handler->label(), - '@date' => $send_iso_date, - ]; - $this->submissionStorage->log($webform_submission, [ - 'handler_id' => $handler_id, - 'operation' => 'email ' . $action, - 'message' => $this->t("Email @action by '@handler' handler to be sent on @date.", $t_args), - ]); - } + $this->getLogger($channel)->notice("@title: Email @action by '@handler' handler to be sent on @date.", $context); return $status; } @@ -308,22 +356,16 @@ public function unschedule(EntityInterface $entity, $handler_id = NULL) { $query->execute(); // Log message in submission's log. - if ($webform->hasSubmissionLog()) { - $t_args = ['@handler' => $handler->label()]; - $this->submissionStorage->log($webform_submission, [ - 'handler_id' => $handler_id, - 'operation' => 'email unscheduled', - 'message' => $this->t("Email unscheduled for '@handler' handler.", $t_args), - ]); - } - - // Log message in Drupal's log. + $channel = ($webform->hasSubmissionLog()) ? 'webform_submission' : 'webform'; $context = [ - '%submission' => $webform_submission->label(), - '%handler' => $handler->label(), + '@title' => $webform_submission->label(), + '@handler' => $handler->label(), 'link' => $webform_submission->toLink($this->t('View'))->toString(), + 'webform_submission' => $webform_submission, + 'handler_id' => $handler_id, + 'operation' => 'email unscheduled', ]; - $this->logger->notice('%submission: Email unscheduled for %handler handler.', $context); + $this->getLogger($channel)->notice("@title: Email unscheduled for '@handler' handler.", $context); } elseif ($entity instanceof WebformInterface) { $webform = $entity; @@ -433,6 +475,7 @@ public function cron(EntityInterface $entity = NULL, $handler_id = NULL, $schedu self::EMAIL_UNSCHEDULED => $this->t('unscheduled'), self::EMAIL_SENT => $this->t('sent'), self::EMAIL_NOT_SENT => $this->t('not sent'), + self::EMAIL_SKIPPED => $this->t('skipped'), ]; $summary = []; foreach ($stats as $type => $total) { @@ -453,7 +496,7 @@ public function cron(EntityInterface $entity = NULL, $handler_id = NULL, $schedu $message = "@entity: Cron task executed '@handler' handler. (@summary)"; } }; - $this->logger->notice($message, $context); + $this->getLogger()->notice($message, $context); $stats['_message'] = $message; $stats['_context'] = $context; @@ -566,6 +609,7 @@ protected function cronSend(EntityInterface $entity = NULL, $handler_id = NULL, $stats = [ self::EMAIL_SENT => 0, self::EMAIL_NOT_SENT => 0, + self::EMAIL_SKIPPED => 0, ]; if (empty($limit)) { return $stats; @@ -594,51 +638,84 @@ protected function cronSend(EntityInterface $entity = NULL, $handler_id = NULL, $eids = []; foreach ($result as $record) { + $sid = $record->sid; + $webform_id = $record->webform_id; + $handler_id = $record->handler_id; + $eids[] = $record->eid; /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ - $webform_submission = $this->submissionStorage->load($record->sid); + $webform_submission = $this->submissionStorage->load($sid); // This should rarely happen and the orphaned record will be deleted. if (!$webform_submission) { continue; } $webform = $webform_submission->getWebform(); - $handler_id = $record->handler_id; /** @var \Drupal\webform_scheduled_email\Plugin\WebformHandler\ScheduleEmailWebformHandler $handler */ - $handler = $webform->getHandler($handler_id); + $handler = $webform_submission->getWebform()->getHandler($handler_id); // This should rarely happen and the orphaned record will be deleted. if (!$handler) { continue; } - // Get and send message. - $message = $handler->getMessage($webform_submission); - $status = $handler->sendMessage($webform_submission, $message); + if (!$handler->checkConditions($webform_submission)) { + // Skip sending email. + $action = $this->t('skipped (conditions not met)'); + $operation = 'scheduled email skipped'; + $stat = self::EMAIL_SKIPPED; + } + else { + // Switch to submission language. + $original_language = $this->languageManager->getConfigOverrideLanguage(); + $switch_languages = ($webform_submission->language()->getId() !== $original_language->getId()); + if ($switch_languages) { + $this->languageManager->setConfigOverrideLanguage($webform_submission->language()); + // Reset the webform, submission, and handler. + $this->webformStorage->resetCache([$webform_id]); + $this->submissionStorage->resetCache([$sid]); + // Reload the webform, submission, and handler. + $webform = $this->webformStorage->load($webform_id); + $webform_submission = $this->submissionStorage->load($sid); + $handler = $webform->getHandler($handler_id); + } - $action = ($status) ? $this->t('sent') : $this->t('not sent'); + // Send (translated) email. + $message = $handler->getMessage($webform_submission); + $status = $handler->sendMessage($webform_submission, $message); + + // Switch back to original language. + if ($switch_languages) { + $this->languageManager->setConfigOverrideLanguage($original_language); + // Reset the webform, submission, and handler. + $this->webformStorage->resetCache([$webform_id]); + $this->submissionStorage->resetCache([$sid]); + // Reload the webform, submission, and handler. + $webform = $this->webformStorage->load($webform_id); + $webform_submission = $this->submissionStorage->load($sid); + $handler = $webform->getHandler($handler_id); + } - // Log scheduled email sent to submission log table. - if ($webform->hasSubmissionLog()) { - $t_args = ['@action' => $action, '@handler' => $handler->label()]; - $this->submissionStorage->log($webform_submission, [ - 'handler_id' => $handler_id, - 'operation' => 'scheduled email sent', - 'message' => $this->t('Scheduled email @action for @handler handler.', $t_args), - ]); + $action = ($status) ? $this->t('sent') : $this->t('not sent'); + $operation = ($status) ? $this->t('scheduled email sent') : $this->t('scheduled email not sent'); + $stat = ($status) ? self::EMAIL_SENT : self::EMAIL_NOT_SENT; } - // Log message in Drupal's log. + $channel = ($webform->hasSubmissionLog()) ? 'webform_submission' : 'webform'; $context = [ + '@title' => $webform_submission->label(), '@action' => $action, - '%submission' => $webform->label(), - '%handler' => $handler->label(), + '@handler' => $handler->label(), 'link' => $webform_submission->toLink($this->t('View'))->toString(), + 'webform_submission' => $webform_submission, + 'handler_id' => $handler_id, + 'operation' => $operation, ]; - $this->logger->notice('%submission: Scheduled email @action for %handler handler.', $context); + $this->getLogger($channel)->notice('Scheduled email @action for @handler handler.', $context); - $stats[$stats ? self::EMAIL_SENT : self::EMAIL_NOT_SENT]++; + // Increment stat. + $stats[$stat]++; } // Delete sent emails from table. @@ -703,6 +780,19 @@ public function total(EntityInterface $entity = NULL, $handler_id = NULL, $state // Helper functions. /****************************************************************************/ + /** + * Get webform or webform_submission logger. + * + * @param string $channel + * The logger channel. Defaults to 'webform'. + * + * @return \Drupal\Core\Logger\LoggerChannelInterface + * Webform logger + */ + protected function getLogger($channel = 'webform') { + return $this->loggerFactory->get($channel); + } + /** * Inspects an entity and returns the associates webform, webform submission, and/or source entity. * diff --git a/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManagerInterface.php b/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManagerInterface.php index 5b2aa03a8c..748d687cb3 100644 --- a/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManagerInterface.php +++ b/web/modules/webform/modules/webform_scheduled_email/src/WebformScheduledEmailManagerInterface.php @@ -111,6 +111,20 @@ interface WebformScheduledEmailManagerInterface { */ const EMAIL_UNSCHEDULED = 'unscheduled'; + /** + * Denote email being ignored. + * + * @var string + */ + const EMAIL_IGNORED = 'ignored'; + + /** + * Denote email being skipped. + * + * @var string + */ + const EMAIL_SKIPPED = 'skipped'; + /** * Denote email being sent. * @@ -129,6 +143,38 @@ interface WebformScheduledEmailManagerInterface { // Scheduled message functions. /****************************************************************************/ + /** + * Get scheduled email date type (date or datetime). + * + * @return string + * Scheduled email date type (date or datetime). + */ + public function getDateType(); + + /** + * Get scheduled email date label (date or date/time). + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string + * Scheduled email date label (date or date/time). + */ + public function getDateTypeLabel(); + + /** + * Get scheduled email date format (Y-m-d or Y-m-d H:i:s). + * + * @return string + * Scheduled email date format (Y-m-d or Y-m-d H:i:s). + */ + public function getDateFormat(); + + /** + * Get scheduled email date format label (YYYY-DD-MM or YYYY-DD-MM HH:MM:SS). + * + * @return string + * Scheduled email date format label (YYYY-DD-MM or YYYY-DD-MM HH:MM:SS). + */ + public function getDateFormatLabel(); + /** * Determine if submission has scheduled email for specified handler. * @@ -164,8 +210,8 @@ public function load(WebformSubmissionInterface $webform_submission, $handler_id * A webform handler id. * * @return string|bool - * A send date using ISO date format (YYYY-MM-DD) or FALSE if the send date - * is invalid. + * A send date using ISO date (YYYY-MM-DD) or datetime + * format (YYYY-MM-DD HH:MM:SS) or FALSE if the send date is invalid. */ public function getSendDate(WebformSubmissionInterface $webform_submission, $handler_id); diff --git a/web/modules/webform/modules/webform_scheduled_email/templates/webform-handler-scheduled-email-summary.html.twig b/web/modules/webform/modules/webform_scheduled_email/templates/webform-handler-scheduled-email-summary.html.twig index 117e0a5985..04f8b47e19 100644 --- a/web/modules/webform/modules/webform_scheduled_email/templates/webform-handler-scheduled-email-summary.html.twig +++ b/web/modules/webform/modules/webform_scheduled_email/templates/webform-handler-scheduled-email-summary.html.twig @@ -18,6 +18,10 @@ <b>{{ 'Send on:'|t }}</b> {{ settings.send }}{% if settings.days %} {{ settings.days > 0 ? '+' : ''}}{{ settings.days }} {{ 'days'|t }}{% endif %}<br /> <b>{{ 'Unschedule:'|t }}</b> {{ settings.unschedule ? 'Yes'|t : 'No'|t }}<br /> +<b>{{ 'Ignore past:'|t }}</b> {{ settings.ignore_past ? 'Yes'|t : 'No'|t }}<br /> +{% if settings.test_send %} + <em>{{ 'Email will be sent immediately when testing this webform'|t }}</em><br /> +{% endif %} <hr /> {% include '@webform/webform-handler-email-summary.html.twig' %} {% if status %}{{ status }}{% endif %} diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/config/install/webform.webform.test_handler_scheduled_email.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/config/install/webform.webform.test_handler_scheduled_email.yml index 567f2afd80..ac4c817d51 100644 --- a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/config/install/webform.webform.test_handler_scheduled_email.yml +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/config/install/webform.webform.test_handler_scheduled_email.yml @@ -8,13 +8,18 @@ dependencies: - webform_scheduled_email open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_scheduled_email title: 'Test: Handler: Test scheduled email' description: 'Test webform schedule email handler.' category: null elements: | + value: + '#type': textfield + '#title': value send: '#type': radios '#title': Send @@ -24,11 +29,11 @@ elements: | last_year: 'Last year' draft_reminder: 'Draft reminder (+14 days)' broken: Broken - other: Other... + other: Other… date: '#type': datetime '#title': Date - '#description': 'Time will be ignored.' + '#description': 'Time maybe be ignored depending on schedule type configuration (/admin/structure/webform/config/handlers).' '#default_value': Now '#states': visible: @@ -42,6 +47,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -49,6 +55,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -64,22 +71,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: true + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -90,6 +112,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -108,9 +131,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -163,6 +188,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: tomorrow: id: scheduled_email @@ -182,25 +211,29 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false + theme_name: '' twig: false debug: true reply_to: '' return_path: '' sender_mail: '' sender_name: '' - send: '[webform_submission:completed:html_date]' + send: '[webform_submission:completed:html_datetime]' days: 1 unschedule: false + ignore_past: false + test_send: false yesterday: id: scheduled_email label: Yesterday @@ -219,25 +252,29 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' sender_mail: '' sender_name: '' - send: '[webform_submission:completed:html_date]' + send: '[webform_submission:completed:html_datetime]' days: -1 unschedule: false + ignore_past: false + test_send: false last_year: id: scheduled_email label: 'Last year' @@ -256,17 +293,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -275,6 +314,8 @@ handlers: send: '2016-01-01' days: 0 unschedule: false + ignore_past: true + test_send: false broken: id: scheduled_email label: Broken @@ -293,17 +334,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -312,6 +355,8 @@ handlers: send: '[broken]' days: 0 unschedule: false + ignore_past: false + test_send: false other: id: scheduled_email label: Other @@ -332,25 +377,29 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' sender_mail: '' sender_name: '' - send: '[webform_submission:values:date:html_date]' + send: '[webform_submission:values:date:html_datetime]' days: 14 unschedule: false + ignore_past: false + test_send: true draft_reminder: id: scheduled_email label: 'Draft reminder' @@ -369,22 +418,26 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' sender_mail: '' sender_name: '' - send: '[date:html_date]' + send: '[date:html_datetime]' days: 14 unschedule: true + ignore_past: false + test_send: false diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/webform_scheduled_email_test.info.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/webform_scheduled_email_test.info.yml index 830ef6828b..6b01bfe452 100644 --- a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/webform_scheduled_email_test.info.yml +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test/webform_scheduled_email_test.info.yml @@ -7,8 +7,8 @@ dependencies: - 'webform:webform' - 'webform:webform_scheduled_email' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language.entity.es.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language.entity.es.yml new file mode 100644 index 0000000000..2d9fc7e259 --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language.entity.es.yml @@ -0,0 +1,8 @@ +langcode: en +status: true +dependencies: { } +id: es +label: Spanish +direction: ltr +weight: 1 +locked: false diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language/es/webform.webform.test_handler_scheduled_translate.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language/es/webform.webform.test_handler_scheduled_translate.yml new file mode 100644 index 0000000000..c608c04e02 --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/language/es/webform.webform.test_handler_scheduled_translate.yml @@ -0,0 +1,5 @@ +handlers: + yesterday: + settings: + subject: 'Spanish Subject' + body: 'Spanish Body' diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/webform.webform.test_handler_scheduled_translate.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/webform.webform.test_handler_scheduled_translate.yml new file mode 100644 index 0000000000..a547d2cebe --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/config/install/webform.webform.test_handler_scheduled_translate.yml @@ -0,0 +1,215 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_scheduled_email_test_translation + module: + - webform_scheduled_email +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_handler_scheduled_translate +title: 'Test: Handler: Test scheduled email translation' +description: 'Test webform schedule email handler translation.' +category: null +elements: | + value: + '#type': textfield + '#title': value +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: true + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + yesterday: + id: scheduled_email + label: Yesterday + handler_id: yesterday + status: true + conditions: { } + weight: -50 + settings: + states: + - completed + to_mail: _default + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: 'English Subject' + body: 'English Body' + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: false + twig: false + theme_name: '' + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' + send: '[webform_submission:completed:html_datetime]' + days: -1 + unschedule: false + ignore_past: false + test_send: false diff --git a/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/webform_scheduled_email_test_translation.info.yml b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/webform_scheduled_email_test_translation.info.yml new file mode 100644 index 0000000000..15b231361f --- /dev/null +++ b/web/modules/webform/modules/webform_scheduled_email/tests/modules/webform_scheduled_email_test_translation/webform_scheduled_email_test_translation.info.yml @@ -0,0 +1,17 @@ +name: 'Webform scheduled email module translation tests' +type: module +description: 'Support module for webform schedule email translation that provides test forms.' +package: Testing +# core: 8.x +dependencies: + - 'drupal:config_translation' + - 'drupal:language' + - 'drupal:locale' + - 'webform:webform' + - 'webform:webform_scheduled_email' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.info.yml b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.info.yml index 87c84e3ba7..8cd301cae4 100644 --- a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.info.yml +++ b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.info.yml @@ -6,8 +6,8 @@ package: Webform dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.install b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.install index 63841ad373..6df1fe8fc3 100644 --- a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.install +++ b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.install @@ -7,6 +7,9 @@ use Drupal\Core\Entity\EntityTypeInterface; +// Webform install helper functions. +include_once __DIR__ . '/../../includes/webform.install.inc'; + /** * Implements hook_schema(). */ @@ -71,3 +74,27 @@ function webform_scheduled_email_schema() { ]; return $schema; } + +/** + * Update schema config to add new "past actions" item. + */ +function webform_scheduled_email_update_8001() { + _webform_update_webform_handler_settings(); +} + +/** + * Issue #3013016: Set a time for the schedule email handler. + */ +function webform_scheduled_email_update_8002() { + \Drupal::configFactory() + ->getEditable('webform_scheduled_email.settings') + ->set('schedule_type', 'date') + ->save(); +} + +/** + * Issue #3014338: Add a setting to the Scheduled Email handler to allows emails to be sent immediately when a webform is being tested. + */ +function webform_scheduled_email_update_8003() { + _webform_update_webform_handler_settings(); +} diff --git a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.module b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.module index d6d1dad232..470755f887 100644 --- a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.module +++ b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.module @@ -6,6 +6,7 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; @@ -64,3 +65,39 @@ function webform_scheduled_email_theme() { ], ]; } + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function webform_scheduled_email_form_webform_admin_config_handlers_form_alter(&$form, FormStateInterface $form_state) { + $form['webform_scheduled_email'] = [ + '#type' => 'details', + '#title' => t('Scheduled email settings'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['webform_scheduled_email']['schedule_type'] = [ + '#type' => 'select', + '#title' => t('Date type'), + '#description' => t('Scheduled emails are queued and sent via hourly <a href="@href">cron tasks</a>. To schedule an email for a specific time, requires site administrators to increase the cron task execution frequency.', ['@href' => 'https://www.drupal.org/docs/8/cron-automated-tasks/cron-automated-tasks-overview']), + '#options' => [ + 'date' => t('Date (@format)', ['@format' => 'YYYY-MM-DD']), + 'datetime' => t('Date/time (@format)', ['@format' => 'YYYY-MM-DD HH:MM:SS']), + ], + '#required' => TRUE, + '#default_value' => \Drupal::config('webform_scheduled_email.settings')->get('schedule_type'), + ]; + + $form['#submit'][] = '_webform_scheduled_email_form_webform_admin_config_handlers_form_submit'; +} + +/** + * Submit callback for Scheduled email settings. + */ +function _webform_scheduled_email_form_webform_admin_config_handlers_form_submit(&$form, FormStateInterface $form_state) { + $values = $form_state->getValue('webform_scheduled_email'); + \Drupal::configFactory() + ->getEditable('webform_scheduled_email.settings') + ->set('schedule_type', $values['schedule_type']) + ->save(); +} diff --git a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.services.yml b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.services.yml index e11cec734c..ddfa4d710a 100644 --- a/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.services.yml +++ b/web/modules/webform/modules/webform_scheduled_email/webform_scheduled_email.services.yml @@ -1,4 +1,4 @@ services: webform_scheduled_email.manager: class: Drupal\webform_scheduled_email\WebformScheduledEmailManager - arguments: ['@database', '@config.factory', '@entity_type.manager', '@logger.channel.webform', '@webform.token_manager'] + arguments: ['@datetime.time', '@database', '@language_manager', '@config.factory', '@logger.factory', '@entity_type.manager', '@webform.token_manager'] diff --git a/web/modules/webform/modules/webform_shortcuts/config/install/webform_shortcuts.settings.yml b/web/modules/webform/modules/webform_shortcuts/config/install/webform_shortcuts.settings.yml new file mode 100644 index 0000000000..b4864d4c9a --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/config/install/webform_shortcuts.settings.yml @@ -0,0 +1,6 @@ +add_element: 'ctrl+e' +add_page: 'ctrl+p' +add_layout: 'ctrl+l' +save_elements: 'ctrl+s' +reset_elements: 'ctrl+r' +toggle_weights: 'ctrl+w' diff --git a/web/modules/webform/modules/webform_shortcuts/config/schema/webform_shortcuts.settings.schema.yml b/web/modules/webform/modules/webform_shortcuts/config/schema/webform_shortcuts.settings.schema.yml new file mode 100644 index 0000000000..79e19fe716 --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/config/schema/webform_shortcuts.settings.schema.yml @@ -0,0 +1,22 @@ +webform_shortcuts.settings: + type: config_object + label: 'Webform shortcuts settings' + mapping: + add_element: + type: string + label: 'Add element' + add_page: + type: string + label: 'Add page' + add_layout: + type: string + label: 'Add layout' + save_elements: + type: string + label: 'Save element or elements' + reset_elements: + type: string + label: 'Reset elements' + toggle_weights: + type: string + label: 'Show/hide row weights' diff --git a/web/modules/webform/modules/webform_shortcuts/js/webform_shortcuts.js b/web/modules/webform/modules/webform_shortcuts/js/webform_shortcuts.js new file mode 100644 index 0000000000..cf261eba5d --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/js/webform_shortcuts.js @@ -0,0 +1,62 @@ +/** + * @file + * JavaScript behaviors for webform builder shortcuts. + * + * @see webform_shortcuts_preprocess_block() + */ + +(function ($, drupalSettings) { + + 'use strict'; + + var shortcuts = drupalSettings.webform.shortcuts; + + // Add element. + if (shortcuts.addElement) { + $(document).bind('keydown', shortcuts.addElement, function () { + $('#webform-ui-add-element').click(); + }); + } + + // Add page. + if (shortcuts.addPage) { + $(document).bind('keydown', shortcuts.addPage, function () { + $('#webform-ui-add-page').focus().click(); + }); + } + + // Add layout. + if (shortcuts.addLayout) { + $(document).bind('keydown', shortcuts.addLayout, function () { + $('#webform-ui-add-layout').click(); + }); + } + + // Save element or elements. + if (shortcuts.saveElements) { + $(document).bind('keydown', shortcuts.saveElements, function () { + var $dialogSubmit = $('form.webform-ui-element-form [data-drupal-selector="edit-submit"]'); + if ($dialogSubmit.length) { + $dialogSubmit.click(); + } + else { + $('form.webform-edit-form [data-drupal-selector="edit-submit"]').click(); + } + }); + } + + // Reset elements. + if (shortcuts.resetElements) { + $(document).bind('keydown', shortcuts.resetElements, function () { + $('form.webform-edit-form [data-drupal-selector="edit-reset"]').click(); + }); + } + + // Toggle weight. + if (shortcuts.toggleWeights) { + $(document).bind('keydown', shortcuts.toggleWeights, function () { + $('.tabledrag-toggle-weight').eq(0).click(); + }); + } + +})(jQuery, drupalSettings); diff --git a/web/modules/webform/modules/webform_shortcuts/tests/src/Functional/WebformShortcutsFunctionalTest.php b/web/modules/webform/modules/webform_shortcuts/tests/src/Functional/WebformShortcutsFunctionalTest.php new file mode 100644 index 0000000000..39f11e6938 --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/tests/src/Functional/WebformShortcutsFunctionalTest.php @@ -0,0 +1,54 @@ +<?php + +namespace Drupal\Tests\webform_shortcuts\Functional; + +use Drupal\Tests\webform\Functional\WebformBrowserTestBase; + +/** + * Webform shortcuts test. + * + * @group webform_browser + */ +class WebformShortcutsFunctionalTest extends WebformBrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'block', + 'webform', + 'webform_ui', + 'webform_shortcuts', + ]; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->placeBlocks(); + } + + /** + * Test shortcuts. + */ + public function testShortcuts() { + $this->drupalLogin($this->rootUser); + + // Check default shortcuts. + $this->drupalGet('/admin/structure/webform/manage/contact'); + $this->assertRaw('<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Keyboard shortcuts</div><div class="webform-element-help--content"><hr />CTRL+E = Add element<br />CTRL+P = Add page<br />CTRL+L = Add layout<br /><hr />CTRL+S = Save element or elements<br />CTRL+R = Reset elements<br /><hr />CTRL+W = Show/hide row weights<br /><hr /></div>"><span aria-hidden="true">?</span></span>'); + + // Customize the shortcuts. + $edit = [ + 'webform_shortcuts[add_element]' => 'crtl+z', + 'webform_shortcuts[toggle_weights]' => '', + ]; + $this->drupalPostForm('/admin/structure/webform/config/advanced', $edit, t('Save configuration')); + + // Check customized shortcuts. + $this->drupalGet('/admin/structure/webform/manage/contact'); + $this->assertRaw('<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Keyboard shortcuts</div><div class="webform-element-help--content"><hr />CRTL+Z = Add element<br />CTRL+P = Add page<br />CTRL+L = Add layout<br /><hr />CTRL+S = Save element or elements<br />CTRL+R = Reset elements<br /><hr /></div>"><span aria-hidden="true">?</span></span>'); + } + +} diff --git a/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.info.yml b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.info.yml new file mode 100644 index 0000000000..542b7cf7a8 --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.info.yml @@ -0,0 +1,14 @@ +name: 'Webform Shortcuts' +type: module +description: 'Provides configurable keyboard shortcuts to create and save webform elements.' +package: 'Webform' +# core: 8.x +dependencies: + - 'webform:webform' + - 'webform:webform_ui' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.install b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.install new file mode 100644 index 0000000000..5442d7b5c6 --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.install @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the Webform shortcuts module. + */ + +/** + * Issue #3040822: Allow keyboard shortcuts to be customized or disabled. + */ +function webform_shortcuts_update_8001() { + \Drupal::configFactory() + ->getEditable('webform_shortcuts.settings') + ->setData([ + 'add_element' => 'ctrl+e', + 'add_page' => 'ctrl+p', + 'add_layout' => 'ctrl+l', + 'save_elements' => 'ctrl+s', + 'reset_elements' => 'ctrl+r', + 'toggle_weights' => 'ctrl+w', + ])->save(); +} diff --git a/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.libraries.yml b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.libraries.yml new file mode 100644 index 0000000000..2af2ef049c --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.libraries.yml @@ -0,0 +1,23 @@ +webform_shortcuts: + version: VERSION + js: + js/webform_shortcuts.js: {} + dependencies: + - core/jquery + - webform_shortcuts/libraries.jquery.hotkeys + +# External libraries. + +libraries.jquery.hotkeys: + remote: https://github.com/jeresig/jquery.hotkeys + version: '0.2.0' + license: + name: MIT + url: https://github.com/rvera/image-picker/blob/master/LICENSE + gpl-compatible: true + cdn: + /libraries/jquery.hotkeys/: https://cdn.rawgit.com/jeresig/jquery.hotkeys/0.2.0/ + js: + /libraries/jquery.hotkeys/jquery.hotkeys.js: {} + dependencies: + - core/jquery diff --git a/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.module b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.module new file mode 100644 index 0000000000..a41597c1aa --- /dev/null +++ b/web/modules/webform/modules/webform_shortcuts/webform_shortcuts.module @@ -0,0 +1,161 @@ +<?php + +/** + * @file + * Provides keyboard shortcuts to create and save webform elements. + */ + +use Drupal\Core\Url; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformTextHelper; + +/** + * Implements hook_webform_libraries_info(). + */ +function webform_shortcuts_webform_libraries_info() { + $libraries = []; + $libraries['jquery.hotkeys'] = [ + 'title' => t('jQuery.Hotkeys'), + 'description' => t('jQuery Hotkeys is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination.'), + 'notes' => t('jQuery Hotkeys is used by the form builder to quickly add and save elements.'), + 'homepage_url' => Url::fromUri('https://github.com/jeresig/jquery.hotkeys'), + 'download_url' => Url::fromUri('https://github.com/jeresig/jquery.hotkeys/archive/0.2.0.zip'), + 'version' => '0.2.0', + 'optional' => FALSE, + ]; + return $libraries; +} + +/** + * Implements hook_preprocess_block(). + */ +function webform_shortcuts_preprocess_block(&$variables) { + if ($variables['plugin_id'] != 'local_actions_block') { + return; + } + + if (\Drupal::routeMatch()->getRouteName() !== 'entity.webform.edit_form') { + return; + } + + // Get shortcuts settings. + $config = \Drupal::config('webform_shortcuts.settings'); + + // Shortcuts. + $shortcuts = [ + '--', + 'add_element' => t('Add element'), + 'add_page' => t('Add page'), + 'add_layout' => t('Add layout'), + '--', + 'save_elements' => t('Save element or elements'), + 'reset_elements' => t('Reset elements'), + '--', + 'toggle_weights' => t('Show/hide row weights'), + '--', + ]; + + $items = []; + $last_shortcut = ''; + foreach ($shortcuts as $name => $task) { + if ($task === '--') { + if ($last_shortcut !== $task) { + $items[] = ['#markup' => '<hr/>']; + } + $last_shortcut = $task; + } + else { + $shortcut = $config->get($name); + if ($shortcut) { + $t_args = [ + '@shortcut' => strtoupper($shortcut), + '@task' => $task, + ]; + $items[] = [ + '#markup' => t('@shortcut = @task', $t_args), + '#suffix' => '<br/>', + ]; + $last_shortcut = $name; + } + } + } + + $variables['content']['help'] = [ + '#type' => 'webform_help', + '#help_title' => t('Keyboard shortcuts'), + '#help' => $items, + ]; + + $variables['content']['help']['#attached']['library'][] = 'webform_shortcuts/webform_shortcuts'; + + // Convert shortcuts PHP settings to camelCase JavaScript settings. + $settings = []; + $data = $config->getRawData(); + foreach ($data as $name => $value) { + $settings[WebformTextHelper::snakeToCamel($name)] = $value; + } + $variables['content']['help']['#attached']['drupalSettings']['webform']['shortcuts'] = $settings; + + // Add config to render array caching. + $renderer = \Drupal::service('renderer'); + $renderer->addCacheableDependency($variables['content']['help'], $config); + +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function webform_shortcuts_form_webform_admin_config_advanced_form_alter(&$form, FormStateInterface $form_state) { + $config = \Drupal::config('webform_shortcuts.settings'); + + $form['webform_shortcuts'] = [ + '#type' => 'details', + '#title' => t('Keyboard shortcut settings'), + '#description' => t('Enter custom keyboard shortcuts for common form builder actions. Leave blank to disable an individual shortcut.') . '<br/>' . + t('<a href=":href">Learn more about configuring shortcuts using the jQuery HotKeys library</a>', [':href' => 'https://github.com/jeresig/jquery.hotkeys']), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['webform_shortcuts']['add_element'] = [ + '#type' => 'textfield', + '#title' => t('Add element'), + '#default_value' => $config->get('add_element'), + ]; + $form['webform_shortcuts']['add_page'] = [ + '#type' => 'textfield', + '#title' => t('Add page'), + '#default_value' => $config->get('add_page'), + ]; + $form['webform_shortcuts']['add_layout'] = [ + '#type' => 'textfield', + '#title' => t('Add layout'), + '#default_value' => $config->get('add_layout'), + ]; + $form['webform_shortcuts']['save_elements'] = [ + '#type' => 'textfield', + '#title' => t('Save element or elements'), + '#default_value' => $config->get('save_elements'), + ]; + $form['webform_shortcuts']['reset_elements'] = [ + '#type' => 'textfield', + '#title' => t('Reset elements'), + '#default_value' => $config->get('reset_elements'), + ]; + $form['webform_shortcuts']['toggle_weights'] = [ + '#type' => 'textfield', + '#title' => t('Show/hide row weights'), + '#default_value' => $config->get('reset_elements'), + ]; + + $form['#submit'][] = '_webform_shortcuts_form_webform_admin_config_advanced_form_submit'; +} + +/** + * Submit callback to save the webform shortcuts setting. + */ +function _webform_shortcuts_form_webform_admin_config_advanced_form_submit($form, FormStateInterface $form_state) { + \Drupal::configFactory() + ->getEditable('webform_shortcuts.settings') + ->setData($form_state->getValue('webform_shortcuts')) + ->save(); +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/Controller/WebformSubmissionExportImportController.php b/web/modules/webform/modules/webform_submission_export_import/src/Controller/WebformSubmissionExportImportController.php new file mode 100644 index 0000000000..39a35ad21f --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/Controller/WebformSubmissionExportImportController.php @@ -0,0 +1,177 @@ +<?php + +namespace Drupal\webform_submission_export_import\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Serialization\Yaml; +use Drupal\webform\WebformRequestInterface; +use Drupal\webform\WebformSubmissionGenerateInterface; +use Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Provides route responses for webform submission export/import. + */ +class WebformSubmissionExportImportController extends ControllerBase implements ContainerInjectionInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The webform submission storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $webformSubmissionStorage; + + /** + * Webform request handler. + * + * @var \Drupal\webform\WebformRequestInterface + */ + protected $requestHandler; + + /** + * The webform submission generation service. + * + * @var \Drupal\webform\WebformSubmissionGenerateInterface + */ + protected $generate; + + /** + * The webform submission exporter. + * + * @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface + */ + protected $importer; + + /** + * Constructs a WebformSubmissionExportImportController object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\webform\WebformRequestInterface $request_handler + * The webform request handler. + * @param \Drupal\webform\WebformSubmissionGenerateInterface $submission_generate + * The webform submission generation service. + * @param \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer + * The webform submission importer. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager, WebformRequestInterface $request_handler, WebformSubmissionGenerateInterface $submission_generate, WebformSubmissionExportImportImporterInterface $importer) { + $this->entityTypeManager = $entity_type_manager; + $this->webformSubmissionStorage = $entity_type_manager->getStorage('webform_submission'); + $this->requestHandler = $request_handler; + $this->generate = $submission_generate; + $this->importer = $importer; + + // Initialize the importer. + $webform = $this->requestHandler->getCurrentWebform(); + $source_entity = $this->requestHandler->getCurrentSourceEntity(); + $this->importer->setWebform($webform); + $this->importer->setSourceEntity($source_entity); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('webform.request'), + $container->get('webform_submission.generate'), + $container->get('webform_submission_export_import.importer') + ); + } + + /** + * Returns the Webform submission export example CSV view. + */ + public function view() { + return $this->createResponse(FALSE); + } + + /** + * Returns the Webform submission export example CSV download. + */ + public function download() { + return $this->createResponse(TRUE); + } + + /** + * Create a response containing submission CSV example. + * + * @param bool $download + * TRUE is response should be downloaded. + * + * @return \Symfony\Component\HttpFoundation\Response + * A response containing submission CSV example. + */ + protected function createResponse($download = FALSE) { + $webform = $this->importer->getWebform(); + + // From: http://obtao.com/blog/2013/12/export-data-to-a-csv-file-with-symfony/ + $response = new StreamedResponse(function () { + $handle = fopen('php://output', 'r+'); + + $header = $this->importer->exportHeader(); + fputcsv($handle, $header); + + for ($i = 1; $i <= 3; $i++) { + $webform_submission = $this->generateSubmission($i); + $record = $this->importer->exportSubmission($webform_submission); + fputcsv($handle, $record); + } + + fclose($handle); + }); + + $response->headers->set('Content-Type', $download ? 'text/csv' : 'text/plain'); + $response->headers->set('Content-Disposition', ($download ? 'attachment' : 'inline') . '; filename=' . $webform->id() . '.csv'); + return $response; + } + + /** + * Generate an unsaved webform submission. + * + * @param int $index + * The submission's index used for the sid and serial number. + * + * @return \Drupal\webform\WebformSubmissionInterface + * An unsaved webform submission. + */ + protected function generateSubmission($index) { + $webform = $this->requestHandler->getCurrentWebform(); + $source_entity = $this->requestHandler->getCurrentSourceEntity(); + + $users = $this->entityTypeManager->getStorage('user')->getQuery()->execute(); + $uid = array_rand($users); + + $url = $webform->toUrl(); + if ($source_entity && $source_entity->hasLinkTemplate('canonical')) { + $url = $source_entity->toUrl(); + } + + return $this->webformSubmissionStorage->create([ + 'sid' => $index, + 'serial' => $index, + 'webform_id' => $webform->id(), + 'entity_type' => ($source_entity) ? $source_entity->getEntityTypeId() : '', + 'entity_id' => ($source_entity) ? $source_entity->id() : '', + 'uid' => $uid, + 'remote_addr' => mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255), + 'uri' => preg_replace('#^' . base_path() . '#', '/', $url->toString()), + 'data' => Yaml::encode($this->generate->getData($webform)), + 'created' => strtotime('-1 year'), + 'completed' => rand(strtotime('-1 year'), time()), + 'changed' => time(), + ]); + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/Form/WebformSubmissionExportImportUploadForm.php b/web/modules/webform/modules/webform_submission_export_import/src/Form/WebformSubmissionExportImportUploadForm.php new file mode 100644 index 0000000000..da4a1af8ef --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/Form/WebformSubmissionExportImportUploadForm.php @@ -0,0 +1,727 @@ +<?php + +namespace Drupal\webform_submission_export_import\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\ConfirmFormHelper; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Url; +use Drupal\file\Entity\File; +use Drupal\webform\Element\WebformMessage; +use Drupal\webform\Utility\WebformOptionsHelper; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformRequestInterface; +use Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * Upload webform submission export import CSV. + */ +class WebformSubmissionExportImportUploadForm extends ConfirmFormBase { + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * Webform request handler. + * + * @var \Drupal\webform\WebformRequestInterface + */ + protected $requestHandler; + + /** + * The webform submission exporter. + * + * @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface + */ + protected $importer; + + /** + * Constructs a WebformResultsExportController object. + * + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\webform\WebformRequestInterface $request_handler + * The webform request handler. + * @param \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer + * The webform submission importer. + */ + public function __construct(DateFormatterInterface $date_formatter, WebformRequestInterface $request_handler, WebformSubmissionExportImportImporterInterface $importer) { + $this->dateFormatter = $date_formatter; + $this->requestHandler = $request_handler; + $this->importer = $importer; + + // Initialize the importer. + $webform = $this->requestHandler->getCurrentWebform(); + $source_entity = $this->requestHandler->getCurrentSourceEntity(); + $this->importer->setWebform($webform); + $this->importer->setSourceEntity($source_entity); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('date.formatter'), + $container->get('webform.request'), + $container->get('webform_submission_export_import.importer') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_submission_export_import_upload_form'; + } + + /** + * {@inheritdoc} + */ + public function getFormName() { + return 'webform_upload'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $this->setImportUri($form_state); + if (!$this->getImportUri()) { + return $this->buildUploadForm($form, $form_state); + } + else { + return $this->buildConfirmForm($form, $form_state); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // Form submit handler is not being used. + // @see \Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm::buildUploadForm + // @see \Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm::buildConfirmForm + } + + /****************************************************************************/ + // Upload form. + /****************************************************************************/ + + /** + * Build upload form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * An associative array containing the structure of the form. + */ + protected function buildUploadForm(array $form, FormStateInterface $form_state) { + // Warning. + $form['experimental_warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_id' => 'webform_submission_export_import_experimental', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_STATE, + '#message_message' => $this->t('Importing submissions is a new and experimental feature.') . '<br/><strong>' . $this->t('Please test and review your imported submissions using a development/test server.') . '</strong>', + ]; + + // Details. + $temporary_maximum_age = $this->config('system.file')->get('temporary_maximum_age'); + $form['details'] = [ + 'title' => [ + '#markup' => $this->t('Please note'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('All submission properties and data is optional.'), + $this->t('If UUIDs are included, existing submissions will always be updated.'), + $this->t('If UUIDs are not included, already imported and unchanged records will not create duplication submissions.'), + $this->t('File uploads must use publicly access URLs which begin with http:// or https://.'), + $this->t('Entity references can use UUIDs or entity IDs.'), + $this->t('Composite (single) values are annotated using double underscores. (e.g. ELEMENT_KEY__SUB_ELEMENT_KEY)'), + $this->t('Multiple values are comma delimited with any nested commas URI escaped (%2E).'), + $this->t('Multiple composite values are formatted using <a href=":href">inline YAML</a>.', [':href' => 'https://en.wikipedia.org/wiki/YAML#Basic_components']), + $this->t('Import maximum execution time limit is @time.', ['@time' => $this->dateFormatter->formatInterval($temporary_maximum_age)]), + ], + ], + ]; + + // Examples. + $download_url = $this->requestHandler->getCurrentWebformUrl('webform_submission_export_import.results_import.example.download'); + $view_url = $this->requestHandler->getCurrentWebformUrl('webform_submission_export_import.results_import.example.view'); + $t_args = [ + ':href_download' => $download_url->toString(), + ':href_view' => $view_url->toString(), + ]; + $form['examples'] = [ + '#markup' => $this->t('<a href=":href_view">View</a> or <a href=":href_download">download</a> an example submission CSV.', $t_args), + '#prefix' => '<p>', + '#suffix' => '</p>', + ]; + + // Form. + $form['import'] = [ + '#type' => 'details', + '#title' => $this->t('Import data source'), + '#open' => TRUE, + ]; + $form['import']['import_type'] = [ + '#title' => 'Type', + '#type' => 'radios', + '#prefix' => '<div class="container-inline">', + '#suffix' => '</div>', + '#options' => [ + 'file' => $this->t('File upload'), + 'url' => $this->t('Remote URL'), + ], + '#default_value' => 'file', + ]; + $form['import']['import_file'] = [ + '#type' => 'file', + '#title' => $this->t('Upload Submission CSV file'), + '#states' => [ + 'visible' => [ + ':input[name="import_type"]' => ['value' => 'file'], + ], + 'required' => [ + ':input[name="import_type"]' => ['value' => 'file'], + ], + ], + ]; + $form['import']['import_url'] = [ + '#type' => 'url', + '#title' => $this->t('Enter Submission CSV remote URL'), + '#description' => $this->t('Remote URL could be a <a href=":href">published Google Sheet</a>.', [':href' => 'https://help.aftership.com/hc/en-us/articles/115008490908-CSV-Auto-Fetch-using-Google-Drive-Spreadsheet']), + '#states' => [ + 'visible' => [ + ':input[name="import_type"]' => ['value' => 'url'], + ], + 'required' => [ + ':input[name="import_type"]' => ['value' => 'url'], + ], + ], + ]; + $form['actions'] = [ + '#type' => 'actions', + ]; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Continue'), + '#validate' => ['::validateUploadForm'], + '#submit' => ['::submitUploadForm'], + ]; + + return $form; + } + + /** + * Upload validation handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function validateUploadForm(array &$form, FormStateInterface $form_state) { + $import_type = $form_state->getValue('import_type'); + switch ($import_type) { + case 'file': + $files = $this->getRequest()->files->get('files', []); + if (empty($files['import_file']) || !$files['import_file']->isValid()) { + $form_state->setErrorByName('import_file', $this->t('The file could not be uploaded.')); + } + break; + + case 'url': + // @todo Determine if remote URL needs to be validated. + break; + } + } + + /** + * Upload submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitUploadForm(array &$form, FormStateInterface $form_state) { + $validators = ['file_validate_extensions' => ['csv']]; + + $import_type = $form_state->getValue('import_type'); + + $file = NULL; + switch ($import_type) { + case 'file': + $files = file_save_upload('import_file', $validators); + $file = ($files) ? reset($files) : NULL; + break; + + case 'url': + $import_url = $form_state->getValue('import_url'); + $file_path = tempnam(file_directory_temp(), 'webform_submission_export_import_') . '.csv'; + file_put_contents($file_path, file_get_contents($import_url)); + + $form_field_name = $this->t('Submission CSV (Comma Separated Values) file'); + $file_size = filesize($file_path); + // Mimic Symfony and Drupal's upload file handling. + $file_info = new UploadedFile($file_path, basename($file_path), NULL, $file_size); + $file = _webform_submission_export_import_file_save_upload_single($file_info, $form_field_name, $validators); + break; + } + + // If a managed file has been create to the file's id and rebuild the form. + if ($file) { + // Normalize carriage returns. + // This prevent issues with CSV files created in Excel. + $contents = file_get_contents($file->getFileUri()); + $contents = preg_replace('~\R~u', "\r\n", $contents); + file_put_contents($file->getFileUri(), $contents); + + $this->importer->setImportUri($file->getFileUri()); + if ($this->importer->getTotal()) { + $form_state->set('import_fid', $file->id()); + $form_state->setRebuild(); + } + else { + $this->messenger()->addError($this->t("Uable to parse CSV file. Please review the CSV file's formatting.")); + } + } + } + + /****************************************************************************/ + // Confirm form. + /****************************************************************************/ + + /** + * Build confirm import form. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * An associative array containing the structure of the form. + */ + protected function buildConfirmForm(array $form, FormStateInterface $form_state) { + $import_options = $form_state->get('import_options'); + $form['#disable_inline_form_errors'] = TRUE; + $form['#attributes']['class'][] = 'confirmation'; + $form['#theme'] = 'confirm_form'; + $form[$this->getFormName()] = ['#type' => 'hidden', '#value' => 1]; + + // Warning. + $total = $this->importer->getTotal(); + $t_args = [ + '@submissions' => $this->formatPlural($total, '1 submission', '@total submissions', ['@total' => $total]), + ]; + $form['warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to import @submissions?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; + + // Details. + $actions = [ + $this->t('Update submissions that have a corresponding UUID.'), + $this->t('Create new submissions.'), + ]; + if ($import_options['skip_validation']) { + $actions[] = $this->t('Form validation will be skipped.'); + } + else { + $actions[] = $this->t('Skip submissions that are invalid.'); + } + $form['details'] = [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => $actions, + ], + ]; + + // Mapping. + $source = $this->appendNameToOptions($this->importer->getSourceColumns()); + $destination = $this->appendNameToOptions($this->importer->getDestinationColumns()); + $mappings = $this->importer->getSourceToDestinationColumnMapping(); + $form['review'] = [ + '#type' => 'details', + '#title' => $this->t('Review import'), + ]; + // Displaying when no UUID or token is found. + if (!isset($source['uuid']) && !isset($source['uuid'])) { + $form['review']['warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('No UUID or token was found in the source (CSV). A unique hash will be generated for the each CSV record. Any changes to already an imported record in the source (CSV) will create a new submission.', $t_args), + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_NONE, + ]; + } + $form['review']['mapping'] = [ + '#type' => 'webform_mapping', + '#title' => $this->t('Import mapping'), + '#source__title' => $this->t('Source (CSV)'), + '#destination__title' => $this->t('Destination (Submission)'), + '#description' => $this->t('Please review and select the imported CSV source column to destination element mapping'), + '#description_display' => 'before', + '#default_value' => $mappings, + '#required' => TRUE, + '#source' => $source, + '#destination' => $destination, + '#parents' => ['import_options', 'mapping'], + ]; + + // Options. + $form['import_options'] = [ + '#type' => 'details', + '#title' => $this->t('Import options'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['import_options']['skip_validation'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Skip form validation'), + '#description' => $this->t('Skipping form validation can cause invalid data to be stored in the database.'), + '#return_value' => TRUE, + ]; + $form['import_options']['treat_warnings_as_errors'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Treat all warnings as errors'), + '#description' => $this->t("CSV data that can't be converted to submission data will display a warning. If checked, these warnings will be treated as errors and prevent the submission from being created."), + '#return_value' => TRUE, + ]; + + // Confirm. + $form['confirm'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Yes, I want to import these submissions'), + '#required' => TRUE, + ]; + + // Actions. + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->getConfirmText(), + '#button_type' => 'primary', + '#submit' => ['::submitImportForm'], + ]; + $form['actions']['cancel'] = ConfirmFormHelper::buildCancelLink($this, $this->getRequest()); + return $form; + } + + /** + * Import submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitImportForm(array &$form, FormStateInterface $form_state) { + $this->setImportUri($form_state); + + $webform = $this->requestHandler->getCurrentWebform(); + $source_entity = $this->requestHandler->getCurrentSourceEntity(); + $import_uri = $this->importer->getImportUri(); + $import_options = $form_state->getValue('import_options'); + + $redirect_url = $this->requestHandler->getCurrentWebformUrl('webform.results_submissions'); + $form_state->setRedirectUrl($redirect_url); + + if ($this->importer->requiresBatch()) { + static::batchSet($webform, $source_entity, $import_uri, $import_options); + } + else { + $this->importer->setImportOptions($import_options); + $stats = $this->importer->import(); + static::displayStats($stats); + $this->importer->deleteImportUri(); + } + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + // Do not alter the form's title. + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Import'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return Url::fromRoute('<current>'); + } + + /****************************************************************************/ + // Helper methods. + /****************************************************************************/ + + /** + * Set the CSV file URI. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state containing the upload file id. + */ + protected function setImportUri(FormStateInterface $form_state) { + $fid = $form_state->get('import_fid'); + if (!empty($fid)) { + $file = File::load($fid); + $this->importer->setImportUri($file->getFileUri()); + } + } + + /** + * Get the CSV file URI. + * + * @return string + * The CSV file URI. + */ + protected function getImportUri() { + return $this->importer->getImportUri(); + } + + /** + * Append option name to the displayed value. + * + * @param array $options + * An array of options. + * + * @return array + * An array of options with the option name appended to the displayed value. + */ + protected function appendNameToOptions(array $options) { + foreach ($options as $name => $value) { + if ($name !== (string) $value) { + $options[$name] .= ' [' . $name . ']'; + } + } + return $options; + } + + /****************************************************************************/ + // Batch functions. + // Using static method to prevent the service container from being serialized. + // "Prevents exception 'AssertionError' with message 'The container was serialized.'." + /****************************************************************************/ + + /** + * Batch API; Initialize batch operations. + * + * @param \Drupal\webform\WebformInterface|null $webform + * A webform. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * A webform source entity. + * @param string $import_uri + * The URI of the CSV import file. + * @param array $import_options + * An array of import options. + * + * @see http://www.jeffgeerling.com/blogs/jeff-geerling/using-batch-api-build-huge-csv + */ + public static function batchSet(WebformInterface $webform, EntityInterface $source_entity = NULL, $import_uri = '', array $import_options = []) { + $parameters = [ + $webform, + $source_entity, + $import_uri, + $import_options, + ]; + $batch = [ + 'title' => t('Importing submissions'), + 'init_message' => t('Initializing submission import'), + 'error_message' => t('The import could not be completed because an error occurred.'), + 'operations' => [ + [['\Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm', 'batchProcess'], $parameters], + ], + 'finished' => ['\Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm', 'batchFinish'], + ]; + + batch_set($batch); + } + + /** + * Batch API callback; Write the header and rows of the export to the export file. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * A webform source entity. + * @param string $import_uri + * The URI of the CSV import file. + * @param array $import_options + * An associative array of import options. + * @param mixed|array $context + * The batch current context. + */ + public static function batchProcess(WebformInterface $webform, EntityInterface $source_entity = NULL, $import_uri = '', array $import_options = [], &$context) { + /** @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer */ + $importer = \Drupal::service('webform_submission_export_import.importer'); + $importer->setWebform($webform); + $importer->setSourceEntity($source_entity); + $importer->setImportUri($import_uri); + $importer->setImportOptions($import_options); + + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['offset'] = 0; + $context['sandbox']['max'] = $importer->getTotal(); + // Drush is losing the results so we are going to track theme + // via the sandbox. + $context['sandbox']['stats'] = [ + 'created' => 0, + 'updated' => 0, + 'skipped' => 0, + 'total' => 0, + 'warnings' => [], + 'errors' => [], + ]; + $context['results'] = [ + 'import_uri' => $import_uri, + ]; + } + + // Import CSV records. + $import_stats = $importer->import($context['sandbox']['offset'], $importer->getBatchLimit()); + + // Append import stats and errors to results. + foreach ($import_stats as $import_stat => $value) { + if (is_array($value)) { + // Convert translatable markup into strings to save memory. + $context['sandbox']['stats'][$import_stat] += WebformOptionsHelper::convertOptionsToString($value); + } + else { + $context['sandbox']['stats'][$import_stat] += $value; + } + } + + // Track progress. + $context['sandbox']['progress'] += $import_stats['total']; + $context['sandbox']['offset'] += $importer->getBatchLimit(); + + // Display message. + $context['message'] = t('Imported @count of @total submissions…', ['@count' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]); + + // Track finished. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } + + // Context results are not being passed to batchFinish via Drush, + // therefor we are going to show them when this is finished. + if ($context['finished'] >= 1) { + static::displayStats($context['sandbox']['stats']); + } + } + + /** + * Batch API callback; Completed export. + * + * @param bool $success + * TRUE if batch successfully completed. + * @param array $results + * Batch results. + * @param array $operations + * An array of function calls (not used in this function). + */ + public static function batchFinish($success, array $results, array $operations) { + if (!$success) { + \Drupal::messenger()->addStatus(t('Finished with an error.')); + } + + // Delete import URI. + if (isset($results['import_uri'])) { + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uri' => $results['import_uri']]); + foreach ($files as $file) { + $file->delete(); + } + } + } + + /** + * Disply import status. + * + * @param array $stats + * Import stats. + */ + public static function displayStats(array $stats) { + $is_cli = (PHP_SAPI === 'cli'); + $number_of_errors = 0; + $error_limit = ($is_cli) ? NULL : 50; + $t_args = [ + '@total' => $stats['total'], + '@created' => $stats['created'], + '@updated' => $stats['updated'], + '@skipped' => $stats['skipped'], + ]; + if ($is_cli) { + \Drupal::logger('webform')->notice(t('Submission import completed. (total: @total; created: @created; updated: @updated; skipped: @skipped)', $t_args)); + } + else { + \Drupal::messenger()->addStatus(t('Submission import completed. (total: @total; created: @created; updated: @updated; skipped: @skipped)', $t_args)); + } + $message_types = [ + 'warnings' => MessengerInterface::TYPE_WARNING, + 'errors' => MessengerInterface::TYPE_ERROR, + ]; + foreach ($message_types as $message_group => $message_type) { + foreach ($stats[$message_group] as $row_number => $messages) { + $row_prefix = [ + '#markup' => t('Row #@number', ['@number' => $row_number]), + '#prefix' => $is_cli ? '' : '<strong>', + '#suffix' => $is_cli ? ': ' : ':</strong> ', + ]; + foreach ($messages as $message) { + if ($is_cli) { + $message = strip_tags($message); + } + $build = [ + 'row' => $row_prefix, + 'message' => ['#markup' => $message], + ]; + $message = \Drupal::service('renderer')->renderPlain($build); + if ($is_cli) { + \Drupal::logger('webform_submission_export_import')->$message_type($message); + } + else { + \Drupal::messenger()->addMessage($message, $message_type); + } + if ($error_limit && ++$number_of_errors >= $error_limit) { + return; + } + } + } + } + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/Plugin/WebformExporter/WebformSubmissionExportImportWebformExporter.php b/web/modules/webform/modules/webform_submission_export_import/src/Plugin/WebformExporter/WebformSubmissionExportImportWebformExporter.php new file mode 100644 index 0000000000..4e715b7748 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/Plugin/WebformExporter/WebformSubmissionExportImportWebformExporter.php @@ -0,0 +1,91 @@ +<?php + +namespace Drupal\webform_submission_export_import\Plugin\WebformExporter; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Plugin\WebformExporter\FileHandleTraitWebformExporter; +use Drupal\webform\Plugin\WebformExporterBase; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Defines a machine readable CSV export that can be imported back into the current webform. + * + * @WebformExporter( + * id = "webform_submission_export_import", + * label = @Translation("CSV download"), + * description = @Translation("Exports results in CSV that can be imported back into the current webform."), + * archive = FALSE, + * files = FALSE, + * options = FALSE, + * ) + */ +class WebformSubmissionExportImportWebformExporter extends WebformExporterBase { + + use FileHandleTraitWebformExporter; + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return parent::defaultConfiguration() + [ + 'uuid' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $form['warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('<strong>Warning:</strong> Opening delimited text files with spreadsheet applications may expose you to <a href=":href">formula injection</a> or other security vulnerabilities. When the submissions contain data from untrusted users and the downloaded file will be used with Microsoft Excel, use \'HTML table\' format.', [':href' => 'https://www.google.com/search?q=spreadsheet+formula+injection']), + ]; + $form['uuid'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Use UUIDs for all entity references.'), + '#description' => $this->t("If checked, all entity references will use the entity's UUID"), + '#return_value' => TRUE, + '#default_value' => $this->configuration['uuid'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function getFileExtension() { + return 'csv'; + } + + /** + * {@inheritdoc} + */ + public function writeHeader() { + $header = $this->getImporter()->exportHeader(); + fputcsv($this->fileHandle, $header); + } + + /** + * {@inheritdoc} + */ + public function writeSubmission(WebformSubmissionInterface $webform_submission) { + $record = $this->getImporter()->exportSubmission($webform_submission, $this->configuration); + fputcsv($this->fileHandle, $record); + } + + /** + * Get the submission importer. + * + * @return \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface + * The submission importer. + */ + protected function getImporter() { + /** @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer */ + $importer = \Drupal::service('webform_submission_export_import.importer'); + $importer->setWebform($this->getWebform()); + return $importer; + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/Routing/WebformSubmissionExportImportRouteSubscriber.php b/web/modules/webform/modules/webform_submission_export_import/src/Routing/WebformSubmissionExportImportRouteSubscriber.php new file mode 100644 index 0000000000..1a03709a2c --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/Routing/WebformSubmissionExportImportRouteSubscriber.php @@ -0,0 +1,42 @@ +<?php + +namespace Drupal\webform_submission_export_import\Routing; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Routing\RouteSubscriberBase; +use Symfony\Component\Routing\RouteCollection; + +/** + * Remove webform submission import export routes. + */ +class WebformSubmissionExportImportRouteSubscriber extends RouteSubscriberBase { + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * Constructs a WebformSubmissionLogRouteSubscriber object. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ + public function __construct(ModuleHandlerInterface $module_handler) { + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + protected function alterRoutes(RouteCollection $collection) { + if (!$this->moduleHandler->moduleExists('webform_node')) { + $collection->remove('entity.node.webform_submission_export_import.results_import'); + $collection->remove('entity.node.webform_submission_export_import.results_import.example.download'); + $collection->remove('entity.node.webform_submission_export_import.results_import.example.view'); + } + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporter.php b/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporter.php new file mode 100644 index 0000000000..fcec97da6f --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporter.php @@ -0,0 +1,1172 @@ +<?php + +namespace Drupal\webform_submission_export_import; + +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Site\Settings; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; +use Drupal\webform\Plugin\WebformElement\WebformLikert; +use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase; +use Drupal\webform\Plugin\WebformElementEntityReferenceInterface; +use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionForm; +use Drupal\webform\WebformSubmissionInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\Yaml\Dumper; + +/** + * Webform submission export import manager. + */ +class WebformSubmissionExportImportImporter implements WebformSubmissionExportImportImporterInterface { + + use StringTranslationTrait; + + /** + * The configuration object factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The logger factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Webform submission storage. + * + * @var \Drupal\webform\WebformSubmissionStorageInterface + */ + protected $entityStorage; + + /** + * Webform element manager. + * + * @var \Drupal\webform\Plugin\WebformElementManagerInterface + */ + protected $elementManager; + + /** + * The webform. + * + * @var \Drupal\webform\WebformInterface + */ + protected $webform; + + /** + * The source entity. + * + * @var \Drupal\Core\Entity\EntityInterface + */ + protected $sourceEntity; + + /** + * The import file URI. + * + * @var string + */ + protected $importUri; + + /** + * The total number of records being imported. + * + * @var int + */ + protected $importTotal; + + /** + * Import options. + * + * @var array + */ + protected $importOptions; + + /** + * An array containing webform element names. + * + * @var array + */ + protected $elements; + + /** + * An array containing a webform's field definition names. + * + * @var array + */ + protected $fieldDefinitions; + + /** + * Constructs a WebformSubmissionExportImport object. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration object factory. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager + * The webform element manager. + */ + public function __construct(ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, EntityTypeManagerInterface $entity_type_manager, WebformElementManagerInterface $element_manager) { + $this->configFactory = $config_factory; + $this->loggerFactory = $logger_factory; + $this->entityTypeManager = $entity_type_manager; + $this->entityStorage = $entity_type_manager->getStorage('webform_submission'); + $this->elementManager = $element_manager; + } + + /** + * {@inheritdoc} + */ + public function setWebform(WebformInterface $webform = NULL) { + $this->webform = $webform; + $this->elementTypes = NULL; + } + + /** + * {@inheritdoc} + */ + public function getWebform() { + return $this->webform; + } + + /** + * {@inheritdoc} + */ + public function setSourceEntity(EntityInterface $entity = NULL) { + $this->sourceEntity = $entity; + } + + /** + * {@inheritdoc} + */ + public function getSourceEntity() { + return $this->sourceEntity; + } + + /** + * {@inheritdoc} + */ + public function getImportUri() { + return $this->importUri; + } + + /** + * {@inheritdoc} + */ + public function setImportUri($uri) { + $this->importUri = $uri; + $this->importTotal = NULL; + } + + /** + * {@inheritdoc} + */ + public function deleteImportUri() { + $files = $this->entityTypeManager->getStorage('file') + ->loadByProperties(['uri' => $this->getImportUri()]); + if ($files) { + $file = reset($files); + $file->delete(); + return TRUE; + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getImportOptions() { + return $this->importOptions; + } + + /** + * {@inheritdoc} + */ + public function setImportOptions(array $options) { + $this->importOptions = $options + $this->getDefaultImportOptions(); + } + + /** + * {@inheritdoc} + */ + public function getImportOption($name) { + return $this->importOptions[$name]; + } + + /** + * {@inheritdoc} + */ + public function getDefaultImportOptions() { + return [ + 'skip_validation' => FALSE, + 'treat_warnings_as_errors' => FALSE, + 'mapping' => [], + ]; + } + + /****************************************************************************/ + // Webform field definitions and elements. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function getFieldDefinitions() { + if (isset($this->fieldDefinitions)) { + return $this->fieldDefinitions; + } + + $this->fieldDefinitions = $this->entityStorage->getFieldDefinitions(); + return $this->fieldDefinitions; + } + + /** + * {@inheritdoc} + */ + public function getElements() { + if (isset($this->elements)) { + return $this->elements; + } + + $this->elements = $this->getWebform() + ->getElementsInitializedFlattenedAndHasValue(); + return $this->elements; + } + + /****************************************************************************/ + // Export. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function exportHeader() { + return array_keys($this->getDestinationColumns()); + } + + /** + * {@inheritdoc} + */ + public function exportSubmission(WebformSubmissionInterface $webform_submission, array $export_options = []) { + $submission_data = $webform_submission->toArray(TRUE); + + $record = []; + + // Append fields. + $field_definitions = $this->getFieldDefinitions(); + foreach ($field_definitions as $field_name => $field_definition) { + switch ($field_name) { + case 'uid': + $value = $this->getEntityExportId($webform_submission->getOwner(), $export_options); + break; + + case 'entity_id': + $value = $this->getEntityExportId($webform_submission->getSourceEntity(), $export_options); + break; + + default: + $value = (isset($submission_data[$field_name])) ? $submission_data[$field_name] : ''; + break; + } + $record[] = $this->exportValue($value); + } + + // Append elements. + $elements = $this->getElements(); + foreach ($elements as $element) { + $element_plugin = $this->elementManager->getElementInstance($element); + $has_multiple_values = $element_plugin->hasMultipleValues($element); + if ($element_plugin instanceof WebformManagedFileBase) { + // Files: Get File URLS. + /** @var \Drupal\file\FileInterface $files */ + $files = $element_plugin->getTargetEntities($element, $webform_submission) ?: []; + $values = []; + foreach ($files as $file) { + $values[] = file_create_url($file->getFileUri()); + } + $value = implode(',', $values); + $record[] = $this->exportValue($value); + } + elseif ($element_plugin instanceof WebformElementEntityReferenceInterface) { + // Entity references: Get entity UUIDs. + $entities = $element_plugin->getTargetEntities($element, $webform_submission); + $values = []; + foreach ($entities as $entity) { + $values[] = $this->getEntityExportId($entity, $export_options); + } + $value = implode(',', $values); + $record[] = $this->exportValue($value); + } + elseif ($element_plugin instanceof WebformLikert) { + // Single Composite: Split questions into individual columns. + $value = $element_plugin->getValue($element, $webform_submission); + $question_keys = array_keys($element['#questions']); + foreach ($question_keys as $question_key) { + $question_value = (isset($value[$question_key])) ? $value[$question_key] : ''; + $record[] = $this->exportValue($question_value); + } + } + elseif ($element_plugin instanceof WebformCompositeBase && !$has_multiple_values) { + // Composite: Split single composite sub elements into individual columns. + $value = $element_plugin->getValue($element, $webform_submission); + $composite_element_keys = array_keys($element_plugin->getCompositeElements()); + foreach ($composite_element_keys as $composite_element_key) { + $composite_value = (isset($value[$composite_element_key])) ? $value[$composite_element_key] : ''; + $record[] = $this->exportValue($composite_value); + } + } + elseif ($element_plugin->isComposite()) { + // Composite: Convert multiple composite values to a single line of YAML. + $value = $element_plugin->getValue($element, $webform_submission); + $dumper = new Dumper(2); + $record[] = $dumper->dump($value); + } + elseif ($has_multiple_values) { + // Multiple: Convert to comma separated values with commas URL encodes. + $values = $element_plugin->getValue($element, $webform_submission); + $values = ($values !== NULL) ? (array) $values : []; + foreach ($values as $index => $value) { + $values[$index] = str_replace(',', '%2C', $value); + } + $value = implode(',', $values); + $record[] = $this->exportValue($value); + } + else { + // Default: Convert NULL values to empty strings. + $value = $element_plugin->getValue($element, $webform_submission); + $value = ($value !== NULL) ? $value : ''; + $record[] = $this->exportValue($value); + } + } + + return $record; + } + + /****************************************************************************/ + // Import. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function import($offset = 0, $limit = NULL) { + if ($limit === NULL) { + $limit = $this->getBatchLimit(); + } + + $import_options = $this->getImportOptions(); + + // Open CSV file. + $handle = fopen($this->getImportUri(), 'r'); + + // Get the column names. + $column_names = fgetcsv($handle); + foreach ($column_names as $index => $name) { + $column_names[$index] = $name; + } + + // Fast forward CSV file to offset. + $index = 0; + while ($index < $offset && !feof($handle)) { + fgets($handle); + $index++; + } + + // Collect import stats. + $stats = [ + 'created' => 0, + 'updated' => 0, + 'skipped' => 0, + 'total' => 0, + 'warnings' => [], + 'errors' => [], + ]; + + // Import submission records. + while ($stats['total'] < $limit && !feof($handle)) { + // Get CSV values. + $values = fgetcsv($handle); + // Complete ignored empty rows. + if (empty($values)) { + continue; + } + + // Create CSV record. + $record = array_combine($column_names, $values); + // Trim all values. + foreach ($record as $key => $value) { + $record[$key] = trim($value); + } + + $index++; + $stats['total']++; + + // Track row specific warnings and errors. + $stats['warnings'][$index] = []; + $stats['errors'][$index] = []; + $row_warnings =& $stats['warnings'][$index]; + $row_errors =& $stats['errors'][$index]; + + // Track original record. + $original_record = $record; + + // Map. + $record = $this->importMapRecord($record); + + // Token: Generate token from the original CSV record. + if (empty($record['token'])) { + $record['token'] = md5(Settings::getHashSalt() . serialize($original_record)); + } + + // Prepare. + $webform_submission = $this->importLoadSubmission($record); + if ($errors = $this->importPrepareRecord($record, $webform_submission)) { + if (!empty($import_options['treat_warnings_as_errors'])) { + $row_errors = array_merge($row_warnings, array_values($errors)); + } + else { + $row_warnings = array_merge($row_warnings, array_values($errors)); + } + } + + // Validate. + if (empty($import_options['skip_validation'])) { + if ($errors = $this->importValidateRecord($record)) { + $row_errors = array_merge($row_errors, array_values($errors)); + } + } + + // Skip import if there are row errors. + if ($row_errors) { + $stats['skipped']++; + continue; + } + + // Save. + $this->importSaveSubmission($record, $webform_submission); + $stats[$webform_submission ? 'updated' : 'created']++; + } + + fclose($handle); + return $stats; + } + + /** + * Map source (CSV) record to destination (submission) records. + * + * @param array $record + * The source (CSV) record. + * + * @return array + * The destination (submission) records. + */ + protected function importMapRecord(array $record) { + $mapping = $this->getImportOption('mapping'); + + // If not mapping is defined return the record AS-IS. + if (empty($mapping)) { + return $record; + } + + $mapped_record = []; + foreach ($mapping as $source_name => $destination_name) { + if (isset($record[$source_name])) { + $mapped_record[$destination_name] = $record[$source_name]; + } + } + return $mapped_record; + } + + /** + * Load import submission record via UUID or token. + * + * @param array $record + * The import submission record. + * + * @return \Drupal\webform\WebformSubmissionInterface|null + * The existing webform submission or NULL if no existing submission found. + */ + protected function importLoadSubmission(array &$record) { + $unique_keys = ['uuid', 'token']; + foreach ($unique_keys as $unique_key) { + if (!empty($record[$unique_key])) { + if ($webform_submissions = $this->entityStorage->loadByProperties([$unique_key => $record[$unique_key]])) { + return reset($webform_submissions); + } + } + } + return NULL; + } + + /** + * Prepare import submission record. + * + * @param array $record + * The import submission record. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * The existing webform submission. + * + * @return array + * An array of error messages. + */ + protected function importPrepareRecord(array &$record, WebformSubmissionInterface $webform_submission = NULL) { + // Track errors. + $errors = []; + + if (isset($record['uid'])) { + // Convert user id to internal IDs. + $record['uid'] = $this->getEntityImportId('user', $record['uid']); + // Convert empty uid to anonymous user (UID: 0). + if (empty($record['uid'])) { + $record['uid'] = 0; + } + } + + // Remove empty uuid. + if (empty($record['uuid'])) { + unset($record['uuid']); + } + + $webform = $this->getWebform(); + $source_entity = $this->getSourceEntity(); + + // Set webform id. + $record['webform_id'] = $webform->id(); + + // Set source entity. + // Load or convert the source entity id to an internal ID. + if ($source_entity) { + $record['entity_type'] = $source_entity->getEntityTypeId(); + $record['entity_id'] = $source_entity->id(); + } + elseif (!empty($record['entity_type']) && isset($record['entity_id'])) { + $record['entity_id'] = $this->getEntityImportId($record['entity_type'], $record['entity_id']); + // If source entity_id can't be found, log error, and + // remove the source entity_type. + if ($record['entity_id'] === NULL) { + $t_args = [ + '@entity_type' => $record['entity_type'], + '@entity_id' => $record['entity_id'], + ]; + $errors[] = $this->t('Unable to locate source entity (@entity_type:@entity_id)', $t_args); + $record['entity_type'] = NULL; + } + } + + // Convert record to submission element data. + $elements = $this->getElements(); + foreach ($record as $name => $value) { + // Set record value form an element. + if (isset($elements[$name])) { + $element = $elements[$name]; + $record[$name] = $this->importElement($element, $value, $webform_submission, $errors); + continue; + } + + // Check if record name is a composite element which is + // delimited using '__'. + if (strpos($name, '__') === FALSE) { + continue; + } + + // Get element and composite key and confirm that the element exists. + list($element_key, $composite_key) = explode('__', $name); + if (!isset($elements[$element_key])) { + continue; + } + + // Make sure the composite element is not storing multiple values which + // must use YAML. + // @see \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporter::importCompositeElement + $element = $elements[$element_key]; + $element_plugin = $this->elementManager->getElementInstance($element); + if ($element_plugin->hasMultipleValues($element)) { + continue; + } + + if ($element_plugin instanceof WebformLikert) { + // Make sure the Likert question exists. + if (!isset($element['#questions']) || !isset($element['#questions'][$composite_key])) { + continue; + } + + $record[$element_key][$composite_key] = $value; + } + elseif ($element_plugin instanceof WebformCompositeBase) { + // Get the the composite element element and make sure it exists. + $composite_elements = $element_plugin->getCompositeElements(); + if (!isset($composite_elements[$composite_key])) { + continue; + } + + $composite_element = $composite_elements[$composite_key]; + $record[$element_key][$composite_key] = $this->importElement($composite_element, $value, $webform_submission, $errors); + } + } + + return $errors; + } + + /** + * Import element. + * + * @param array $element + * A managed file element. + * @param mixed $value + * File URI(s) from CSV record. + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission + * Existing submission or NULL if new submission. + * @param array $errors + * An array of error messages. + * + * @return array|int|null + * An array of multiple files, single file id, or NULL if file could + * not be imported. + */ + protected function importElement(array $element, $value, WebformSubmissionInterface $webform_submission = NULL, array &$errors) { + $element_plugin = $this->elementManager->getElementInstance($element); + + if ($value === '') { + // Empty: Convert multiple values to an empty array or NULL value. + return ($element_plugin->hasMultipleValues($element)) ? [] : NULL; + } + elseif ($element_plugin instanceof WebformManagedFileBase) { + // Files: Convert File URL to file object. + return $this->importManageFileElement($element, $value, $webform_submission, $errors); + } + elseif ($element_plugin instanceof WebformElementEntityReferenceInterface) { + // Entity references: Convert entity UUIDs to internal IDs. + return $this->importEntityReferenceElement($element, $value, $webform_submission, $errors); + } + elseif ($element_plugin->isComposite()) { + // Composite: Decode YAML. + return $this->importCompositeElement($element, $value, $webform_submission, $errors); + } + elseif ($element_plugin->hasMultipleValues($element)) { + // Multiple: Convert to comma separated values to array. + return $this->importMultipleElement($element, $value, $webform_submission, $errors); + } + else { + return $value; + } + } + + /** + * Import managed file element. + * + * @param array $element + * A managed file element. + * @param mixed $value + * File URI(s) from CSV record. + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission + * Existing submission or NULL if new submission. + * @param array $errors + * An array of error messages. + * + * @return array|int|null + * An array of multiple files, single file id, or NULL if file could + * not be imported. + */ + protected function importManageFileElement(array $element, $value, WebformSubmissionInterface $webform_submission = NULL, array &$errors) { + $webform = $this->getWebform(); + $element_plugin = $this->elementManager->getElementInstance($element); + + // Prepare managed file element with a temp submission. + $element_plugin->prepare($element, $this->entityStorage->create(['webform_id' => $webform->id()])); + + // Get file destination. + $file_destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL; + if (isset($file_destination) && !file_prepare_directory($file_destination, FILE_CREATE_DIRECTORY)) { + $this->loggerFactory->get('file') + ->notice('The upload directory %directory for the file element %name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', [ + '%directory' => $file_destination, + '%name' => $element['#webform_key'], + ]); + return ($element_plugin->hasMultipleValues($element)) ? [] : NULL; + } + + // Get file upload validators. + $file_upload_validators = $element['#upload_validators']; + + // Create a uri and sha1 lookup tables for existing files. + $existing_file_ids = []; + $existing_file_uris = []; + $existing_files = ($webform_submission) ? $element_plugin->getTargetEntities($element, $webform_submission) ?: [] : []; + foreach ($existing_files as $existing_file) { + $existing_file_uri = file_create_url($existing_file->getFileUri()); + $existing_file_uris[$existing_file_uri] = $existing_file->id(); + + $existing_file_hash = sha1_file($existing_file->getFileUri()); + $existing_file_ids[$existing_file_hash] = $existing_file->id(); + } + + // Find or upload new file. + $new_file_ids = []; + $new_file_uris = explode(',', $value); + foreach ($new_file_uris as $new_file_uri) { + // Check existing file URIs. + if (isset($existing_file_uris[$new_file_uri])) { + $new_file_ids[$new_file_uri] = $existing_file_uris[$new_file_uri]; + continue; + } + + $t_args = [ + '@element_key' => $element['#webform_key'], + '@url' => $new_file_uri, + ]; + + // Check URL protocol. + if (!preg_match('#^https?://#', $new_file_uri)) { + $errors[] = $this->t('[@element_key] Invalid file URL (@url). URLS must begin with http:// or https://.', $t_args); + continue; + } + + // Check URL status code. + $file_headers = @get_headers($new_file_uri); + if (!$file_headers || $file_headers[0] == 'HTTP/1.1 404 Not Found') { + $errors[] = $this->t('[@element_key] URL (@url) returns 404 file not found.', $t_args); + continue; + } + + $new_file_hash = @sha1_file($new_file_uri); + if (!$new_file_hash) { + $errors[] = $this->t('[@element_key] Unable to read file from URL (@url).', $t_args); + continue; + } + + // Check existing file hashes. + if (isset($existing_file_ids[$new_file_hash])) { + $new_file_ids[$new_file_hash] = $existing_file_ids[$new_file_hash]; + continue; + } + + // Write new file URI to server and upload it. + $temp_file_contents = @file_get_contents($new_file_uri); + if (!$temp_file_contents) { + $errors[] = $this->t('[@element_key] Unable to read file from URL (@url).', $t_args); + continue; + } + + // Create a temp file. + $handle = tmpfile(); + fwrite($handle, $temp_file_contents); + $temp_file_meta_data = stream_get_meta_data($handle); + $temp_file_path = $temp_file_meta_data['uri']; + $temp_file_size = filesize($temp_file_path); + + // Mimic Symfony and Drupal's upload file handling. + $temp_file_info = new UploadedFile($temp_file_path, basename($new_file_uri), NULL, $temp_file_size); + $webform_element_key = $element_plugin->getLabel($element); + $new_file = _webform_submission_export_import_file_save_upload_single($temp_file_info, $webform_element_key, $file_upload_validators, $file_destination); + if ($new_file) { + $new_file_ids[$new_file_hash] = $new_file->id(); + } + } + + $values = array_values($new_file_ids); + if (empty($values)) { + return ($element_plugin->hasMultipleValues($element)) ? [] : NULL; + } + else { + return ($element_plugin->hasMultipleValues($element)) ? $values : reset($values); + } + } + + /** + * Import entity reference element. + * + * @param array $element + * A managed file element. + * @param mixed $value + * File URI(s) from CSV record. + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission + * Existing submission or NULL if new submission. + * @param array $errors + * An array of error messages. + * + * @return array|int|null + * An array of entity ids, a single entity id, or NULL if entity ids + * could not be imported. + */ + protected function importEntityReferenceElement(array $element, $value, WebformSubmissionInterface $webform_submission = NULL, array &$errors) { + $element_plugin = $this->elementManager->getElementInstance($element); + $entity_type_id = $element_plugin->getTargetType($element); + $values = explode(',', $value); + foreach ($values as $index => $value) { + $values[$index] = $this->getEntityImportId($entity_type_id, $value); + if ($values[$index] === NULL) { + $t_args = [ + '@element_key' => $element['#webform_key'], + '@entity_id' => $value, + ]; + $errors[] = $this->t('[@element_key] Unable to locate entity (@entity_id).', $t_args); + unset($values[$index]); + } + } + $values = array_values($values); + if (empty($values)) { + return ($element_plugin->hasMultipleValues($element)) ? [] : NULL; + } + else { + return ($element_plugin->hasMultipleValues($element)) ? $values : reset($values); + } + } + + /** + * Import composite element. + * + * @param array $element + * A composite element. + * @param mixed $value + * File URI(s) from CSV record. + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission + * Existing submission or NULL if new submission. + * @param array $errors + * An array of error messages. + * + * @return array + * An array of composite element data. + */ + protected function importCompositeElement(array $element, $value, WebformSubmissionInterface $webform_submission = NULL, array &$errors) { + try { + return Yaml::decode($value); + } + catch (\Exception $exception) { + $t_args = [ + '@element_key' => $element['#webform_key'], + '@error' => $exception->getMessage(), + ]; + $errors[] = $this->t('[@element_key] YAML is not valid. @error', $t_args); + return []; + } + } + + /** + * Import multiple element. + * + * @param array $element + * An element with multiple values.. + * @param mixed $value + * File URI(s) from CSV record. + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission + * Existing submission or NULL if new submission. + * @param array $errors + * An array of error messages. + * + * @return array + * An array of multiple values. + */ + protected function importMultipleElement(array $element, $value, WebformSubmissionInterface $webform_submission = NULL, array &$errors) { + $values = preg_split('/\s*,\s*/', $value); + foreach ($values as $index => $item) { + $values[$index] = str_replace('%2C', ',', $item); + } + return $values; + } + + /** + * Validate import record submission. + * + * @param array $record + * The record to be imported. + * + * @return array + * An array of error messages. + */ + protected function importValidateRecord(array $record) { + $values = $this->importConvertRecordToValues($record); + return WebformSubmissionForm::validateFormValues($values); + } + + /** + * Save import record submission. + * + * @param array $record + * The record to be imported. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * The existing webform submission. + */ + protected function importSaveSubmission(array $record, WebformSubmissionInterface $webform_submission = NULL) { + $field_definitions = $this->getFieldDefinitions(); + $elements = $this->getElements(); + + if ($webform_submission) { + // Update submission. + unset($record['sid'], $record['serial'], $record['uuid']); + foreach ($record as $name => $value) { + if (isset($field_definitions[$name])) { + $webform_submission->set($name, $value); + } + elseif (isset($elements[$name])) { + $webform_submission->setElementData($name, $value); + } + } + } + else { + // Create submission. + unset($record['sid'], $record['serial']); + $values = $this->importConvertRecordToValues($record); + $webform_submission = $this->entityStorage->create($values); + } + $webform_submission->save(); + } + + /** + * Convert CSV records to entity values. + * + * @param array $record + * The record to be imported. + * + * @return array + * The CSV records converted to entity values. + * + * @see \Drupal\webform\Entity\WebformSubmission::preCreate + */ + protected function importConvertRecordToValues(array $record) { + $field_definitions = $this->getFieldDefinitions(); + $elements = $this->getElements(); + + $values = ['data' => []]; + foreach ($record as $name => $value) { + if (isset($field_definitions[$name])) { + $values[$name] = $value; + } + elseif (isset($elements[$name])) { + $values['data'][$name] = $value; + } + } + + // Never allow the record to set the sid or serial. + unset($values['sid'], $values['serial']); + + return $values; + } + + /****************************************************************************/ + // Summary. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function getTotal() { + if (isset($this->importTotal)) { + return $this->importTotal; + } + // Ignore the header. + $total = -1; + $handle = fopen($this->importUri, 'r'); + while (!feof($handle)) { + $line = fgets($handle); + if (!empty(trim($line))) { + $total++; + } + } + $this->importTotal = $total; + return $this->importTotal; + } + + /** + * {@inheritdoc} + */ + public function getSourceColumns() { + $file = fopen($this->getImportUri(), 'r'); + $values = fgetcsv($file); + fclose($file); + return array_combine($values, $values); + } + + /** + * {@inheritdoc} + */ + public function getDestinationColumns() { + $columns = []; + + $field_definitions = $this->getFieldDefinitions(); + foreach ($field_definitions as $field_name => $field_definition) { + $columns[$field_name] = $field_definition['title']; + } + + $elements = $this->getElements(); + foreach ($elements as $element_key => $element) { + $element_plugin = $this->elementManager->getElementInstance($element); + $element_title = $element_plugin->getAdminLabel($element); + $has_multiple_values = $element_plugin->hasMultipleValues($element); + if (!$has_multiple_values && $element_plugin instanceof WebformCompositeBase) { + $composite_elements = $element_plugin->getCompositeElements(); + foreach ($composite_elements as $composite_element_key => $composite_element) { + $composite_element_name = $element_key . '__' . $composite_element_key; + $composite_element_plugin = $this->elementManager->getElementInstance($composite_element); + $composite_element_title = $composite_element_plugin->getAdminLabel($composite_element); + $t_args = [ + '@element_title' => $element_title, + '@composite_title' => $composite_element_title, + ]; + $columns[$composite_element_name] = $this->t('@element_title: @composite_title', $t_args); + } + } + elseif (!$has_multiple_values && $element_plugin instanceof WebformLikert) { + $questions = $element['#questions']; + foreach ($questions as $question_key => $question) { + $question_element_name = $element_key . '__' . $question_key; + $t_args = [ + '@element_title' => $element_title, + '@question_title' => $question, + ]; + $columns[$question_element_name] = $this->t('@element_title: @question_title', $t_args); + } + } + else { + $columns[$element_key] = $element_title; + } + } + + return $columns; + } + + /** + * {@inheritdoc} + */ + public function getSourceToDestinationColumnMapping() { + $source_column_names = $this->getSourceColumns(); + $destination_column_names = $this->getDestinationColumns(); + + // Map source to destination columns. + $mapping = []; + foreach ($source_column_names as $source_column_name) { + if (isset($destination_column_names[$source_column_name])) { + $mapping[$source_column_name] = $source_column_name; + } + else { + $mapping[$source_column_name] = ''; + } + } + + return $mapping; + } + + /****************************************************************************/ + // Batch. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function getBatchLimit() { + return $this->configFactory->get('webform.settings') + ->get('batch.default_batch_import_size') ?: 100; + } + + /** + * {@inheritdoc} + */ + public function requiresBatch() { + return ($this->getTotal() > $this->getBatchLimit()) ? TRUE : FALSE; + } + + /****************************************************************************/ + // Helpers. + /****************************************************************************/ + + /** + * Get an entity's export id or UUID based on the export options. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. + * @param array $export_options + * Export options. + * + * @return string|int + * The entity's id or UUID. + */ + protected function getEntityExportId(EntityInterface $entity = NULL, array $export_options = []) { + if (!$entity) { + return ''; + } + else { + return (empty($export_options['uuid'])) ? $entity->id() : $entity->uuid(); + } + } + + /** + * Get an entity's import internal id. + * + * @param string $entity_type + * The entity type. + * @param string $entity_id + * The entity id or UUID. + * + * @return int|string|null + * An entity's internal id. NULL if an entity's internal id + * can't be determined. + */ + protected function getEntityImportId($entity_type, $entity_id) { + if (!$this->entityTypeManager->hasDefinition($entity_type)) { + return NULL; + } + + $entity_storage = $this->entityTypeManager->getStorage($entity_type); + + // Load entity by properties. + if ($entity_type === 'user') { + $properties = ['uuid', 'mail', 'name']; + } + else { + $properties = ['uuid']; + } + foreach ($properties as $property) { + $entities = $entity_storage->loadByProperties([$property => $entity_id]); + if ($entities) { + $entity = reset($entities); + return $entity->id(); + } + } + + // Load entity by internal id. + $entity = $entity_storage->load($entity_id); + if ($entity) { + return $entity->id(); + } + + return NULL; + } + + /** + * Export value so that it can be editted in Excel and Google Sheets. + * + * @param string $value + * A value. + * + * @return string + * A value that it can be editted in Excel and Googl Sheets. + */ + protected function exportValue($value) { + // Prevent Excel and Google Sheets from convert string beginning with + // + or - into formulas by adding a space before the string. + // @see https://stackoverflow.com/questions/4438589/bypass-excel-csv-formula-conversion-on-fields-starting-with-or + if (is_string($value) && strpos($value, '+') === 0 || strpos($value, '-') === 0) { + return ' ' . $value; + } + else { + return $value; + } + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporterInterface.php b/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporterInterface.php new file mode 100644 index 0000000000..89beedfb36 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/src/WebformSubmissionExportImportImporterInterface.php @@ -0,0 +1,226 @@ +<?php + +namespace Drupal\webform_submission_export_import; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Defines an interface for exporting webform submission results. + */ +interface WebformSubmissionExportImportImporterInterface { + + /** + * Set the webform whose submissions are being imported. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + */ + public function setWebform(WebformInterface $webform = NULL); + + /** + * Get the webform whose submissions are being imported. + * + * @return \Drupal\webform\WebformInterface + * A webform. + */ + public function getWebform(); + + /** + * Set the webform source entity whose submissions are being imported. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * A webform's source entity. + */ + public function setSourceEntity(EntityInterface $entity = NULL); + + /** + * Get the webform source entity whose submissions are being imported. + * + * @return \Drupal\Core\Entity\EntityInterface + * A webform's source entity. + */ + public function getSourceEntity(); + + /** + * Get the URI of the CSV import file. + * + * @return string + * The URI of the CSV import file. + */ + public function getImportUri(); + + /** + * Set the URI of the CSV import file. + * + * @param string $uri + * The URI of the CSV import file. + */ + public function setImportUri($uri); + + /** + * Attempt delete managed file created for import uri. + * + * @return bool + * TRUE if managed file created for import uri was deleted. + */ + public function deleteImportUri(); + + /** + * Get import options. + * + * @return array + * Import options. + */ + public function getImportOptions(); + + /** + * Set import options. + * + * @param array $options + * Import options. + */ + public function setImportOptions(array $options); + + /** + * Get import option value. + * + * @param string $name + * The import option name. + * + * @return mixed + * The import option value or the default value. + */ + public function getImportOption($name); + + /** + * Get default import options. + * + * @return array + * Default import options. + */ + public function getDefaultImportOptions(); + + /****************************************************************************/ + // Webform field definitions and elements. + /****************************************************************************/ + + /** + * Get a webform's field definitions. + * + * @return array + * An associative array containing a webform's field definitions. + */ + public function getFieldDefinitions(); + + /** + * Get webform elements. + * + * @return array + * An associative array containing webform elements keyed by name. + */ + public function getElements(); + + /****************************************************************************/ + // Export. + /****************************************************************************/ + + /** + * Create CSV export header. + * + * @return array + * The CSV export header columns. + */ + public function exportHeader(); + + /** + * Export webform submission as a CSV record. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $export_options + * An associative array of export options. + * + * @return array + * The webform submission converted to a CSV record. + */ + public function exportSubmission(WebformSubmissionInterface $webform_submission, array $export_options = []); + + /****************************************************************************/ + // Import. + /****************************************************************************/ + + /** + * Import records from CSV import file. + * + * @param int $offset + * Line to be begin importing from. + * @param int|null $limit + * The number of records to be imported. + * + * @return array + * An associate array containing imports states including total, + * created, updated, skipped, and errors. + */ + public function import($offset = 0, $limit = NULL); + + /****************************************************************************/ + // Summary. + /****************************************************************************/ + + /** + * Total number of submissions to be imported. + * + * @return int + * The total number of submissions to be imported. + */ + public function getTotal(); + + /** + * Get source (CSV) columns name. + * + * @return array + * An associative array containing source (CSV) columns name. + */ + public function getSourceColumns(); + + /** + * Get destination (field and element) columns name. + * + * @return array + * An associative array containing destination (field and element) + * columns name. + */ + public function getDestinationColumns(); + + /** + * Get source (CSV) to destination (field and element) column mapping. + * + * @return array + * An associative array containing source (CSV) to + * destination (field and element) column mapping. + */ + public function getSourceToDestinationColumnMapping(); + + /****************************************************************************/ + // Batch. + /****************************************************************************/ + + /** + * Get the number of submissions to be exported with each batch. + * + * @return int + * Number of submissions to be exported with each batch. + */ + public function getBatchLimit(); + + /** + * Determine if webform submissions must be imported using batch processing. + * + * @return bool + * TRUE if webform submissions must be imported using batch processing. + */ + public function requiresBatch(); + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/config/install/webform.webform.test_submission_export_import.yml b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/config/install/webform.webform.test_submission_export_import.yml new file mode 100644 index 0000000000..afaa8cf146 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/config/install/webform.webform.test_submission_export_import.yml @@ -0,0 +1,231 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_submission_export_import_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_submission_export_import +title: 'Test: Submission: Export/Import' +description: 'Test webform submission export/import.' +category: 'Test: Element' +elements: | + summary: + '#type': textfield + '#title': summary + email: + '#type': email + '#title': email + emails: + '#type': email + '#title': emails + '#multiple': true + checkbox: + '#type': checkbox + '#title': checkbox + checkboxes: + '#type': checkboxes + '#title': checkboxes + '#options': + one: One + two: Two + three: Three + file: + '#type': managed_file + '#title': file + files: + '#type': managed_file + '#title': files + '#multiple': true + likert: + '#type': webform_likert + '#title': likert + '#questions': + q1: 'Question 1' + q2: 'Question 2' + q3: 'Question 3' + '#answers': + 1: 'Option 1' + 2: 'Option 2' + 3: 'Option 3' + composite: + '#type': webform_link + '#title': composite + composites: + '#type': webform_link + '#title': composites + '#multiple': true + '#multiple__header': true + entity_reference: + '#type': webform_entity_select + '#title': entity_reference + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + entity_references: + '#type': webform_entity_select + '#title': entity_references + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: both + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-external.csv b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-external.csv new file mode 100644 index 0000000000..7762f76537 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-external.csv @@ -0,0 +1,2 @@ +summary,notes,email,emails,checkbox,checkboxes,file,files,likert__q1,likert__q2,likert__q3,composite__title,composite__url,composites,entity_reference,entity_references,not_mapped +valid external data,valid external data,example@example.com,"example@example.com,random@random.com,test@test.com",1,"two,three,one",https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.gif,"https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.gif,https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.png",3,3,3,Oratione,http://example.com,"[{ title: Oratione, url: 'http://example.com' }, { title: Oratione, url: 'http://example.com' }, { title: Oratione, url: 'http://test.com' }]",user_id,user_uuid,{not mapped} \ No newline at end of file diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-webform.csv b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-webform.csv new file mode 100644 index 0000000000..3eec3fd52a --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/files/test_submission_export_import-webform.csv @@ -0,0 +1,4 @@ +uuid,notes,summary,email,emails,checkbox,checkboxes,file,files,likert__q1,likert__q2,likert__q3,composite__title,composite__url,composites,entity_reference,entity_references,not_mapped +e1d59c85-7096-4bee-bafa-1bd6798862e2,valid,valid,example@example.com,"example@example.com,random@random.com,test@test.com",1,"two,three,one",https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.gif,"https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.gif,https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/tests/files/sample.png",3,3,3,Oratione,http://example.com,"[{ title: Oratione, url: 'http://example.com' }, { title: Oratione, url: 'http://example.com' }, { title: Oratione, url: 'http://test.com' }]",user_name,user_mail,{not mapped} +9a05b67b-a69a-43d8-a498-9bea83c1cbbe,validation warnings,validation warnings,test@test.com,"random@random.com,example@example.com,test@test.com",1,"two,one,three",/webform/plain/tests/files/sample.gif,,3,3,3,Loremipsum,http://test.com,@#$%^not valid ':' yaml,,,{not mapped} +428e338b-d09c-4bb6-8e34-7dcea79f1f0d,validation errors,validation errors,not an email address,"test@test.com,example@example.com,not an email address",1,invalid,,,1,1,2,Dixisset,http://example.com,"[{ title: Oratione, url: 'http://example.com' }, { title: Oratione, url: 'http://example.com' }, { title: Loremipsum, url: 'http://example.com' }]",,,{not mapped} \ No newline at end of file diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/js/webform_submission_export_import_test.js b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/js/webform_submission_export_import_test.js new file mode 100644 index 0000000000..aac955ab48 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/js/webform_submission_export_import_test.js @@ -0,0 +1,27 @@ +/** + * @file + * JavaScript behaviors for Webform Export/Import Test module. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Set import URL and submit the form. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformSubmissionExportImportTest = { + attach: function (context) { + $('#edit-import-url--description a', context) + .once('webform-export-import-test') + .click(function () { + $('#edit-import-url').val(this.href); + $('#webform-submission-export-import-upload-form').submit(); + return false; + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.info.yml b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.info.yml new file mode 100644 index 0000000000..2de933323f --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Submission Export/Import Test' +type: module +description: 'Support module for webform submission export/import.' +package: Testing +# core: 8.x +dependencies: + - 'webform_image_select:webform_image_select' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.install b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.install new file mode 100644 index 0000000000..9c92ee908d --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.install @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Install/uninstall functions for the Webform Export/Import Test module. + */ + +/** + * Implements hook_uninstall(). + */ +function webform_submission_export_import_test_uninstall() { + // Delete webform and external CSV import examples. + $files_path = drupal_get_path('module', 'webform_submission_export_import_test') . '/files'; + $file_names = [ + 'public://test_submission_export_import-webform.csv', + 'public://test_submission_export_import-external.csv', + ]; + foreach ($file_names as $file_name) { + if (file_exists("$files_path/$file_name")) { + file_unmanaged_delete("$files_path/$file_name"); + } + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.libraries.yml b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.libraries.yml new file mode 100644 index 0000000000..401975897a --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.libraries.yml @@ -0,0 +1,8 @@ +webform_submission_export_import_test: + version: VERSION + js: + js/webform_submission_export_import_test.js: {} + dependencies: + - core/drupal + - core/jquery + - core/jquery.once diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.module b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.module new file mode 100644 index 0000000000..1f030d7bb7 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/modules/webform_submission_export_import_test/webform_submission_export_import_test.module @@ -0,0 +1,87 @@ +<?php + +/** + * @file + * Support module for webform submission export/import. + */ + +use Drupal\Core\Form\FormStateInterface; +use Drupal\user\Entity\User; + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Make testing the CSV import a little easier by using and prepopulating + * external Google Sheet CSV. + */ +function webform_submission_export_import_test_form_webform_submission_export_import_upload_form_alter(&$form, FormStateInterface $form_state) { + $webform_id = \Drupal::routeMatch()->getRawParameter('webform'); + if ($webform_id === 'test_submission_export_import' && isset($form['import']['import_url'])) { + global $base_url; + + $webform_path = drupal_get_path('module', 'webform'); + $files_path = drupal_get_path('module', 'webform_submission_export_import_test') . '/files'; + $file_names = [ + // @see https://docs.google.com/spreadsheets/d/e/2PACX-1vTYImRfGbGGCtKq7hrYGkm5jJo_P0fwNrLOBpcSapBkEUPlBToShXcSJAdDAF5hjVupbGFAm1anUtip/pub?gid=646036902&single=true&output=csv + 'webform', + // @see https://docs.google.com/spreadsheets/d/e/2PACX-1vTYImRfGbGGCtKq7hrYGkm5jJo_P0fwNrLOBpcSapBkEUPlBToShXcSJAdDAF5hjVupbGFAm1anUtip/pub?gid=173244372&single=true&output=csv + 'external', + ]; + + $t_args = []; + foreach ($file_names as $file_name) { + // Set the test CSV URI. + $csv_uri = "public://test_submission_export_import-$file_name.csv"; + + // Set file URL href as href. + $t_args[":href_$file_name"] = file_create_url($csv_uri); + + // Skip generate test CSV if it exists. + if (file_exists($csv_uri)) { + continue; + } + + // Copy CSV file from module to public:// uri. + $csv_file_path = "$files_path/test_submission_export_import-$file_name.csv"; + file_unmanaged_copy($csv_file_path, NULL, FILE_EXISTS_REPLACE); + + $contents = file_get_contents($csv_uri); + + // Replace GitHub URLs with location URLs. + $github_base_url = 'https://raw.githubusercontent.com/drupalprojects/webform/8.x-5.x/'; + $contents = str_replace($github_base_url, "$base_url/$webform_path/", $contents); + + // Replace user entity reference properties. + $account = User::load(1); + $contents = str_replace('user_id', $account->id(), $contents); + $contents = str_replace('user_name', $account->getAccountName(), $contents); + $contents = str_replace('user_mail', $account->getEmail(), $contents); + $contents = str_replace('user_uuid', $account->uuid(), $contents); + + file_put_contents($csv_uri, $contents); + } + + // Set the default type and URL to the webform.csv. + $form['import']['import_type']['#default_value'] = 'url'; + $form['import']['import_url']['#default_value'] = $t_args[':href_webform']; + + // Display clickable description that populates the import URL and + // submits the form. + // @see Drupal.behaviors.webformSubmissionExportImportTest + $form['import']['import_url']['#description'] = t('Test <a href=":href_webform">webform</a> or <a href=":href_external">external</a>', $t_args); + $form['import']['import_url']['#help'] = FALSE; + $form['#attached']['library'][] = 'webform_submission_export_import_test/webform_submission_export_import_test'; + } + + /* + // Get webform.csv and external.csv from Google Sheets. + file_put_contents( + "$files_path/test_submission_export_import-webform.csv", + file_get_contents('https://docs.google.com/spreadsheets/d/e/2PACX-1vTYImRfGbGGCtKq7hrYGkm5jJo_P0fwNrLOBpcSapBkEUPlBToShXcSJAdDAF5hjVupbGFAm1anUtip/pub?gid=646036902&single=true&output=csv') + ); + file_put_contents( + "$files_path/test_submission_export_import-external.csv", + file_get_contents('https://docs.google.com/spreadsheets/d/e/2PACX-1vTYImRfGbGGCtKq7hrYGkm5jJo_P0fwNrLOBpcSapBkEUPlBToShXcSJAdDAF5hjVupbGFAm1anUtip/pub?gid=173244372&single=true&output=csv') + ); + */ +} diff --git a/web/modules/webform/modules/webform_submission_export_import/tests/src/Functional/WebformSubmissionImportExportFunctionalTest.php b/web/modules/webform/modules/webform_submission_export_import/tests/src/Functional/WebformSubmissionImportExportFunctionalTest.php new file mode 100644 index 0000000000..264b721b6b --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/tests/src/Functional/WebformSubmissionImportExportFunctionalTest.php @@ -0,0 +1,425 @@ +<?php + +namespace Drupal\Tests\webform_submission_import_export\Functional; + +use Drupal\file\Entity\File; +use Drupal\Tests\webform\Functional\WebformBrowserTestBase; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Utility\WebformElementHelper; + +/** + * Webform submission export/import test. + * + * @group webform_browser + */ +class WebformSubmissionImportExportFunctionalTest extends WebformBrowserTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'file', + 'webform', + 'webform_submission_export_import', + 'webform_submission_export_import_test', + ]; + + /** + * Test submission import. + */ + public function testSubmissionExport() { + $this->drupalLogin($this->rootUser); + + $export_csv_uri = 'public://test_submission_export_import-export.csv'; + $export_csv_url = file_create_url('public://test_submission_export_import-export.csv'); + + $webform = Webform::load('test_submission_export_import'); + + /** @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer */ + $importer = \Drupal::service('webform_submission_export_import.importer'); + $importer->setWebform($webform); + $importer->setImportUri($export_csv_url); + + // Create 3 submissions. + /** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */ + $submissions = [ + WebformSubmission::load($this->postSubmissionTest($webform)), + WebformSubmission::load($this->postSubmissionTest($webform)), + WebformSubmission::load($this->postSubmissionTest($webform)), + ]; + + // Create CSV export. + $this->drupalPostForm('/admin/structure/webform/manage/test_submission_export_import/results/download', ['exporter' => 'webform_submission_export_import'], t('Download')); + file_put_contents($export_csv_uri, $this->getRawContent()); + + /**************************************************************************/ + + // Import CSV export without any changes. + $actual_stats = $importer->import(); + WebformElementHelper::convertRenderMarkupToStrings($actual_stats); + $expected_stats = [ + 'created' => 0, + 'updated' => 3, + 'skipped' => 0, + 'total' => 3, + 'warnings' => [ + 1 => [], + 2 => [], + 3 => [], + ], + 'errors' => [ + 1 => [], + 2 => [], + 3 => [], + ], + ]; + $this->assertEquals($expected_stats, $actual_stats); + + // Check that submission values are unchanged. + foreach ($submissions as $original_submission) { + $expected_values = $original_submission->toArray(TRUE); + $updated_submission = $this->loadSubmissionByProperty('uuid', $original_submission->uuid()); + $actual_values = $updated_submission->toArray(TRUE); + $this->assertEquals($expected_values, $actual_values); + } + + // Alter the first submission. + $submissions[0]->setCompletedTime(time() - 1000); + $submissions[0]->setNotes('This is a note'); + $submissions[0]->save(); + + // Deleted the third submission. + $file_uri = file_create_url(File::load($submissions[2]->getElementData('file'))->getFileUri()); + $files_uri = file_create_url(File::load($submissions[2]->getElementData('files')[0])->getFileUri()); + $submissions[2]->delete(); + unset($submissions[2]); + + // Import CSV export without any changes. + $actual_stats = $importer->import(); + WebformElementHelper::convertRenderMarkupToStrings($actual_stats); + $this->debug($actual_stats); + $expected_stats = [ + 'created' => 1, + 'updated' => 2, + 'skipped' => 0, + 'total' => 3, + 'warnings' => [ + 1 => [], + 2 => [], + 3 => [ + 0 => '[file] Unable to read file from URL (' . $file_uri . ').', + 1 => '[files] Unable to read file from URL (' . $files_uri . ').', + ], + ], + 'errors' => [ + 1 => [], + 2 => [], + 3 => [], + ], + ]; + $this->assertEquals($expected_stats, $actual_stats); + + // Check that submission 1 values reset to original values. + $original_submission = $submissions[0]; + $expected_values = $original_submission->toArray(TRUE); + $updated_submission = $this->loadSubmissionByProperty('uuid', $original_submission->uuid()); + $actual_values = $updated_submission->toArray(TRUE); + + // Check that changes and notes were updated. + $this->assertNotEqual($expected_values['completed'], $actual_values['completed']); + $this->assertNotEqual($expected_values['notes'], $actual_values['notes']); + + // Check that notes was reset. + $this->assertEqual('This is a note', $expected_values['notes']); + $this->assertEqual('', $actual_values['notes']); + + // Unset changed and notes. + unset($expected_values['completed'], $expected_values['notes']); + unset($actual_values['completed'], $actual_values['notes']); + + // Check all other values remained the same. + $this->assertEquals($expected_values, $actual_values); + } + + /** + * Test submission import. + */ + public function testSubmissionImport() { + $this->drupalLogin($this->rootUser); + + $webform_csv_url = file_create_url('public://test_submission_export_import-webform.csv'); + $external_csv_url = file_create_url('public://test_submission_export_import-external.csv'); + + $webform = Webform::load('test_submission_export_import'); + + /** @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $importer */ + $importer = \Drupal::service('webform_submission_export_import.importer'); + $importer->setWebform($webform); + $importer->setImportUri($webform_csv_url); + + /**************************************************************************/ + + // Upload the webform.csv. + $this->drupalPostForm( + '/admin/structure/webform/manage/test_submission_export_import/results/upload', + ['import_url' => $webform_csv_url], + t('Continue') + ); + + // Check submission count. + $this->assertRaw('Are you sure you want to import 3 submissions?'); + + // Import only the valid record. + $this->drupalPostForm( + NULL, + ['import_options[treat_warnings_as_errors]' => TRUE, 'confirm' => TRUE], + t('Import') + ); + + // Check import stats. + $this->assertRaw('Submission import completed. (total: 3; created: 1; updated: 0; skipped: 2)'); + + // Check error messages. + $this->assertRaw('<strong>Row #2:</strong> [file] Invalid file URL (/webform/plain/tests/files/sample.gif). URLS must begin with http:// or https://.'); + $this->assertRaw('<strong>Row #2:</strong> [composites] YAML is not valid. The reserved indicator "@" cannot start a plain scalar; you need to quote the scalar at line 1 (near "@#$%^not valid ':' yaml").'); + $this->assertRaw('<strong>Row #3:</strong> The email address <em class="placeholder">not an email address</em> is not valid.'); + $this->assertRaw('<strong>Row #3:</strong> An illegal choice has been detected. Please contact the site administrator.'); + + // Check the submission 1 (valid) record. + $submission_1 = $this->loadSubmissionByProperty('notes', 'valid'); + $this->assertEquals($submission_1->getElementData('summary'), 'valid'); + $this->assertEquals('e1d59c85-7096-4bee-bafa-1bd6798862e2', $submission_1->uuid()); + $this->assertEquals($this->rootUser->id(), $submission_1->getOwnerId()); + + // Check submission 1 data. + $submission_1_expected_data = [ + 'checkbox' => '1', + 'checkboxes' => [ + 0 => 'two', + 1 => 'three', + 2 => 'one', + ], + 'composite' => [ + 'title' => 'Oratione', + 'url' => 'http://example.com', + ], + 'composites' => [ + 0 => [ + 'title' => 'Oratione', + 'url' => 'http://example.com', + ], + 1 => [ + 'title' => 'Oratione', + 'url' => 'http://example.com', + ], + 2 => [ + 'title' => 'Oratione', + 'url' => 'http://test.com', + ], + ], + 'email' => 'example@example.com', + 'emails' => [ + 0 => 'example@example.com', + 1 => 'random@random.com', + 2 => 'test@test.com', + ], + 'entity_reference' => '1', + 'entity_references' => '1', + 'file' => '2', + 'files' => [ + 0 => '3', + 1 => '4', + ], + 'likert' => [ + 'q1' => '3', + 'q2' => '3', + 'q3' => '3', + ], + 'summary' => 'valid', + ]; + $submission_1_actual_data = $submission_1->getData(); + $this->assertEquals($submission_1_expected_data, $submission_1_actual_data); + + // Re-import the webform.csv using the API with warnings + // not treated as errors. + $actual_stats = $importer->import(); + WebformElementHelper::convertRenderMarkupToStrings($actual_stats); + $expected_stats = [ + 'created' => 1, + 'updated' => 1, + 'skipped' => 1, + 'total' => 3, + 'warnings' => [ + 1 => [], + 2 => [ + 0 => '[file] Invalid file URL (/webform/plain/tests/files/sample.gif). URLS must begin with http:// or https://.', + 1 => '[composites] YAML is not valid. The reserved indicator "@" cannot start a plain scalar; you need to quote the scalar at line 1 (near "@#$%^not valid ':' yaml").', + ], + 3 => [], + ], + 'errors' => [ + 1 => [], + 2 => [], + 3 => [ + 0 => 'The email address <em class="placeholder">not an email address</em> is not valid.', + 1 => 'The email address <em class="placeholder">not an email address</em> is not valid.', + 2 => 'An illegal choice has been detected. Please contact the site administrator.', + ], + ], + ]; + $this->assertEquals($expected_stats, $actual_stats); + + // Check the submission 2 (validation warnings) record. + $submission_2 = $this->loadSubmissionByProperty('notes', 'validation warnings'); + $this->assertEquals($submission_2->getElementData('summary'), 'validation warnings'); + $this->assertEquals('9a05b67b-a69a-43d8-a498-9bea83c1cbbe', $submission_2->uuid()); + + // Check submission 2 data. + $submission_2_actual_data = $submission_2->getData(); + $submission_2_expected_data = [ + 'checkbox' => '1', + 'checkboxes' => [ + 0 => 'two', + 1 => 'one', + 2 => 'three', + ], + 'composite' => [ + 'title' => 'Loremipsum', + 'url' => 'http://test.com', + ], + 'email' => 'test@test.com', + 'emails' => [ + 0 => 'random@random.com', + 1 => 'example@example.com', + 2 => 'test@test.com', + ], + 'entity_reference' => '', + 'entity_references' => '', + 'file' => '', + 'likert' => [ + 'q1' => '3', + 'q2' => '3', + 'q3' => '3', + ], + 'summary' => 'validation warnings', + ]; + $this->assertEquals($submission_2_expected_data, $submission_2_actual_data); + + // Re-import the webform.csv using the API with warnings + // not treated as errors and skipping validation errors. + $importer->setImportOptions(['skip_validation' => TRUE]); + $actual_stats = $importer->import(); + WebformElementHelper::convertRenderMarkupToStrings($actual_stats); + $expected_stats = [ + 'created' => 1, + 'updated' => 2, + 'skipped' => 0, + 'total' => 3, + 'warnings' => [ + 1 => [], + 2 => [ + 0 => '[file] Invalid file URL (/webform/plain/tests/files/sample.gif). URLS must begin with http:// or https://.', + 1 => '[composites] YAML is not valid. The reserved indicator "@" cannot start a plain scalar; you need to quote the scalar at line 1 (near "@#$%^not valid ':' yaml").', + ], + 3 => [], + ], + 'errors' => [ + 1 => [], + 2 => [], + 3 => [], + ], + ]; + $this->assertEquals($expected_stats, $actual_stats); + + // Check the submission 3 (validation warnings) record. + $submission_3 = $this->loadSubmissionByProperty('notes', 'validation errors'); + $this->assertEquals('428e338b-d09c-4bb6-8e34-7dcea79f1f0d', $submission_3->uuid()); + $this->assertEquals($submission_3->getElementData('summary'), 'validation errors'); + + // Check submission 3 contain invalid data. + $this->assertEqual($submission_3->getElementData('checkboxes'), ['invalid']); + $this->assertEqual($submission_3->getElementData('email'), 'not an email address'); + $this->assertEqual($submission_3->getElementData('emails')[2], 'not an email address'); + + // Set not_mapped destination to summary using the UI. + // Upload the webform.csv. + $this->drupalPostForm( + '/admin/structure/webform/manage/test_submission_export_import/results/upload', + ['import_url' => $webform_csv_url], + t('Continue') + ); + + $this->drupalPostForm( + NULL, + [ + 'import_options[mapping][summary]' => '', + 'import_options[mapping][not_mapped]' => 'summary', + 'confirm' => TRUE, + ], + t('Import') + ); + + // Check that submission summary now is set to not mapped. + $submission_1 = $this->loadSubmissionByProperty('notes', 'valid'); + $this->assertEquals($submission_1->getElementData('summary'), '{not mapped}'); + + // Upload the external.csv. + $this->drupalPostForm( + '/admin/structure/webform/manage/test_submission_export_import/results/upload', + ['import_url' => $external_csv_url], + t('Continue') + ); + + // Check that UUID warning is displayed. + $this->assertRaw('No UUID or token was found in the source (CSV). A unique hash will be generated for the each CSV record. Any changes to already an imported record in the source (CSV) will create a new submission.'); + + // Import the external.csv. + $this->drupalPostForm(NULL, ['confirm' => TRUE], t('Import')); + + // Check that 1 external submission created. + $this->assertRaw('Submission import completed. (total: 1; created: 1; updated: 0; skipped: 0)'); + + // Check that external submissions exists. + $submission_4 = $this->loadSubmissionByProperty('notes', 'valid external data'); + $this->assertEquals($submission_4->getElementData('summary'), 'valid external data'); + + // Upload the external.csv. + $this->drupalPostForm( + '/admin/structure/webform/manage/test_submission_export_import/results/upload', + ['import_url' => $external_csv_url], + t('Continue') + ); + + // Re-import the external.csv. + $this->drupalPostForm(NULL, ['confirm' => TRUE], t('Import')); + + // Check that 1 external submission updated. + $this->assertRaw('Submission import completed. (total: 1; created: 0; updated: 1; skipped: 0)'); + } + + /****************************************************************************/ + + /** + * Load a webform submission using a property value. + * + * @param string $property + * A submission property. + * @param string|int $value + * A property value. + * + * @return \Drupal\webform\WebformSubmissionInterface + * A webform submission. + */ + protected function loadSubmissionByProperty($property, $value) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + + // Always reset the cache. + $submission_storage->resetCache(); + + $submissions = $submission_storage->loadByProperties([$property => $value]); + return reset($submissions); + } + +} diff --git a/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.info.yml b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.info.yml new file mode 100644 index 0000000000..12660514cb --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.info.yml @@ -0,0 +1,14 @@ +name: 'Webform Submission Export/Import [EXPERIMENTAL]' +type: module +description: 'Provides the ability to export and import submissions.' +package: Webform +# core: 8.x +dependencies: + - 'devel:devel' + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.links.task.yml b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.links.task.yml new file mode 100644 index 0000000000..ebb608618d --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.links.task.yml @@ -0,0 +1,15 @@ +entity.webform_submission_export_import.results_import: + title: 'Upload' + route_name: entity.webform_submission_export_import.results_import + parent_id: entity.webform.results + weight: 11 + +# Webform node task. +# This task will be removed if the webform_node.module is not installed. +# @see webform_submission_export_import_local_tasks_alter() + +entity.node.webform_submission_export_import.results_import: + title: 'Upload' + route_name: entity.node.webform_submission_export_import.results_import + parent_id: entity.node.webform.results + weight: 11 diff --git a/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.module b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.module new file mode 100644 index 0000000000..b72e625e6a --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.module @@ -0,0 +1,254 @@ +<?php + +/** + * @file + * Provides the ability to export and import submissions. + */ + +use Drupal\file\Entity\File; + +/** + * Implements hook_webform_help_info(). + */ +function webform_submission_export_import_webform_help_info() { + $help['webform_webform_submission_export_import'] = [ + 'group' => 'forms', + 'title' => t('Upload'), + 'content' => t('The <strong>Upload</strong> page allows a CSV (comma separated values) file or URL to be uploaded, converted, and imported into webform submissions.'), + 'video_id' => 'import', + 'routes' => [ + // @see /admin/structure/webform/manage/{webform}/results/upload + 'entity.webform_submission_export_import.results_import', + // @see /node/{node}/webform/results/upload + 'entity.node.webform_submission_export_import.results_import', + ], + ]; + + return $help; +} + +/** + * Implements hook_local_tasks_alter(). + */ +function webform_submission_export_import_local_tasks_alter(&$local_tasks) { + // Remove webform node results import if the webform_node.module + // is not installed. + if (!\Drupal::moduleHandler()->moduleExists('webform_node')) { + unset( + $local_tasks['entity.node.webform_submission_export_import.results_import'] + ); + } +} + +/******************************************************************************/ +// The below code is copy of _file_save_upload_single() which allows imported +// files to be securely created. +/******************************************************************************/ + +/** + * Saves a file upload to a new location. + * + * @param \SplFileInfo $file_info + * The file upload to save. + * @param string $form_field_name + * A string that is the associative array key of the upload form element in + * the form array. + * @param array $validators + * (optional) An associative array of callback functions used to validate the + * file. + * @param bool $destination + * (optional) A string containing the URI that the file should be copied to. + * @param int $replace + * (optional) The replace behavior when the destination file already exists. + * + * @return \Drupal\file\FileInterface|false + * The created file entity or FALSE if the uploaded file not saved. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * + * @internal + * This method should only be called from file_save_upload(). Use that method + * instead. + * + * @see file_save_upload() + * @see file_save_upload_single() + */ +function _webform_submission_export_import_file_save_upload_single(\SplFileInfo $file_info, $form_field_name, array $validators = [], $destination = FALSE, $replace = FILE_EXISTS_RENAME) { + $user = \Drupal::currentUser(); + // Check for file upload errors and return FALSE for this file if a lower + // level system error occurred. For a complete list of errors: + // See http://php.net/manual/features.file-upload.errors.php. + switch ($file_info->getError()) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + \Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())])); + return FALSE; + + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + \Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()])); + return FALSE; + + case UPLOAD_ERR_OK: + /************************************************************************/ + // DO NOT USE IF UPLOADED FILE. + /************************************************************************/ + /* + // Final check that this is a valid upload, if it isn't, use the + // default error handler. + if (is_uploaded_file($file_info->getRealPath())) { + break; + } + */ + break; + + default: + // Unknown error + \Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()])); + return FALSE; + + } + + // Begin building file entity. + $values = [ + 'uid' => $user->id(), + 'status' => 0, + 'filename' => $file_info->getClientOriginalName(), + 'uri' => $file_info->getRealPath(), + 'filesize' => $file_info->getSize(), + ]; + $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); + $file = File::create($values); + + $extensions = ''; + if (isset($validators['file_validate_extensions'])) { + if (isset($validators['file_validate_extensions'][0])) { + // Build the list of non-munged extensions if the caller provided them. + $extensions = $validators['file_validate_extensions'][0]; + } + else { + // If 'file_validate_extensions' is set and the list is empty then the + // caller wants to allow any extension. In this case we have to remove the + // validator or else it will reject all extensions. + unset($validators['file_validate_extensions']); + } + } + else { + // No validator was provided, so add one using the default list. + // Build a default non-munged safe list for file_munge_filename(). + $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; + $validators['file_validate_extensions'] = []; + $validators['file_validate_extensions'][0] = $extensions; + } + + if (!empty($extensions)) { + // Munge the filename to protect against possible malicious extension + // hiding within an unknown file type (ie: filename.html.foo). + $file->setFilename(file_munge_filename($file->getFilename(), $extensions)); + } + + // Rename potentially executable files, to help prevent exploits (i.e. will + // rename filename.php.foo and filename.php to filename.php.foo.txt and + // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' + // evaluates to TRUE. + if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { + $file->setMimeType('text/plain'); + // The destination filename will also later be used to create the URI. + $file->setFilename($file->getFilename() . '.txt'); + // The .txt extension may not be in the allowed list of extensions. We have + // to add it here or else the file upload will fail. + if (!empty($extensions)) { + $validators['file_validate_extensions'][0] .= ' txt'; + \Drupal::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()])); + } + } + + // If the destination is not provided, use the temporary directory. + if (empty($destination)) { + $destination = 'temporary://'; + } + + // Assert that the destination contains a valid stream. + $destination_scheme = file_uri_scheme($destination); + if (!file_stream_wrapper_valid_scheme($destination_scheme)) { + \Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination])); + return FALSE; + } + + $file->source = $form_field_name; + // A file URI may already have a trailing slash or look like "public://". + if (substr($destination, -1) != '/') { + $destination .= '/'; + } + $file->destination = file_destination($destination . $file->getFilename(), $replace); + // If file_destination() returns FALSE then $replace === FILE_EXISTS_ERROR and + // there's an existing file so we need to bail. + if ($file->destination === FALSE) { + \Drupal::messenger()->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination])); + return FALSE; + } + + // Add in our check of the file name length. + $validators['file_validate_name_length'] = []; + + // Call the validation functions specified by this function's caller. + $errors = file_validate($file, $validators); + + // Check for errors. + if (!empty($errors)) { + $message = [ + 'error' => [ + '#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]), + ], + 'item_list' => [ + '#theme' => 'item_list', + '#items' => $errors, + ], + ]; + // @todo Add support for render arrays in + // \Drupal\Core\Messenger\MessengerInterface::addMessage()? + // @see https://www.drupal.org/node/2505497. + \Drupal::messenger()->addError(\Drupal::service('renderer')->renderPlain($message)); + return FALSE; + } + + $file->setFileUri($file->destination); + + /************************************************************************/ + // DO NOT USE MOVE UPLOADED FILE. + /************************************************************************/ + // if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) { + if (!file_unmanaged_move($file_info->getRealPath(), $file->getFileUri(), $replace)) { + \Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.')); + \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]); + return FALSE; + } + + // Set the permissions on the new file. + drupal_chmod($file->getFileUri()); + + // If we are replacing an existing file re-use its database record. + // @todo Do not create a new entity in order to update it. See + // https://www.drupal.org/node/2241865. + if ($replace == FILE_EXISTS_REPLACE) { + $existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]); + if (count($existing_files)) { + $existing = reset($existing_files); + $file->fid = $existing->id(); + $file->setOriginalId($existing->id()); + } + } + + // If we made it this far it's safe to record this file in the database. + $file->save(); + + // Allow an anonymous user who creates a non-public file to see it. See + // \Drupal\file\FileAccessControlHandler::checkAccess(). + if ($user->isAnonymous() && $destination_scheme !== 'public') { + $session = \Drupal::request()->getSession(); + $allowed_temp_files = $session->get('anonymous_allowed_file_ids', []); + $allowed_temp_files[$file->id()] = $file->id(); + $session->set('anonymous_allowed_file_ids', $allowed_temp_files); + } + return $file; +} diff --git a/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.routing.yml b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.routing.yml new file mode 100644 index 0000000000..85ecddfb05 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.routing.yml @@ -0,0 +1,80 @@ +entity.webform_submission_export_import.results_import: + path: '/admin/structure/webform/manage/{webform}/results/upload' + defaults: + _form: '\Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm' + _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _entity_access: 'webform.submission_update_any' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkResultsAccess' + +entity.webform_submission_export_import.results_import.example.download: + path: '/admin/structure/webform/manage/{webform}/results/upload/example/download' + defaults: + _controller: '\Drupal\webform_submission_export_import\Controller\WebformSubmissionExportImportController::download' + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _entity_access: 'webform.submission_update_any' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkResultsAccess' + +entity.webform_submission_export_import.results_import.example.view: + path: '/admin/structure/webform/manage/{webform}/results/upload/example/view' + defaults: + _controller: '\Drupal\webform_submission_export_import\Controller\WebformSubmissionExportImportController::view' + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _entity_access: 'webform.submission_update_any' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkResultsAccess' + +# Webform node routes. +# This route will be removed if the webform_node.module is not installed. +# @see \Drupal\webform_submission_export_import\Routing\WebformSubmissionExportImportRouteSubscriber + +entity.node.webform_submission_export_import.results_import: + path: '/node/{node}/webform/results/upload' + defaults: + _form: '\Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + operation: webform_submission_view + entity_access: 'webform.submission_view_any' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformResultsAccess' + +entity.node.webform_submission_export_import.results_import.example.download: + path: '/node/{node}/webform/results/upload/example/download' + defaults: + _controller: '\Drupal\webform_submission_export_import\Controller\WebformSubmissionExportImportController::download' + operation: webform_submission_view + entity_access: 'webform.submission_view_any' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformResultsAccess' + +entity.node.webform_submission_export_import.results_import.example.view: + path: '/node/{node}/webform/results/upload/example/view' + defaults: + _controller: '\Drupal\webform_submission_export_import\Controller\WebformSubmissionExportImportController::view' + operation: webform_submission_view + entity_access: 'webform.submission_view_any' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformResultsAccess' diff --git a/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.services.yml b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.services.yml new file mode 100644 index 0000000000..db64f978e4 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_export_import/webform_submission_export_import.services.yml @@ -0,0 +1,17 @@ +services: + webform_submission_export_import.importer: + class: Drupal\webform_submission_export_import\WebformSubmissionExportImportImporter + arguments: ['@config.factory', '@logger.factory', '@entity_type.manager', '@plugin.manager.webform.element'] + + webform_submission_export_import.route_subscriber: + class: Drupal\webform_submission_export_import\Routing\WebformSubmissionExportImportRouteSubscriber + arguments: ['@module_handler'] + tags: + - { name: event_subscriber } + + # Logger. + + logger.channel.webform_submission_export_import: + class: Drupal\Core\Logger\LoggerChannel + factory: logger.factory:get + arguments: ['webform_submission_export_import'] diff --git a/web/modules/webform/src/Controller/WebformSubmissionLogController.php b/web/modules/webform/modules/webform_submission_log/src/Controller/WebformSubmissionLogController.php similarity index 72% rename from web/modules/webform/src/Controller/WebformSubmissionLogController.php rename to web/modules/webform/modules/webform_submission_log/src/Controller/WebformSubmissionLogController.php index 460f60b71e..dcf251c756 100644 --- a/web/modules/webform/src/Controller/WebformSubmissionLogController.php +++ b/web/modules/webform/modules/webform_submission_log/src/Controller/WebformSubmissionLogController.php @@ -1,14 +1,16 @@ <?php -namespace Drupal\webform\Controller; +namespace Drupal\webform_submission_log\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Database\Connection; use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\webform\WebformInterface; use Drupal\webform\WebformRequestInterface; use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform_submission_log\WebformSubmissionLogManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -42,7 +44,7 @@ class WebformSubmissionLogController extends ControllerBase { /** * The webform storage. * - * @var \Drupal\webform\WebformStorageInterface + * @var \Drupal\webform\WebformEntityStorageInterface */ protected $webformStorage; @@ -60,6 +62,13 @@ class WebformSubmissionLogController extends ControllerBase { */ protected $requestHandler; + /** + * The webform submission log manager. + * + * @var \Drupal\webform_submission_log\WebformSubmissionLogManagerInterface + */ + protected $logManager; + /** * Constructs a WebformSubmissionLogController object. * @@ -69,14 +78,17 @@ class WebformSubmissionLogController extends ControllerBase { * The date formatter service. * @param \Drupal\webform\WebformRequestInterface $request_handler * The webform request handler. + * @param \Drupal\webform_submission_log\WebformSubmissionLogManagerInterface $log_manager + * The webform submission log manager. */ - public function __construct(Connection $database, DateFormatterInterface $date_formatter, WebformRequestInterface $request_handler) { + public function __construct(Connection $database, DateFormatterInterface $date_formatter, WebformRequestInterface $request_handler, WebformSubmissionLogManagerInterface $log_manager) { $this->database = $database; $this->dateFormatter = $date_formatter; $this->webformStorage = $this->entityTypeManager()->getStorage('webform'); $this->webformSubmissionStorage = $this->entityTypeManager()->getStorage('webform_submission'); $this->userStorage = $this->entityTypeManager()->getStorage('user'); $this->requestHandler = $request_handler; + $this->logManager = $log_manager; } /** @@ -86,7 +98,8 @@ public static function create(ContainerInterface $container) { return new static( $container->get('database'), $container->get('date.formatter'), - $container->get('webform.request') + $container->get('webform.request'), + $container->get('webform_submission_log.manager') ); } @@ -95,78 +108,51 @@ public static function create(ContainerInterface $container) { * * @param \Drupal\webform\WebformInterface|null $webform * A webform. - * @param \Drupal\webform\WebformInterface|null $webform_submission + * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission * A webform submission. * @param \Drupal\Core\Entity\EntityInterface|null $source_entity * A source entity. + * @param \Drupal\Core\Session\AccountInterface|null $account + * A user account. * * @return array * A render array as expected by drupal_render(). */ - public function overview(WebformInterface $webform = NULL, WebformSubmissionInterface $webform_submission = NULL, EntityInterface $source_entity = NULL) { + public function overview(WebformInterface $webform = NULL, WebformSubmissionInterface $webform_submission = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { + // Entities. if (empty($webform) && !empty($webform_submission)) { $webform = $webform_submission->getWebform(); } if (empty($source_entity) && !empty($webform_submission)) { $source_entity = $webform_submission->getSourceEntity(); } + $webform_entity = $webform_submission ?: $webform; // Header. $header = []; - $header['lid'] = ['data' => $this->t('#'), 'field' => 'l.lid', 'sort' => 'desc']; + $header['lid'] = ['data' => $this->t('#'), 'field' => 'log.lid', 'sort' => 'desc']; if (empty($webform)) { - $header['webform_id'] = ['data' => $this->t('Webform'), 'field' => 'l.webform_id', 'class' => [RESPONSIVE_PRIORITY_MEDIUM]]; + $header['webform_id'] = ['data' => $this->t('Webform'), 'field' => 'log.webform_id', 'class' => [RESPONSIVE_PRIORITY_MEDIUM]]; } if (empty($source_entity) && empty($webform_submission)) { $header['entity'] = ['data' => $this->t('Submitted to'), 'class' => [RESPONSIVE_PRIORITY_LOW]]; } if (empty($webform_submission)) { - $header['sid'] = ['data' => $this->t('Submission'), 'field' => 'l.sid']; + $header['sid'] = ['data' => $this->t('Submission'), 'field' => 'log.sid']; } - $header['handler_id'] = ['data' => $this->t('Handler'), 'field' => 'l.handler_id']; - $header['operation'] = ['data' => $this->t('Operation'), 'field' => 'l.operation', 'class' => [RESPONSIVE_PRIORITY_MEDIUM]]; - $header['message'] = ['data' => $this->t('Message'), 'field' => 'l.message', 'class' => [RESPONSIVE_PRIORITY_LOW]]; - $header['uid'] = ['data' => $this->t('User'), 'field' => 'ufd.name', 'class' => [RESPONSIVE_PRIORITY_LOW]]; - $header['timestamp'] = ['data' => $this->t('Date'), 'field' => 'l.timestamp', 'sort' => 'desc', 'class' => [RESPONSIVE_PRIORITY_LOW]]; + $header['handler_id'] = ['data' => $this->t('Handler'), 'field' => 'log.handler_id']; + $header['operation'] = ['data' => $this->t('Operation'), 'field' => 'log.operation', 'class' => [RESPONSIVE_PRIORITY_MEDIUM]]; + $header['message'] = ['data' => $this->t('Message'), 'field' => 'log.message', 'class' => [RESPONSIVE_PRIORITY_LOW]]; + $header['uid'] = ['data' => $this->t('User'), 'field' => 'user.name', 'class' => [RESPONSIVE_PRIORITY_LOW]]; + $header['timestamp'] = ['data' => $this->t('Date'), 'field' => 'log.timestamp', 'sort' => 'desc', 'class' => [RESPONSIVE_PRIORITY_LOW]]; // Query. - $query = $this->database->select('webform_submission_log', 'l') - ->extend('\Drupal\Core\Database\Query\PagerSelectExtender') - ->extend('\Drupal\Core\Database\Query\TableSortExtender'); - $query->leftJoin('users_field_data', 'ufd', 'l.uid = ufd.uid'); - $query->leftJoin('webform_submission', 'ws', 'l.sid = ws.sid'); - $query->fields('l', [ - 'lid', - 'uid', - 'webform_id', - 'sid', - 'handler_id', - 'operation', - 'message', - 'timestamp', - ]); - $query->fields('ws', [ - 'entity_type', - 'entity_id', - ]); - if ($webform) { - $query->condition('l.webform_id', $webform->id()); - } - if ($webform_submission) { - $query->condition('l.sid', $webform_submission->id()); - } - if ($source_entity) { - $query->condition('ws.entity_type', $source_entity->getEntityTypeId()); - $query->condition('ws.entity_id', $source_entity->id()); - } - $result = $query - ->limit(50) - ->orderByHeader($header) - ->execute(); + $options = ['header' => $header, 'limit' => 50]; + $logs = $this->logManager->loadByEntities($webform_entity, $source_entity, $account, $options); // Rows. $rows = []; - foreach ($result as $log) { + foreach ($logs as $log) { $row = []; $row['lid'] = $log->lid; if (empty($webform)) { @@ -206,7 +192,11 @@ public function overview(WebformInterface $webform = NULL, WebformSubmissionInte } $row['handler_id'] = $log->handler_id; $row['operation'] = $log->operation; - $row['message'] = $log->message; + $row['message'] = [ + 'data' => [ + '#markup' => $this->t($log->message, $log->variables), + ], + ]; $row['uid'] = [ 'data' => [ '#theme' => 'username', @@ -222,10 +212,18 @@ public function overview(WebformInterface $webform = NULL, WebformSubmissionInte '#type' => 'table', '#header' => $header, '#rows' => $rows, + '#sticky' => TRUE, '#empty' => $this->t('No log messages available.'), ]; $build['pager'] = ['#type' => 'pager']; return $build; } + /** + * Wrapper that allows the $node to be used as $source_entity. + */ + public function nodeOverview(WebformInterface $webform = NULL, WebformSubmissionInterface $webform_submission = NULL, EntityInterface $node = NULL) { + return $this->overview($webform, $webform_submission, $node); + } + } diff --git a/web/modules/webform/modules/webform_submission_log/src/Routing/WebformSubmissionLogRouteSubscriber.php b/web/modules/webform/modules/webform_submission_log/src/Routing/WebformSubmissionLogRouteSubscriber.php new file mode 100644 index 0000000000..6f218215c1 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/src/Routing/WebformSubmissionLogRouteSubscriber.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\webform_submission_log\Routing; + +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Routing\RouteSubscriberBase; +use Symfony\Component\Routing\RouteCollection; + +/** + * Remove webform node log routes. + */ +class WebformSubmissionLogRouteSubscriber extends RouteSubscriberBase { + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * Constructs a WebformSubmissionLogRouteSubscriber object. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + */ + public function __construct(ModuleHandlerInterface $module_handler) { + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + protected function alterRoutes(RouteCollection $collection) { + if (!$this->moduleHandler->moduleExists('webform_node')) { + $collection->remove('entity.node.webform.results_log'); + $collection->remove('entity.node.webform_submission.log'); + } + } + +} diff --git a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeSubmissionLogTest.php b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogNodeTest.php similarity index 69% rename from web/modules/webform/modules/webform_node/src/Tests/WebformNodeSubmissionLogTest.php rename to web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogNodeTest.php index 00978dd5a5..9ad2900369 100644 --- a/web/modules/webform/modules/webform_node/src/Tests/WebformNodeSubmissionLogTest.php +++ b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogNodeTest.php @@ -1,20 +1,25 @@ <?php -namespace Drupal\webform_node\Tests; +namespace Drupal\webform_submission_log\Tests; + +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform_node\Tests\WebformNodeTestBase; /** * Tests for webform node submission log. * - * @group WebformNode + * @group WebformSubmissionLog */ -class WebformNodeSubmissionLogTest extends WebformNodeTestBase { +class WebformSubmissionLogNodeTest extends WebformNodeTestBase { + + use WebformSubmissionLogTrait; /** * Modules to enable. * * @var array */ - public static $modules = ['block', 'webform', 'webform_node']; + public static $modules = ['block', 'webform', 'webform_node', 'webform_submission_log']; /** * Webforms to load. @@ -32,13 +37,15 @@ public function testSubmissionLog() { $nid = $node->id(); $sid = $this->postNodeSubmission($node); + $submission = WebformSubmission::load($sid); $log = $this->getLastSubmissionLog(); $this->assertEqual($log->lid, 1); $this->assertEqual($log->sid, 1); $this->assertEqual($log->uid, 0); $this->assertEqual($log->handler_id, ''); $this->assertEqual($log->operation, 'submission created'); - $this->assertEqual($log->message, t('@title: Submission #1 created.', ['@title' => $node->label()])); + $this->assertEqual($log->message, '@title created.'); + $this->assertEqual($log->variables, ['@title' => $submission->label()]); $this->assertEqual($log->webform_id, 'test_submission_log'); $this->assertEqual($log->entity_type, 'node'); $this->assertEqual($log->entity_id, $node->id()); @@ -51,7 +58,7 @@ public function testSubmissionLog() { $this->assertResponse(200); $this->assertNoRaw('No log messages available.'); $this->assertRaw('<a href="' . $base_path . 'node/' . $nid . '/webform/submission/' . $sid . '/log">' . $sid . '</a>'); - $this->assertRaw(t('@title: Submission #1 created.', ['@title' => $node->label()])); + $this->assertRaw(t('@title created.', ['@title' => $submission->label()])); // Check webform node submission log tab. $this->drupalGet("node/$nid/webform/submission/$sid/log"); diff --git a/web/modules/webform/src/Tests/WebformSubmissionLogTest.php b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTest.php similarity index 72% rename from web/modules/webform/src/Tests/WebformSubmissionLogTest.php rename to web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTest.php index c616c28894..e06c5e583b 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionLogTest.php +++ b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTest.php @@ -1,33 +1,33 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform_submission_log\Tests; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\WebformTestBase; /** - * Tests for webform storage tests. + * Tests for webform submission log. * - * @group Webform + * @group WebformSubmissionLog */ class WebformSubmissionLogTest extends WebformTestBase { + use WebformSubmissionLogTrait; + /** - * Webforms to load. + * Modules to enable. * * @var array */ - protected static $testWebforms = ['test_submission_log']; + public static $modules = ['webform', 'webform_submission_log']; /** - * {@inheritdoc} + * Webforms to load. + * + * @var array */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } + protected static $testWebforms = ['test_submission_log']; /** * Test webform submission log. @@ -35,8 +35,15 @@ public function setUp() { public function testSubmissionLog() { global $base_path; + $admin_user = $this->drupalCreateUser([ + 'administer webform', + 'access webform submission log', + ]); + $webform = Webform::load('test_submission_log'); + /**************************************************************************/ + // Check submission created. $sid_1 = $this->postSubmission($webform); $log = $this->getLastSubmissionLog(); @@ -45,7 +52,8 @@ public function testSubmissionLog() { $this->assertEqual($log->uid, 0); $this->assertEqual($log->handler_id, ''); $this->assertEqual($log->operation, 'submission created'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #1 created.'); + $this->assertEqual($log->message, '@title created.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #1']); $this->assertEqual($log->webform_id, 'test_submission_log'); $this->assertNull($log->entity_type); $this->assertNull($log->entity_id); @@ -58,7 +66,8 @@ public function testSubmissionLog() { $this->assertEqual($log->uid, 0); $this->assertEqual($log->handler_id, ''); $this->assertEqual($log->operation, 'draft created'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #2 draft created.'); + $this->assertEqual($log->message, '@title draft created.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #2']); $this->assertEqual($log->webform_id, 'test_submission_log'); $this->assertNull($log->entity_type); $this->assertNull($log->entity_id); @@ -71,7 +80,8 @@ public function testSubmissionLog() { $this->assertEqual($log->uid, 0); $this->assertEqual($log->handler_id, ''); $this->assertEqual($log->operation, 'draft updated'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #2 draft updated.'); + $this->assertEqual($log->message, '@title draft updated.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #2']); $this->assertEqual($log->webform_id, 'test_submission_log'); $this->assertNull($log->entity_type); $this->assertNull($log->entity_id); @@ -84,38 +94,41 @@ public function testSubmissionLog() { $this->assertEqual($log->uid, 0); $this->assertEqual($log->handler_id, ''); $this->assertEqual($log->operation, 'submission completed'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #2 completed using saved draft.'); + $this->assertEqual($log->message, '@title completed using saved draft.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #2']); $this->assertEqual($log->webform_id, 'test_submission_log'); $this->assertNull($log->entity_type); $this->assertNull($log->entity_id); // Login admin user. - $this->drupalLogin($this->adminWebformUser); + $this->drupalLogin($admin_user); $submission_log = $this->getSubmissionLog(); // Check submission #2 converted. $log = $submission_log[0]; $this->assertEqual($log->lid, 6); - $this->assertEqual($log->uid, $this->adminWebformUser->id()); + $this->assertEqual($log->uid, $admin_user->id()); $this->assertEqual($log->sid, 2); $this->assertEqual($log->operation, 'submission converted'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #2 converted from anonymous to ' . $this->adminWebformUser->label() . '.'); + $this->assertEqual($log->message, '@title converted from anonymous to @user.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #2', '@user' => $admin_user->label()]); // Check submission #1 converted. $log = $submission_log[1]; $this->assertEqual($log->lid, 5); - $this->assertEqual($log->uid, $this->adminWebformUser->id()); + $this->assertEqual($log->uid, $admin_user->id()); $this->assertEqual($log->sid, 1); $this->assertEqual($log->operation, 'submission converted'); - $this->assertEqual($log->message, 'Test: Submission: Logging: Submission #1 converted from anonymous to ' . $this->adminWebformUser->label() . '.'); + $this->assertEqual($log->message, '@title converted from anonymous to @user.'); + $this->assertEqual($log->variables, ['@title' => 'Test: Submission: Logging: Submission #1', '@user' => $admin_user->label()]); // Check submission updated. $this->drupalPostForm("admin/structure/webform/manage/test_submission_log/submission/$sid_2/edit", [], t('Save')); $log = $this->getLastSubmissionLog(); $this->assertEqual($log->lid, 7); $this->assertEqual($log->sid, 2); - $this->assertEqual($log->uid, $this->adminWebformUser->id()); + $this->assertEqual($log->uid, $admin_user->id()); $this->assertEqual($log->handler_id, ''); /**************************************************************************/ // $this->assertEqual($log->operation, 'submission completed'); @@ -132,25 +145,25 @@ public function testSubmissionLog() { $this->assertFalse($log); // Check all results log table is empty. - $this->drupalGet('admin/structure/webform/submissions/log'); + $this->drupalGet('/admin/structure/webform/submissions/log'); $this->assertRaw('No log messages available.'); // Check webform results log table is empty. - $this->drupalGet('admin/structure/webform/manage/test_submission_log/results/log'); + $this->drupalGet('/admin/structure/webform/manage/test_submission_log/results/log'); $this->assertRaw('No log messages available.'); $sid_3 = $this->postSubmission($webform); WebformSubmission::load($sid_3); // Check all results log table has record. - $this->drupalGet('admin/structure/webform/submissions/log'); + $this->drupalGet('/admin/structure/webform/submissions/log'); $this->assertNoRaw('No log messages available.'); $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/manage/test_submission_log/results/log">Test: Submission: Logging</a>'); $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/manage/test_submission_log/submission/3/log">3</a></td>'); $this->assertRaw('Test: Submission: Logging: Submission #3 created.'); // Check webform results log table has record. - $this->drupalGet('admin/structure/webform/manage/test_submission_log/results/log'); + $this->drupalGet('/admin/structure/webform/manage/test_submission_log/results/log'); $this->assertNoRaw('No log messages available.'); $this->assertNoRaw('<a href="' . $base_path . 'admin/structure/webform/manage/test_submission_log/results/log">Test: Submission: Logging</a>'); $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/manage/test_submission_log/submission/3/log">3</a></td>'); diff --git a/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTrait.php b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTrait.php new file mode 100644 index 0000000000..d867802206 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/src/Tests/WebformSubmissionLogTrait.php @@ -0,0 +1,75 @@ +<?php + +namespace Drupal\webform_submission_log\Tests; + +/** + * Trait for webform submission log tests. + */ +trait WebformSubmissionLogTrait { + + /** + * Get the last submission id. + * + * @return int + * The last submission id. + */ + protected function getLastSubmissionLog() { + $query = \Drupal::database()->select('webform_submission_log', 'l'); + $query->leftJoin('webform_submission', 'ws', 'l.sid = ws.sid'); + $query->fields('l', [ + 'lid', + 'uid', + 'sid', + 'handler_id', + 'operation', + 'message', + 'variables', + 'timestamp', + ]); + $query->fields('ws', [ + 'webform_id', + 'entity_type', + 'entity_id', + ]); + $query->orderBy('l.lid', 'DESC'); + $query->range(0, 1); + $submission_log = $query->execute()->fetch(); + if ($submission_log) { + $submission_log->variables = unserialize($submission_log->variables); + } + return $submission_log; + } + + /** + * Get the entire submission log. + * + * @return int + * The last submission id. + */ + protected function getSubmissionLog() { + $query = \Drupal::database()->select('webform_submission_log', 'l'); + $query->leftJoin('webform_submission', 'ws', 'l.sid = ws.sid'); + $query->fields('l', [ + 'lid', + 'uid', + 'sid', + 'handler_id', + 'operation', + 'message', + 'variables', + 'timestamp', + ]); + $query->fields('ws', [ + 'webform_id', + 'entity_type', + 'entity_id', + ]); + $query->orderBy('l.lid', 'DESC'); + $submission_logs = $query->execute()->fetchAll(); + foreach ($submission_logs as &$submission_log) { + $submission_log->variables = unserialize($submission_log->variables); + } + return $submission_logs; + } + +} diff --git a/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogLogger.php b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogLogger.php new file mode 100644 index 0000000000..0aa0cd3e85 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogLogger.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\webform_submission_log; + +use Drupal\Core\Logger\LogMessageParserInterface; +use Drupal\Core\Logger\RfcLoggerTrait; +use Psr\Log\LoggerInterface; + +/** + * Logger that listens for 'webform_submission' channel. + */ +class WebformSubmissionLogLogger implements LoggerInterface { + + use RfcLoggerTrait; + + /** + * The webform submission log manager. + * + * @var \Drupal\webform_submission_log\WebformSubmissionLogManagerInterface + */ + protected $logManager; + + /** + * The message's placeholders parser. + * + * @var \Drupal\Core\Logger\LogMessageParserInterface + */ + protected $parser; + + /** + * WebformSubmissionLog constructor. + * + * @param \Drupal\Core\Logger\LogMessageParserInterface $parser + * The log message parser service. + * @param \Drupal\webform_submission_log\WebformSubmissionLogManagerInterface $log_manager + * The webform submission log manager. + */ + public function __construct(LogMessageParserInterface $parser, WebformSubmissionLogManagerInterface $log_manager) { + $this->parser = $parser; + $this->logManager = $log_manager; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = []) { + // Only log the 'webform_submission' channel. + if ($context['channel'] !== 'webform_submission') { + return; + } + + // Make sure the context contains a webform submission. + if (!isset($context['webform_submission'])) { + return; + } + + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $context['webform_submission']; + + // Make sure webform submission log is enabled. + if (!$webform_submission->getWebform()->hasSubmissionLog()) { + return; + } + + // Set default values. + $context += [ + 'handler_id' => '', + 'operation' => '', + 'data' => [], + ]; + + // Cast message to string. + $message = (string) $message; + $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context); + + $this->logManager->insert([ + 'webform_id' => $webform_submission->getWebform()->id(), + 'sid' => $webform_submission->id(), + 'handler_id' => $context['handler_id'], + 'operation' => $context['operation'], + 'uid' => $context['uid'], + 'message' => $message, + 'variables' => serialize($message_placeholders), + 'data' => serialize($context['data']), + 'timestamp' => $context['timestamp'], + ]); + } + +} diff --git a/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManager.php b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManager.php new file mode 100644 index 0000000000..ac6fb420a9 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManager.php @@ -0,0 +1,147 @@ +<?php + +namespace Drupal\webform_submission_log; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; + +/** + * Webform submission log manager. + */ +class WebformSubmissionLogManager implements WebformSubmissionLogManagerInterface { + use DependencySerializationTrait; + + /** + * Name of the table where log entries are stored. + */ + const TABLE = 'webform_submission_log'; + + /** + * The database service. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** + * WebformSubmissionLogManager constructor. + * + * @param \Drupal\Core\Database\Connection $datababse + * The database service. + */ + public function __construct(Connection $datababse) { + $this->database = $datababse; + } + + /** + * {@inheritdoc} + */ + public function insert(array $fields) { + $fields += [ + 'webform_id' => '', + 'sid' => '', + 'handler_id' => '', + 'operation' => '', + 'uid' => '', + 'message' => '', + 'variables' => serialize([]), + 'data' => serialize([]), + 'timestamp' => '', + ]; + $this->database->insert(self::TABLE) + ->fields($fields) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function getQuery(EntityInterface $webform_entity = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, array $options = []) { + // Default options. + $options += [ + 'header' => NULL, + 'limit' => NULL, + ]; + + $query = $this->database->select(static::TABLE, 'log'); + + // Log fields. + $query->fields('log', [ + 'lid', + 'uid', + 'webform_id', + 'sid', + 'handler_id', + 'operation', + 'message', + 'variables', + 'timestamp', + 'data', + ]); + + // User fields. + $query->leftJoin('users_field_data', 'user', 'log.uid = user.uid'); + + // Submission fields. + $query->leftJoin('webform_submission', 'submission', 'log.sid = submission.sid'); + $query->fields('submission', [ + 'entity_type', + 'entity_id', + ]); + + // Webform condition. + if ($webform_entity instanceof WebformInterface) { + $query->condition('log.webform_id', $webform_entity->id()); + } + // Webform submission conditions. + elseif ($webform_entity instanceof WebformSubmissionInterface) { + $query->condition('log.webform_id', $webform_entity->getWebform()->id()); + $query->condition('log.sid', $webform_entity->id()); + } + + // Source entity conditions. + if ($source_entity) { + $query->condition('submission.entity_type', $source_entity->getEntityTypeId()); + $query->condition('submission.entity_id', $source_entity->id()); + } + + // User account condition. + if ($account) { + $query->condition('log.uid', $account->id()); + } + + // Set header sorting. + if ($options['header']) { + $query = $query->extend('\Drupal\Core\Database\Query\TableSortExtender') + ->orderByHeader($options['header']); + } + + // Set limit pager. + if ($options['limit']) { + $query = $query->extend('\Drupal\Core\Database\Query\PagerSelectExtender') + ->limit($options['limit']); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function loadByEntities(EntityInterface $webform_entity = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, array $options = []) { + $result = $this->getQuery($webform_entity, $source_entity, $account, $options) + ->execute(); + $records = []; + while ($record = $result->fetchObject()) { + $record->variables = unserialize($record->variables); + $record->data = unserialize($record->data); + $records[] = $record; + } + return $records; + } + +} diff --git a/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManagerInterface.php b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManagerInterface.php new file mode 100644 index 0000000000..1ed2cd5188 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/src/WebformSubmissionLogManagerInterface.php @@ -0,0 +1,67 @@ +<?php + +namespace Drupal\webform_submission_log; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Interface for webform submission log manager. + */ +interface WebformSubmissionLogManagerInterface { + + /** + * Insert submission log. + * + * @param array $fields + * An associative array of fields to be inserted into the submission log. + */ + public function insert(array $fields); + + /** + * Get webform submission log query. + * + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission entity. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * (optional) A webform submission source entity. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user account. + * @param array $options + * (optional) Additional options and query conditions. + * + * @return \Drupal\Core\Database\Query\SelectInterface + * A webform submission log select query. + */ + public function getQuery(EntityInterface $webform_entity = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, array $options = []); + + /** + * Log webform submission logs. + * + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission entity. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * (optional) A webform submission source entity. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user account. + * @param array $options + * (optional) Additional options and query conditions. + * + * @return array + * An array of webform submission logs. + * Log entry object includes: + * - lid: (int) ID of the log entry. + * - sid: (int) Webform submission ID on which the operation was executed. + * - uid: (int) UID of the user that executed the operation. + * - handler_id: (string) Optional name of the handler that executed the + * operation. + * - operation: (string) Name of the executed operation. + * - message: (string) Untranslated message of the executed operation. + * - variables: (array) Variables to use whenever message has to be + * translated. + * - data: (array) Data associated with this log entry. + * - timestamp: (int) Timestamp when the operation was executed. + */ + public function loadByEntities(EntityInterface $webform_entity = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, array $options = []); + +} diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.info.yml b/web/modules/webform/modules/webform_submission_log/webform_submission_log.info.yml new file mode 100644 index 0000000000..400a12eaf9 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Submission Log' +type: module +description: 'Dedicated logging and reporting for webform submissions.' +package: Webform +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.install b/web/modules/webform/modules/webform_submission_log/webform_submission_log.install new file mode 100644 index 0000000000..7f85f976c5 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.install @@ -0,0 +1,90 @@ +<?php + +/** + * @file + * Install, uninstall, and update hooks of the module. + */ + +/** + * Implements hook_schema(). + */ +function webform_submission_log_schema() { + $schema = []; + + $schema['webform_submission_log'] = [ + 'description' => 'Table that contains logs of all webform submission events.', + 'fields' => [ + 'lid' => [ + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique log event ID.', + ], + 'webform_id' => [ + 'description' => 'The webform id.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ], + 'sid' => [ + 'description' => 'The webform submission id.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ], + 'handler_id' => [ + 'description' => 'The webform handler id.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + ], + 'uid' => [ + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The {users}.uid of the user who triggered the event.', + ], + 'operation' => [ + 'type' => 'varchar_ascii', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Type of operation, for example "save", "sent", or "update."', + ], + 'message' => [ + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + 'description' => 'Text of log message.', + ], + 'variables' => [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.', + ], + 'data' => [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'description' => 'Serialized array of data.', + ], + 'timestamp' => [ + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Unix timestamp of when event occurred.', + ], + ], + 'primary key' => ['lid'], + 'indexes' => [ + 'webform_id' => ['webform_id'], + 'sid' => ['sid'], + 'uid' => ['uid'], + 'handler_id' => ['handler_id'], + 'handler_id_operation' => ['handler_id', 'operation'], + ], + ]; + + return $schema; +} diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.links.task.yml b/web/modules/webform/modules/webform_submission_log/webform_submission_log.links.task.yml new file mode 100644 index 0000000000..35baeaa824 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.links.task.yml @@ -0,0 +1,33 @@ +entity.webform.results_log: + title: 'Log' + route_name: entity.webform.results_log + parent_id: entity.webform.results + weight: 30 + +entity.webform_submission.collection_log: + title: 'Log' + route_name: entity.webform_submission.collection_log + parent_id: entity.webform_submission.collection + weight: 20 + +entity.webform_submission.log: + title: 'Log' + route_name: entity.webform_submission.log + base_route: entity.webform_submission.canonical + weight: 40 + +# Webform node task. +# This task will be removed if the webform_node.module is not installed. +# @see webform_submission_log_local_tasks_alter() + +entity.node.webform.results_log: + title: 'Log' + route_name: entity.node.webform.results_log + parent_id: entity.node.webform.results + weight: 30 + +entity.node.webform_submission.log: + title: 'Log' + route_name: entity.node.webform_submission.log + base_route: entity.node.webform_submission.canonical + weight: 40 diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.module b/web/modules/webform/modules/webform_submission_log/webform_submission_log.module new file mode 100644 index 0000000000..5fef695aca --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.module @@ -0,0 +1,87 @@ +<?php + +/** + * @file + * Dedicated logging for webform submissions. + */ + +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Implements hook_webform_help_info(). + */ +function webform_submission_log_webform_help_info() { + $help = []; + $help['submissions_log'] = [ + 'group' => 'submissions', + 'title' => t('Submissions: Log'), + 'content' => t('The <strong>Submissions log</strong> page tracks all submission events for all webforms that have submission logging enabled. Submission logging can be enabled globally or on a per webform basis.'), + 'routes' => [ + // @see /admin/structure/webform/results/log + 'entity.webform_submission.collection_log', + ], + ]; + $help['submission_log'] = [ + 'group' => 'submission', + 'title' => t('Submission: Log'), + 'content' => t("The <strong>Log</strong> page shows all events and transactions for a submission."), + 'video_id' => 'submission', + 'routes' => [ + // @see /admin/structure/webform/manage/{webform}/submission/{webform_submisssion}/log + 'entity.webform_submission.log', + // @see /node/{node}/webform/submission/{webform_submission}/log + 'entity.node.webform_submission.log', + ], + ]; + $help['results_log'] = [ + 'group' => 'submissions', + 'title' => t('Results: Log'), + 'content' => t('The <strong>Results Log</strong> lists all webform submission events for the current webform.'), + 'routes' => [ + // @see /admin/structure/webform/manage/{webform}/results/log + 'entity.webform.results_log', + ], + ]; + $help['webform_node_results_log'] = [ + 'group' => 'webform_nodes', + 'title' => t('Webform Node: Results: Log'), + 'content' => t('The <strong>Results Log</strong> lists all webform submission events for the current webform.'), + 'routes' => [ + // @see /node/{node}/webform/results/log + 'entity.node.webform.results_log', + ], + ]; + return $help; +} + +/** + * Implements hook_local_tasks_alter(). + */ +function webform_submission_log_local_tasks_alter(&$local_tasks) { + // Remove webform node log if the webform_node.module is not installed. + if (!\Drupal::moduleHandler()->moduleExists('webform_node')) { + unset( + $local_tasks['entity.node.webform.results_log'], + $local_tasks['entity.node.webform_submission.log'] + ); + } +} + +/** + * Implements hook_webform_delete(). + */ +function webform_submission_log_webform_delete(WebformInterface $webform) { + \Drupal::database()->delete('webform_submission_log') + ->condition('webform_id', $webform->id()) + ->execute(); +} + +/** + * Implements hook_webform_submission_delete(). + */ +function webform_submission_log_webform_submission_delete(WebformSubmissionInterface $webform_submission) { + \Drupal::database()->delete('webform_submission_log') + ->condition('sid', $webform_submission->id()) + ->execute(); +} diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.permissions.yml b/web/modules/webform/modules/webform_submission_log/webform_submission_log.permissions.yml new file mode 100644 index 0000000000..b17ce68d7e --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.permissions.yml @@ -0,0 +1,3 @@ +'access webform submission log': + title: 'Access the webform submission log' + description: 'Allows viewing of <em>all</em> submission events, if the user can access a webform''s results.' diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.routing.yml b/web/modules/webform/modules/webform_submission_log/webform_submission_log.routing.yml new file mode 100644 index 0000000000..d5204fef56 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.routing.yml @@ -0,0 +1,68 @@ +entity.webform_submission.collection_log: + path: '/admin/structure/webform/submissions/log' + defaults: + _controller: '\Drupal\webform_submission_log\Controller\WebformSubmissionLogController::overview' + _title: 'Webforms: Submissions log' + requirements: + _permission: 'access webform submission log' + _custom_access: '\Drupal\webform\Access\WebformAccountAccess:checkSubmissionAccess' + +entity.webform.results_log: + path: '/admin/structure/webform/manage/{webform}/results/log' + defaults: + _controller: '\Drupal\webform_submission_log\Controller\WebformSubmissionLogController::overview' + _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _permission: 'access webform submission log' + _entity_access: 'webform.submission_view_any' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkLogAccess' + +entity.webform_submission.log: + path: '/admin/structure/webform/manage/{webform}/submission/{webform_submission}/log' + defaults: + _controller: '\Drupal\webform_submission_log\Controller\WebformSubmissionLogController::overview' + _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' + requirements: + _permission: 'access webform submission log' + _entity_access: 'webform_submission.view_any' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess::checkLogAccess' + +# Webform node routes. +# This routes will be removed if the webform_node.module is not installed. +# @see \Drupal\webform_submission_log\Routing\WebformSubmissionLogRouteSubscriber + +entity.node.webform.results_log: + path: '/node/{node}/webform/results/log' + defaults: + _controller: '\Drupal\webform_submission_log\Controller\WebformSubmissionLogController::nodeOverview' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + operation: webform_submission_view + entity_access: 'webform.submission_view_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _permission: 'access webform submission log' + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformLogAccess' + +entity.node.webform_submission.log: + path: '/node/{node}/webform/submission/{webform_submission}/log' + defaults: + _controller: '\Drupal\webform_submission_log\Controller\WebformSubmissionLogController::overview' + _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' + operation: webform_submission_view + entity_access: 'webform.submission_view_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _permission: 'access webform submission log' + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformLogAccess' diff --git a/web/modules/webform/modules/webform_submission_log/webform_submission_log.services.yml b/web/modules/webform/modules/webform_submission_log/webform_submission_log.services.yml new file mode 100644 index 0000000000..fdd1f39565 --- /dev/null +++ b/web/modules/webform/modules/webform_submission_log/webform_submission_log.services.yml @@ -0,0 +1,16 @@ +services: + webform_submission_log.manager: + class: Drupal\webform_submission_log\WebformSubmissionLogManager + arguments: ['@database'] + + logger.webform_submission_log: + class: Drupal\webform_submission_log\WebformSubmissionLogLogger + arguments: ['@logger.log_message_parser', '@webform_submission_log.manager'] + tags: + - { name: logger } + + webform_submission_log.route_subscriber: + class: Drupal\webform_submission_log\Routing\WebformSubmissionLogRouteSubscriber + arguments: ['@module_handler'] + tags: + - { name: event_subscriber } diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml index 2468be3256..29ad230d81 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_contact title: 'Contact Us' description: 'A basic contact webform template.' @@ -41,6 +43,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -48,6 +51,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -63,22 +67,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -89,6 +108,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -107,9 +127,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -162,6 +184,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_confirmation: id: email @@ -179,17 +205,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: '[webform_submission:values:subject:raw]' body: '[webform_submission:values:message:value]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' @@ -205,7 +233,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -219,9 +247,11 @@ handlers: excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml index 084f19a3b0..077908e1b9 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_employee_evaluation title: 'Employee Evaluation' description: 'An employee evaluation webform template.' @@ -15,7 +17,7 @@ category: '' elements: | evaluator_information: '#title': 'Your Information' - '#type': fieldset + '#type': webform_section evaluator_first_name: '#title': 'First Name' '#type': textfield @@ -37,7 +39,7 @@ elements: | '#required': true employee_information: '#title': 'Employee Information' - '#type': fieldset + '#type': webform_section employee_first_name: '#title': 'First Name' '#type': textfield @@ -52,7 +54,7 @@ elements: | '#required': true employee_ratings: '#type': webform_likert - '#title': 'How would you rate the employee''s...' + '#title': 'How would you rate the employee''s…' '#questions': attendance: Attendance attire: Attire @@ -88,6 +90,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -95,6 +98,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -110,22 +114,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -136,6 +155,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -154,9 +174,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -209,4 +231,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_feedback.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_feedback.yml index 8b8a13c402..0f650f548b 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_feedback.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_feedback.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_feedback title: Feedback description: 'A basic feedback template.' @@ -46,6 +48,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -53,6 +56,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -68,22 +72,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -94,6 +113,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -112,9 +132,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -167,6 +189,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_confirmation: id: email @@ -184,17 +210,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default + from_name: _default + subject: _default body: '[webform_submission:values:comments:value]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' @@ -220,7 +248,7 @@ handlers: text: '[site:mail]' - value: _other_ text: '[site:mail]' - - value: _default_ + - value: _default text: '[site:mail]' cc_mail: '' cc_options: { } @@ -229,14 +257,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:name:raw]' - subject: default + subject: _default body: '[webform_submission:values:comments:value]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml index 1e83fb0cf6..107689908a 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_issue title: Issue description: 'An issue webform template.' @@ -129,6 +131,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -136,6 +139,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -151,22 +155,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: false wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -177,6 +196,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -195,9 +215,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -250,4 +272,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml index cf7236b20c..65fc513c3e 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_job_application title: 'Job Application' description: 'A job application webform template.' @@ -15,7 +17,7 @@ category: '' elements: | information: '#title': 'Your Information' - '#type': fieldset + '#type': webform_section first_name: '#title': 'First Name' '#type': textfield @@ -25,13 +27,13 @@ elements: | '#type': textfield '#required': true gender: - '#type': radios + '#type': webform_radios_other '#title': Gender '#options': gender '#required': true contact_information: '#title': 'Contact Information' - '#type': fieldset + '#type': webform_section contact: '#type': webform_contact '#title': Contact @@ -40,7 +42,7 @@ elements: | '#company__access': false resume: '#title': 'Your Resume' - '#type': fieldset + '#type': webform_section resume_method: '#type': radios '#options': @@ -89,6 +91,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -96,6 +99,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -111,22 +115,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -137,6 +156,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -155,9 +175,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -210,6 +232,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -221,7 +247,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -230,14 +256,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml index 29581be22b..419cd3700a 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_job_seeker_profile title: 'Job Seeker Profile' description: 'A job seeker profile webform template.' @@ -21,9 +23,10 @@ elements: | <li>Providing more information gives a better picture to employers</li> <li>Salary requirements, location preferences and skill level are all important factors in the hiring decision</li> </ul> + information: '#title': 'Job Seeker Information' - '#type': fieldset + '#type': webform_section first_name: '#title': 'First Name' '#type': textfield @@ -88,6 +91,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -95,6 +99,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -110,22 +115,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -136,6 +156,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -154,9 +175,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -209,6 +232,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -220,7 +247,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -229,14 +256,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml index 4d9f8cbc5a..1bcb417124 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_registration title: Registration description: 'A registration webform template.' @@ -15,7 +17,7 @@ category: '' elements: | personal_information: '#title': 'Your Personal Information' - '#type': fieldset + '#type': webform_section first_name: '#title': 'First Name' '#type': textfield @@ -26,7 +28,7 @@ elements: | '#required': true contact_information: '#title': 'Your Contact Information' - '#type': fieldset + '#type': webform_section contact: '#type': webform_contact '#title': Contact @@ -35,13 +37,13 @@ elements: | '#company__access': false mailinglist: '#title': 'Mailing List' - '#type': fieldset + '#type': webform_section subscribe: '#title': 'Please subscribe me to your mailing list.' '#type': checkbox additional_information: '#title': 'Additional Information' - '#type': fieldset + '#type': webform_section '#open': true notes: '#title': Comments @@ -58,6 +60,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -65,6 +68,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -80,22 +84,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -106,6 +125,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -124,9 +144,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -179,6 +201,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -190,7 +216,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -199,14 +225,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml index bbfef35fc3..7ae8180a08 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_session_evaluation title: 'Session Evaluation' description: 'A session evaluation webform template.' @@ -21,7 +23,7 @@ elements: | '#required': true speaker: '#type': webform_likert - '#title': 'Please rate the speaker''s...' + '#title': 'Please rate the speaker''s…' '#questions': mastery: 'Mastery of this topic' presentation: 'Presentation skills' @@ -50,6 +52,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -57,6 +60,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -72,22 +76,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: false wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -98,6 +117,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -116,9 +136,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -171,6 +193,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -182,7 +208,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -191,14 +217,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml index 703d98c0ae..774eb2f32d 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_subscribe title: Subscribe description: 'A subscribe to mailing list webform template.' @@ -37,6 +39,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -44,6 +47,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -59,22 +63,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -85,6 +104,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -103,9 +123,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -158,6 +180,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -169,7 +195,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -178,14 +204,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml index 7d86ab92e5..7102fd34f1 100644 --- a/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml +++ b/web/modules/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml @@ -6,8 +6,10 @@ dependencies: - webform_templates open: null close: null +weight: 0 uid: null template: true +archive: false id: template_user_profile title: 'User Profile' description: 'A user profile webform template.' @@ -15,7 +17,7 @@ category: '' elements: | account_information: '#title': 'Your Account Information' - '#type': fieldset + '#type': webform_section user_name: '#type': textfield '#title': 'User Name' @@ -27,7 +29,7 @@ elements: | '#file_extensions': 'gif jpg png svg' personal_information: '#title': 'Your Personal Information' - '#type': fieldset + '#type': webform_section first_name: '#title': 'First Name' '#type': textfield @@ -41,7 +43,7 @@ elements: | '#title': Country '#options': country_names location: - '#type': webform_location + '#type': webform_location_places '#title': Location '#title_display': invisible '#description': 'Your location will be saved and may be shared.' @@ -55,7 +57,7 @@ elements: | '#select2': true '#options': languages gender: - '#type': radios + '#type': webform_radios_other '#title': Gender '#options': gender biography: @@ -79,7 +81,7 @@ elements: | '#description': 'Your GitHub user name.' work_information: '#title': 'Your Work Information' - '#type': fieldset + '#type': webform_section current_organization: '#type': textfield '#title': 'Current Organization' @@ -98,7 +100,7 @@ elements: | '#select2': true email_settings: '#title': 'Email addresses' - '#type': fieldset + '#type': webform_section email: '#type': email '#title': 'Primary E-mall Address' @@ -109,7 +111,7 @@ elements: | '#description': 'Enter multiple email addresses separated by commas.' regional_settings: '#title': 'Regional Settings' - '#type': fieldset + '#type': webform_section time_zone: '#type': select '#title': Timezone @@ -131,6 +133,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -138,6 +141,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -153,22 +157,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -179,6 +198,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -197,9 +217,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -252,6 +274,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_notification: id: email @@ -263,7 +289,7 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } @@ -272,14 +298,16 @@ handlers: from_mail: '[webform_submission:values:email:raw]' from_options: { } from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' - subject: default - body: default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php b/web/modules/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php index 7ae7cc6f1d..700ee7d540 100644 --- a/web/modules/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php +++ b/web/modules/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php @@ -49,7 +49,7 @@ class WebformTemplatesController extends ControllerBase implements ContainerInje * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The webform builder. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity manager. + * The entity type manager. */ public function __construct(AccountInterface $current_user, FormBuilderInterface $form_builder, EntityTypeManagerInterface $entity_type_manager) { $this->currentUser = $current_user; @@ -73,12 +73,14 @@ public static function create(ContainerInterface $container) { * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. + * @param bool $manage + * Manage templates. * * @return array|RedirectResponse * A render array representing the webform templates index page or redirect * response to a selected webform via the filter's autocomplete. */ - public function index(Request $request) { + public function index(Request $request, $manage = FALSE) { $keys = $request->get('search'); $category = $request->get('category'); @@ -89,12 +91,14 @@ public function index(Request $request) { } } - $header = [ - $this->t('Title'), - ['data' => $this->t('Description'), 'class' => [RESPONSIVE_PRIORITY_LOW]], - ['data' => $this->t('Category'), 'class' => [RESPONSIVE_PRIORITY_LOW]], - ['data' => $this->t('Operations'), 'colspan' => 2], - ]; + $header = []; + $header['title'] = $this->t('Title'); + $header['description'] = ['data' => $this->t('Description'), 'class' => [RESPONSIVE_PRIORITY_LOW]]; + $header['category'] = ['data' => $this->t('Category'), 'class' => [RESPONSIVE_PRIORITY_LOW]]; + if ($manage) { + $header['owner'] = ['data' => $this->t('Author'), 'class' => [RESPONSIVE_PRIORITY_LOW]]; + } + $header['operations'] = ['data' => $this->t('Operations')]; $webforms = $this->getTemplates($keys, $category); $rows = []; @@ -104,20 +108,65 @@ public function index(Request $request) { $row['title'] = $webform->toLink(); $row['description']['data']['#markup'] = $webform->get('description'); $row['category']['data']['#markup'] = $webform->get('category'); - if ($this->currentUser->hasPermission('create webform')) { + + if ($manage) { + $row['owner'] = ($owner = $webform->getOwner()) ? $owner->toLink() : ''; + + $operations = []; + if ($webform->access('update')) { + $operations['edit'] = [ + 'title' => $this->t('Build'), + 'url' => $this->ensureDestination($webform->toUrl('edit-form')), + ]; + } + if ($webform->access('submission_page')) { + $operations['view'] = [ + 'title' => $this->t('View'), + 'url' => $webform->toUrl('canonical'), + ]; + } + if ($webform->access('update')) { + $operations['settings'] = [ + 'title' => $this->t('Settings'), + 'url' => $webform->toUrl('settings'), + ]; + } + if ($webform->access('duplicate')) { + $operations['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'url' => $webform->toUrl('duplicate-form', ['query' => ['template' => 1]]), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; + } + if ($webform->access('delete') && $webform->hasLinkTemplate('delete-form')) { + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => $this->ensureDestination($webform->toUrl('delete-form')), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; + } + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => $operations, + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + } + else { $row['operations']['data']['select'] = [ '#type' => 'link', '#title' => $this->t('Select'), '#url' => Url::fromRoute('entity.webform.duplicate_form', $route_parameters), '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['button', 'button--primary']), ]; + $row['operations']['data']['preview'] = [ + '#type' => 'link', + '#title' => $this->t('Preview'), + '#url' => Url::fromRoute('entity.webform.preview', $route_parameters), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button']), + ]; } - $row['operations']['data']['preview'] = [ - '#type' => 'link', - '#title' => $this->t('Preview'), - '#url' => Url::fromRoute('entity.webform.preview', $route_parameters), - '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button']), - ]; + $rows[] = $row; } @@ -137,6 +186,7 @@ public function index(Request $request) { '#type' => 'table', '#header' => $header, '#rows' => $rows, + '#sticky' => TRUE, '#empty' => $this->t('There are no templates available.'), '#cache' => [ 'contexts' => $this->webformStorage->getEntityType()->getListCacheContexts(), @@ -183,6 +233,7 @@ public function previewForm(Request $request, WebformInterface $webform) { protected function getTemplates($keys = '', $category = '') { $query = $this->webformStorage->getQuery(); $query->condition('template', TRUE); + $query->condition('archive', FALSE); // Filter by key(word). if ($keys) { $or = $query->orConditionGroup() @@ -245,4 +296,17 @@ protected function isAdmin() { return ($this->currentUser->hasPermission('administer webform') || $this->currentUser->hasPermission('edit any webform')); } + /** + * Ensures that a destination is present on the given URL. + * + * @param \Drupal\Core\Url $url + * The URL object to which the destination should be added. + * + * @return \Drupal\Core\Url + * The updated URL object. + */ + protected function ensureDestination(Url $url) { + return $url->mergeOptions(['query' => $this->getRedirectDestination()->getAsArray()]); + } + } diff --git a/web/modules/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php b/web/modules/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php index a05279a715..40f130fc3a 100644 --- a/web/modules/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php +++ b/web/modules/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php @@ -10,7 +10,7 @@ /** * Provides the webform templates filter webform. */ -class WebformtemplatesFilterForm extends FormBase { +class WebformTemplatesFilterForm extends FormBase { /** * {@inheritdoc} diff --git a/web/modules/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php b/web/modules/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php index 26c164b3c3..9802169016 100644 --- a/web/modules/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php +++ b/web/modules/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php @@ -31,8 +31,19 @@ class WebformTemplatesTest extends WebformTestBase { * Tests webform templates. */ public function testTemplates() { - // Login the own user. - $this->drupalLogin($this->rootUser); + $user_account = $this->drupalCreateUser([ + 'access webform overview', + 'administer webform', + ]); + + $admin_account = $this->drupalCreateUser([ + 'access webform overview', + 'administer webform', + 'administer webform templates', + ]); + + // Login the user. + $this->drupalLogin($user_account); $template_webform = Webform::load('test_form_template'); @@ -42,14 +53,33 @@ public function testTemplates() { $this->assertTrue($template_webform->isClosed()); // Check template is included in the 'Templates' list display. - $this->drupalGet('admin/structure/webform/templates'); + $this->drupalGet('/admin/structure/webform/templates'); $this->assertRaw('Test: Webform: Template'); $this->assertRaw('Test using a webform as a template.'); // Check template is accessible to user with create webform access. - $this->drupalGet('webform/test_form_template'); + $this->drupalGet('/webform/test_form_template'); $this->assertResponse(200); $this->assertRaw('You are previewing the below template,'); + + // Check select template clears the description. + $this->drupalGet('/admin/structure/webform/manage/test_form_template/duplicate'); + $this->assertFieldByName('description[value]', ''); + + // Check that admin can not access manage templates. + $this->drupalGet('/admin/structure/webform/templates/manage'); + $this->assertResponse(403); + + // Login the admin. + $this->drupalLogin($admin_account); + + // Check that admin can access manage templates. + $this->drupalGet('/admin/structure/webform/templates/manage'); + $this->assertResponse(200); + + // Check select template clears the description. + $this->drupalGet('/admin/structure/webform/manage/test_form_template/duplicate', ['query' => ['template' => 1]]); + $this->assertFieldByName('description[value]', 'Test using a webform as a template.'); } } diff --git a/web/modules/webform/modules/webform_templates/webform_templates.info.yml b/web/modules/webform/modules/webform_templates/webform_templates.info.yml index f4ca970e98..e4e725cc5a 100644 --- a/web/modules/webform/modules/webform_templates/webform_templates.info.yml +++ b/web/modules/webform/modules/webform_templates/webform_templates.info.yml @@ -6,8 +6,8 @@ package: Webform dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_templates/webform_templates.links.task.yml b/web/modules/webform/modules/webform_templates/webform_templates.links.task.yml index 0bf39c9af9..c3f5645cbe 100644 --- a/web/modules/webform/modules/webform_templates/webform_templates.links.task.yml +++ b/web/modules/webform/modules/webform_templates/webform_templates.links.task.yml @@ -3,3 +3,15 @@ entity.webform.templates: route_name: entity.webform.templates base_route: entity.webform.collection weight: 10 + +entity.webform.templates.view: + title: 'List' + route_name: entity.webform.templates + parent_id: entity.webform.templates + weight: 0 + +entity.webform.templates.manage: + title: 'Manage' + route_name: entity.webform.templates.manage + parent_id: entity.webform.templates + weight: 10 diff --git a/web/modules/webform/modules/webform_templates/webform_templates.permissions.yml b/web/modules/webform/modules/webform_templates/webform_templates.permissions.yml new file mode 100644 index 0000000000..5e871e765e --- /dev/null +++ b/web/modules/webform/modules/webform_templates/webform_templates.permissions.yml @@ -0,0 +1,4 @@ +'administer webform templates': + title: 'Administer webform templates' + description: 'Allows administration to manage webform templates.' + restrict access: true diff --git a/web/modules/webform/modules/webform_templates/webform_templates.routing.yml b/web/modules/webform/modules/webform_templates/webform_templates.routing.yml index a26f7ba9e9..9e0fb7f25d 100644 --- a/web/modules/webform/modules/webform_templates/webform_templates.routing.yml +++ b/web/modules/webform/modules/webform_templates/webform_templates.routing.yml @@ -4,7 +4,17 @@ entity.webform.templates: _controller: '\Drupal\webform_templates\Controller\WebformTemplatesController::index' _title: 'Webforms: Templates' requirements: - _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkOverviewAccess' + _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkTemplatesAccess' + +entity.webform.templates.manage: + path: '/admin/structure/webform/templates/manage' + defaults: + _controller: '\Drupal\webform_templates\Controller\WebformTemplatesController::index' + _title: 'Webforms: Templates' + manage: true + requirements: + _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkTemplatesAccess' + _permission: 'administer webform templates' entity.webform.preview: path: '/webform/{webform}/preview' @@ -20,4 +30,4 @@ entity.webform.templates.autocomplete: _controller: '\Drupal\webform\Controller\WebformEntityController::autocomplete' templates: TRUE requirements: - _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkOverviewAccess' + _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkTemplatesAccess' diff --git a/web/modules/webform/modules/webform_ui/css/webform_ui.module.css b/web/modules/webform/modules/webform_ui/css/webform_ui.module.css index 26a468856b..9d2561c864 100644 --- a/web/modules/webform/modules/webform_ui/css/webform_ui.module.css +++ b/web/modules/webform/modules/webform_ui/css/webform_ui.module.css @@ -14,6 +14,25 @@ thead th .dropbutton { text-transform: none; } +/** + * Actions. + * + * Secondary buttons when .button--primary is removed. + * @see Drupal.behaviors.webformUiElementsActionsSecondary + */ +.action-links .button--secondary.button--primary { + display: none; +} + +/** + * Hide secondary buttons for mobile. + */ +@media screen and (max-width: 600px) { + .action-links .button--secondary { + display: none; + } +} + /** * Elements (/admin/structure/webform/manage/{webform}) */ @@ -63,6 +82,13 @@ thead th .dropbutton { font-weight: bold; } +.webform-ui-elements-table tr.webform-ui-element-type-details input, +.webform-ui-elements-table tr.webform-ui-element-type-details select, +.webform-ui-elements-table tr.webform-ui-element-type-fieldset input, +.webform-ui-elements-table tr.webform-ui-element-type-fieldset select { + font-weight: normal; +} + .webform-ui-elements-table tr.webform-ui-element-type-webform_actions td { border-top: 1px solid #a6a6a6; border-bottom: 1px solid #a6a6a6; @@ -70,6 +96,24 @@ thead th .dropbutton { font-weight: bold; } +.webform-ui-elements-table tr.webform-ui-element-type-webform_actions input, +.webform-ui-elements-table tr.webform-ui-element-type-webform_actions select { + font-weight: normal; +} + +@media screen and (max-width: 1280px) { + .js-off-canvas-dialog-open .webform-ui-elements-table th, + .js-off-canvas-dialog-open .webform-ui-elements-table td { + display: none; + } + .js-off-canvas-dialog-open .webform-ui-elements-table th:first-child, + .js-off-canvas-dialog-open .webform-ui-elements-table th:last-child, + .js-off-canvas-dialog-open .webform-ui-elements-table td:first-child, + .js-off-canvas-dialog-open .webform-ui-elements-table td:last-child { + display: table-cell; + } +} + /** * Element types (/admin/structure/webform/manage/[webform_id]/element/add) */ @@ -131,7 +175,7 @@ thead th .dropbutton { } /** - * Element (/admin/structure/webform/manage/[webform_id]/element/add/[elment_type]) + * Element (/admin/structure/webform/manage/[webform_id]/element/add/[element_type]) * @see \Drupal\webform\Plugin\WebformElementBase::form */ .form--inline.webform-ui-element-form-inline--input .form-item { diff --git a/web/modules/webform/modules/webform_ui/js/webform_ui.js b/web/modules/webform/modules/webform_ui/js/webform_ui.js index 6f4d96c596..467019721b 100644 --- a/web/modules/webform/modules/webform_ui/js/webform_ui.js +++ b/web/modules/webform/modules/webform_ui/js/webform_ui.js @@ -8,22 +8,104 @@ 'use strict'; /** - * Lock the default actions element by moving it to the table footer (<tfoot>). + * Remove .button-primary class from .action-links .button-secondary. + * + * The seven.theme adds the .button-primary class to all actions. * * @type {Drupal~behavior} * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior for locking the default actions element by moving - * it to the table footer (<tfoot>). + * @see webform_ui_preprocess_menu_local_action() + * @see seven_preprocess_menu_local_action() + * @see webform_ui.module.css */ - Drupal.behaviors.webformUiElementsActionsDefault = { + Drupal.behaviors.webformUiElementsActionsSecondary = { attach: function (context, settings) { - $(context).find('[data-drupal-selector="edit-webform-ui-elements-webform-actions-default"]').once('webform-ui-elements-webform-actions-default').each(function () { - var $tr = $(this); - var $table = $tr.parents('table'); - $table.append($('<tfoot></tfoot>').append($tr)); + $(context).find('.action-links .button--secondary').once('webform-ui-elements-webform-actions-secondary').each(function () { + $(this).removeClass('button--primary'); }); } }; + /** + * Adds keyboard support to the form builder. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformUiElementsKeyboard = { + attach: function (context, settings) { + var $table = $(context) + .find('.webform-ui-elements-table') + .once('webform-ui-elements-keyboard'); + + // Disable autosubmit when Enter is pressed on 'Required' checkboxes. + $table.find('td input:checkbox') + .on('keyup keypress', function (e) { + if (e.which === 13) { + e.preventDefault(); + return false; + } + }); + + // Move keyboard focus up (38) or down (40). + $table.find('td:first-child a:not(.tabledrag-handle), td input:checkbox, td .webform-dropbutton li.dropbutton-action a, td .webform-dropbutton button') + .on('keydown', function (event) { + if (event.which === 38 || event.which === 40) { + var $cell = $(this).closest('td'); + var $row = $cell.parent(); + var direction = (event.which === 38) ? 'prev' : 'next'; + var index = $cell.index(); + var tagName = this.tagName; + while ($row[direction]().length) { + $row = $row[direction](); + $cell = $row.find('td').eq(index).find(tagName); + if ($cell.length) { + $cell.focus(); + break; + } + } + event.preventDefault(); + } + }); + + // Move keyboard focus left (37) or right (39). + $table.find('td a:not(.tabledrag-handle), td input, td select, td button') + .on('keydown', function (event) { + if (event.which === 37 || event.which === 39) { + var $cell = $(this).closest('td'); + var direction = (event.which === 37) ? 'prev' : 'next'; + var $focus; + + + // Move keyboard focus within operations dropbutton. + if ($(this).closest('.webform-dropbutton').length) { + if (direction === 'next' && + this.tagName === 'A' && + $(this).parent('.dropbutton-action').length) { + $cell.find('button').focus(); + event.preventDefault(); + return; + } + else if (direction === 'prev' && this.tagName === 'BUTTON') { + $cell.find('a').focus(); + event.preventDefault(); + return; + } + } + + while ($cell.length) { + $cell = $cell[direction](); + $focus = $cell.find('a:visible, input:visible, select:visible'); + if ($focus.length) { + $focus.focus(); + event.preventDefault(); + return; + } + } + } + + }); + + } + }; + })(jQuery, Drupal, drupalSettings); diff --git a/web/modules/webform/modules/webform_ui/src/Access/WebformUiAccess.php b/web/modules/webform/modules/webform_ui/src/Access/WebformUiAccess.php index d4d774d86c..3bdf4a1583 100644 --- a/web/modules/webform/modules/webform_ui/src/Access/WebformUiAccess.php +++ b/web/modules/webform/modules/webform_ui/src/Access/WebformUiAccess.php @@ -24,7 +24,8 @@ class WebformUiAccess { * The access result. */ public static function checkWebformSourceAccess(WebformInterface $webform, AccountInterface $account) { - return AccessResult::allowedIf($webform->access('update', $account) && $account->hasPermission('edit webform source')); + return $webform->access('update', $account, TRUE) + ->andIf(AccessResult::allowedIfHasPermission($account, 'edit webform source')); } /** @@ -39,7 +40,8 @@ public static function checkWebformSourceAccess(WebformInterface $webform, Accou * The access result. */ public static function checkWebformOptionSourceAccess(WebformOptionsInterface $webform_options, AccountInterface $account) { - return AccessResult::allowedIf($webform_options->access('update', $account) && $account->hasPermission('edit webform source')); + return $webform_options->access('update', $account, TRUE) + ->andIf(AccessResult::allowedIfHasPermission($account, 'edit webform source')); } /** @@ -54,7 +56,31 @@ public static function checkWebformOptionSourceAccess(WebformOptionsInterface $w * The access result. */ public static function checkWebformEditAccess(WebformInterface $webform, AccountInterface $account) { - return AccessResult::allowedIf($webform->access('update', $account)); + return $webform->access('update', $account, TRUE); + } + + /** + * Check that webform element type can be added by a user. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param string $type + * An element type. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkWebformElementAccess(WebformInterface $webform, $type, AccountInterface $account) { + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + + $access = $webform->access('update', $account, TRUE); + $access = $access->andIf(!$element_manager->isExcluded($type) ? AccessResult::allowed() : AccessResult::forbidden()); + $access->addCacheableDependency($webform); + $access->addCacheableDependency(\Drupal::config('webform.settings')); + return $access; } } diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php index f950b67371..9ed5534b4b 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php @@ -11,6 +11,11 @@ */ class WebformUiElementAddForm extends WebformUiElementFormBase { + /** + * {@inheritdoc} + */ + protected $operation = 'create'; + /** * {@inheritdoc} */ @@ -21,7 +26,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $parent_key = $this->getRequest()->get('parent'); if ($parent_key) { - $parent_element = $webform->getElementDecoded($parent_key); + $parent_element = $webform->getElement($parent_key); if (!$parent_element) { throw new NotFoundHttpException(); } @@ -29,7 +34,14 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $element_plugin = $this->getWebformElementPlugin(); - $form['#title'] = $this->t('Add @label element', ['@label' => $element_plugin->getPluginLabel()]); + $t_args = ['@label' => $element_plugin->getPluginLabel()]; + if ($parent_key) { + $t_args['@parent'] = $parent_element['#admin_title'] ?: $parent_element['#title'] ?: $parent_element['#webform_key']; + $form['#title'] = $this->t('Add @label element to "@parent"', $t_args); + } + else { + $form['#title'] = $this->t('Add @label element', $t_args); + } $form = parent::buildForm($form, $form_state, $webform, NULL, $parent_key); if (isset($form['properties']['element']['title'])) { diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php index 6e18e7ba76..5f154253b1 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php @@ -3,10 +3,9 @@ namespace Drupal\webform_ui\Form; use Drupal\Component\Render\FormattableMarkup; -use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\RendererInterface; -use Drupal\webform\Form\WebformDialogFormTrait; +use Drupal\webform\Form\WebformDeleteFormBase; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\WebformEntityElementsValidatorInterface; use Drupal\webform\WebformInterface; @@ -16,9 +15,7 @@ /** * Webform for deleting a webform element. */ -class WebformUiElementDeleteForm extends ConfirmFormBase { - - use WebformDialogFormTrait; +class WebformUiElementDeleteForm extends WebformDeleteFormBase { /** * The renderer. @@ -96,84 +93,86 @@ public static function create(ContainerInterface $container) { ); } + /****************************************************************************/ + // Delete form. + /****************************************************************************/ + /** * {@inheritdoc} */ - public function getDescription() { - $t_args = [ - '%element' => $this->getElementTitle(), - '%webform' => $this->webform->label(), - ]; - - $build = []; - $element_plugin = $this->getWebformElementPlugin(); - if ($element_plugin->isContainer($this->element)) { - $build['warning'] = [ - '#markup' => $this->t('This will immediately delete the %element container and all nested elements within %element from the %webform webform. This cannot be undone.', $t_args), + public function getQuestion() { + if ($this->isDialog()) { + $t_args = [ + '@title' => $this->getElementTitle(), ]; + return $this->t("Delete the '@title' element?", $t_args); } else { - $build['warning'] = [ - '#markup' => $this->t('This will immediately delete the %element element from the %webform webform. This cannot be undone.', $t_args), + $t_args = [ + '%webform' => $this->webform->label(), + '%title' => $this->getElementTitle(), ]; + return $this->t('Delete the %title element from the %webform webform?', $t_args); } + } - if ($this->element['#webform_children']) { - $build['elements'] = $this->getDeletedElementsItemList($this->element['#webform_children']); - $build['elements']['#title'] = t('The below nested elements will be also deleted.'); - } - - return $this->renderer->renderPlain($build); + /** + * {@inheritdoc} + */ + public function getWarning() { + $t_args = ['%title' => $this->getElementTitle()]; + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to delete the %title element?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; } /** - * Get deleted elements as item list. - * - * @param array $children - * An array child key. - * - * @return array - * A render array representing an item list of elements. + * {@inheritdoc} */ - protected function getDeletedElementsItemList(array $children) { - if (empty($children)) { - return []; - } + public function getDescription() { + $element_plugin = $this->getWebformElementPlugin(); $items = []; - foreach ($children as $key) { - $element = $this->webform->getElement($key); - if (isset($element['#title'])) { - $title = new FormattableMarkup('@title (@key)', ['@title' => $element['#title'], '@key' => $key]); - } - else { - $title = $key; - } - $items[$key]['title'] = ['#markup' => $title]; - if ($element['#webform_children']) { - $items[$key]['items'] = $this->getDeletedElementsItemList($element['#webform_children']); - } + $items[] = $this->t('Remove this element'); + $items[] = $this->t('Delete any submission data associated with this element'); + if ($element_plugin->isContainer($this->element)) { + $items[] = $this->t('Delete all child elements'); } return [ - '#theme' => 'item_list', - '#items' => $items, + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => $items, + ], ]; } /** * {@inheritdoc} */ - public function getQuestion() { - return $this->t('Are you sure you want to delete the %title element from the %webform webform?', ['%webform' => $this->webform->label(), '%title' => $this->getElementTitle()]); + public function getDetails() { + $elements = $this->getDeletedElementsItemList($this->element['#webform_children']); + if ($elements) { + return [ + '#type' => 'details', + '#title' => $this->t('Nested elements being deleted'), + 'elements' => $elements, + ]; + } + else { + return []; + } } - /** - * {@inheritdoc} - */ - public function getConfirmText() { - return $this->t('Delete'); - } + /****************************************************************************/ + // Form methods. + /****************************************************************************/ /** * {@inheritdoc} @@ -213,10 +212,49 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $this->webform->deleteElement($this->key); $this->webform->save(); - drupal_set_message($this->t('The webform element %title has been deleted.', ['%title' => $this->getElementTitle()])); + $this->messenger()->addStatus($this->t('The webform element %title has been deleted.', ['%title' => $this->getElementTitle()])); $form_state->setRedirectUrl($this->webform->toUrl('edit-form')); } + /****************************************************************************/ + // Helper methods. + /****************************************************************************/ + + /** + * Get deleted elements as item list. + * + * @param array $children + * An array child key. + * + * @return array + * A render array representing an item list of elements. + */ + protected function getDeletedElementsItemList(array $children) { + if (empty($children)) { + return []; + } + + $items = []; + foreach ($children as $key) { + $element = $this->webform->getElement($key); + if (isset($element['#title'])) { + $title = new FormattableMarkup('@title (@key)', ['@title' => $element['#title'], '@key' => $key]); + } + else { + $title = $key; + } + $items[$key]['title'] = ['#markup' => $title]; + if ($element['#webform_children']) { + $items[$key]['items'] = $this->getDeletedElementsItemList($element['#webform_children']); + } + } + + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + } + /** * Get the webform element's title or key. * diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php index ca007fc18d..c806efbf91 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php @@ -11,6 +11,11 @@ */ class WebformUiElementDuplicateForm extends WebformUiElementFormBase { + /** + * {@inheritdoc} + */ + protected $operation = 'duplicate'; + /** * {@inheritdoc} */ @@ -26,9 +31,8 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $element_initialized = $webform->getElement($key); - $form['#title'] = $this->t('Duplicate @title element', [ - '@title' => (!empty($this->element['#title'])) ? $this->element['#title'] : $key, - ]); + $t_args = ['@title' => $this->element['#admin_title'] ?: $this->element['#title']]; + $form['#title'] = $this->t('Duplicate @title element', $t_args); $this->action = $this->t('created'); return parent::buildForm($form, $form_state, $webform, NULL, $element_initialized['#webform_parent_key']); diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php index 333b2c7c2d..651dfb3214 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php @@ -6,7 +6,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Markup; use Drupal\Core\Url; -use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\WebformInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -15,6 +14,11 @@ */ class WebformUiElementEditForm extends WebformUiElementFormBase { + /** + * {@inheritdoc} + */ + protected $operation = 'update'; + /** * {@inheritdoc} */ @@ -45,45 +49,10 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $form = parent::buildForm($form, $form_state, $webform, $key); - // ISSUE: - // The below delete link with .use-ajax is throwing errors because the modal - // dialog code is creating a <button> without any parent form. - // Issue #2879304: Editing Select Other elements produces JavaScript errors - // @see Drupal.Ajax - /* - if ($this->isModalDialog()) { - $form['actions']['delete'] = [ - '#type' => 'link', - '#title' => $this->t('Delete'), - '#url' => new Url( - 'entity.webform_ui.element.delete_form', - [ - 'webform' => $webform->id(), - 'key' => $key, - ] - ), - '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::NARROW_DIALOG, ['button', 'button--danger']), - ]; - } - */ - - // WORKAROUND: - // Create a hidden link that is clicked using jQuery. - if ($this->isDialog()) { - $form['delete'] = [ - '#type' => 'link', - '#title' => $this->t('Delete'), - '#url' => new Url('entity.webform_ui.element.delete_form', ['webform' => $webform->id(), 'key' => $key]), - '#attributes' => ['style' => 'display:none'] + WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['webform-ui-element-delete-link']), - ]; - $form['actions']['delete'] = [ - '#type' => 'submit', - '#value' => $this->t('Delete'), - '#attributes' => [ - 'class' => ['button', 'button--danger'], - 'onclick' => "jQuery('.webform-ui-element-delete-link').click(); return false;", - ], - ]; + // Delete action. + if (!$form_state->get('default_value_element')) { + $url = new Url('entity.webform_ui.element.delete_form', ['webform' => $webform->id(), 'key' => $key]); + $this->buildDialogDeleteAction($form, $form_state, $url); } return $form; diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php index cfad8991e2..e06e425161 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php @@ -2,13 +2,13 @@ namespace Drupal\webform_ui\Form; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; use Drupal\Core\Render\RendererInterface; -use Drupal\Core\Serialization\Yaml; use Drupal\Core\Url; use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\Form\WebformDialogFormTrait; @@ -16,6 +16,7 @@ use Drupal\webform\Utility\WebformYaml; use Drupal\webform\WebformEntityElementsValidatorInterface; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -66,6 +67,13 @@ abstract class WebformUiElementFormBase extends FormBase implements WebformUiEle */ protected $elementsValidator; + /** + * The token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + /** * The webform. * @@ -101,6 +109,13 @@ abstract class WebformUiElementFormBase extends FormBase implements WebformUiEle */ protected $originalType; + /** + * The operation of the current webform. + * + * @var string + */ + protected $operation; + /** * The action of the current webform. * @@ -126,12 +141,15 @@ public function getFormId() { * The webform element manager. * @param \Drupal\webform\WebformEntityElementsValidatorInterface $elements_validator * Webform element validator. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. */ - public function __construct(RendererInterface $renderer, EntityFieldManagerInterface $entity_field_manager, WebformElementManagerInterface $element_manager, WebformEntityElementsValidatorInterface $elements_validator) { + public function __construct(RendererInterface $renderer, EntityFieldManagerInterface $entity_field_manager, WebformElementManagerInterface $element_manager, WebformEntityElementsValidatorInterface $elements_validator, WebformTokenManagerInterface $token_manager) { $this->renderer = $renderer; $this->entityFieldManager = $entity_field_manager; $this->elementManager = $element_manager; $this->elementsValidator = $elements_validator; + $this->tokenManager = $token_manager; } /** @@ -142,7 +160,8 @@ public static function create(ContainerInterface $container) { $container->get('renderer'), $container->get('entity_field.manager'), $container->get('plugin.manager.webform.element'), - $container->get('webform.elements_validator') + $container->get('webform.elements_validator'), + $container->get('webform.token_manager') ); } @@ -200,7 +219,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn '#type' => 'link', '#title' => $this->t('Cancel'), '#url' => new Url('entity.webform_ui.element.edit_form', $route_parameters), - '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button', 'button--small']), + '#attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button', 'button--small']), ]; $form['properties']['element']['type']['#description'] = '(' . $this->t('Changing from %type', ['%type' => $original_webform_element->getPluginLabel()]) . ')'; } @@ -270,6 +289,21 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn '#button_type' => 'primary', '#_validate_form' => TRUE, ]; + if ($this->operation === 'create' && $this->isAjax()) { + $form['actions']['save_add_element'] = [ + '#type' => 'submit', + '#value' => $this->t('Save + Add element'), + '#_validate_form' => TRUE, + ]; + } + + // Add token links below the form and on every tab. + $form['token_tree_link'] = $this->tokenManager->buildTreeElement(); + if ($form['token_tree_link']) { + $form['token_tree_link'] += [ + '#weight' => 101, + ]; + } $form = $this->buildDefaultValueForm($form, $form_state); @@ -320,7 +354,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $t_args = [':href' => Url::fromRoute('entity.webform.source_form', ['webform' => $webform->id()])->toString()]; $form_state->setErrorByName('elements', $this->t('There has been error validating the elements. You may need to edit the <a href=":href">YAML source</a> to resolve the issue.', $t_args)); foreach ($messages as $message) { - drupal_set_message($message, 'error'); + $this->messenger()->addError($message); } } } @@ -329,6 +363,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { + $op = $form_state->getValue('op'); $parent_key = $form_state->getValue('parent_key'); $key = $form_state->getValue('key'); @@ -354,18 +389,28 @@ public function submitForm(array &$form, FormStateInterface $form_state) { '%title' => (!empty($properties['title'])) ? $properties['title'] : $key, '@action' => $this->action, ]; - drupal_set_message($this->t('%title has been @action.', $t_args)); + $this->messenger()->addStatus($this->t('%title has been @action.', $t_args)); + + // Determine add element parent key. + $save_and_add_element = ($op == (string) $this->t('Save + Add element')) ? TRUE : FALSE; + $add_element = ($element_plugin->isContainer($this->getElement())) ? $key : $parent_key; + $add_element = $add_element ? Html::getClass($add_element) : '_root_'; // Append ?update= to (redirect) destination. if ($this->requestStack->getCurrentRequest()->query->get('destination')) { $redirect_destination = $this->getRedirectDestination(); $destination = $redirect_destination->get(); $destination .= (strpos($destination, '?') !== FALSE ? '&' : '?') . 'update=' . $key; + $destination .= ($save_and_add_element) ? '&add_element=' . $add_element : ''; $redirect_destination->set($destination); } // Still set the redirect URL just to be safe. - $form_state->setRedirectUrl($this->webform->toUrl('edit-form', ['query' => ['update' => $key]])); + $query = ['update' => $key]; + if ($save_and_add_element) { + $query['add_element'] = $add_element; + } + $form_state->setRedirectUrl($this->webform->toUrl('edit-form', ['query' => $query])); } /** @@ -459,13 +504,13 @@ public function exists($key) { /** * Get the default key for the current element. * - * Default key will be auto incremented when there are duplicate keys. + * Default key will be auto incremented when there are duplicate keys. * * @return null|string * An element's default key which will be incremented to prevent duplicate * keys. */ - protected function getDefaultKey() { + public function getDefaultKey() { $element_plugin = $this->getWebformElementPlugin(); if (empty($element_plugin->getDefaultKey())) { return NULL; @@ -537,9 +582,6 @@ public function buildDefaultValueForm(array &$form, FormStateInterface $form_sta $form['properties']['#type'] = 'container'; $form['properties']['#attributes']['style'] = 'display: none'; - // Add tokens. - $form['token_tree_link'] = $form['properties']['token_tree_link']; - // Disable client-side validation. $form['#attributes']['novalidate'] = TRUE; @@ -547,11 +589,15 @@ public function buildDefaultValueForm(array &$form, FormStateInterface $form_sta $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Update default value'), + '#attributes' => ['data-hash' => 'webform-tab--advanced'], '#validate' => ['::validateDefaultValue'], '#submit' => ['::getDefaultValue'], '#button_type' => 'primary', ]; + // Remove 'Save + Add element'. + unset($form['actions']['save_add_element']); + if ($this->isAjax()) { $form['actions']['submit']['#ajax'] = [ 'callback' => '::submitAjaxForm', @@ -561,18 +607,17 @@ public function buildDefaultValueForm(array &$form, FormStateInterface $form_sta } else { // Add 'Set default value' button. - $form['properties']['default']['set_default_value'] = [ + $form['properties']['default']['actions'] = ['#type' => 'container']; + $form['properties']['default']['actions']['set_default_value'] = [ '#type' => 'submit', '#value' => $this->t('Set default value'), '#submit' => ['::setDefaultValue'], - // Disable client-site validation to allow tokens to be posted back to - // server. - '#attributes' => ['class' => ['js-webform-novalidate']], + '#attributes' => ['formnovalidate' => 'formnovalidate'], '#_validate_form' => TRUE, ]; if ($this->isAjax()) { - $form['properties']['default']['set_default_value']['#ajax'] = [ + $form['properties']['default']['actions']['set_default_value']['#ajax'] = [ 'callback' => '::submitAjaxForm', 'event' => 'click', ]; @@ -601,7 +646,7 @@ public function getDefaultValue(array &$form, FormStateInterface $form_state) { $element_plugin = $this->getWebformElementPlugin(); if (is_array($default_value)) { if ($element_plugin->isComposite()) { - $default_value = WebformYaml::tidy(Yaml::encode($default_value)); + $default_value = WebformYaml::encode($default_value); } else { $default_value = implode(', ', $default_value); diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTestForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTestForm.php index 25fe75825d..321f4aef67 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTestForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTestForm.php @@ -20,6 +20,11 @@ */ class WebformUiElementTestForm extends WebformUiElementFormBase { + /** + * {@inheritdoc} + */ + protected $operation = 'test'; + /** * Type of webform element being tested. * @@ -155,7 +160,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn ]; } - // Clear all messages including 'Unable to display this webform...' which is + // Clear all messages including 'Unable to display this webform…' which is // generated because we are using a temp webform. // drupal_get_messages(); return $form; @@ -166,7 +171,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn */ public function reset(array &$form, FormStateInterface $form_state) { \Drupal::request()->getSession()->remove('webform_ui_test_element_' . $this->type); - drupal_set_message($this->t('Webform element %type test has been reset.', ['%type' => $this->type])); + $this->messenger()->addStatus($this->t('Webform element %type test has been reset.', ['%type' => $this->type])); } /** @@ -192,7 +197,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { \Drupal::request()->getSession()->set('webform_ui_test_element_' . $this->type, $properties); - drupal_set_message($this->t('Webform element %type test has been updated.', ['%type' => $this->type])); + $this->messenger()->addStatus($this->t('Webform element %type test has been updated.', ['%type' => $this->type])); } /** diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php index 14163e95bb..6c49f62795 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php @@ -35,7 +35,6 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn } $elements = $this->elementManager->getInstances(); - $definitions = $this->getDefinitions(); $form = parent::buildForm($form, $form_state, $webform); @@ -51,21 +50,20 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $form['actions']['cancel'] = [ '#type' => 'link', '#title' => $this->t('Cancel'), - '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button']), + '#attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(WebformDialogHelper::DIALOG_NORMAL, ['button']), '#url' => Url::fromRoute('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key]), ]; foreach ($related_types as $element_type => $element_type_label) { /** @var \Drupal\webform\Plugin\WebformElementInterface $webform_element */ $webform_element = $elements[$element_type]; - $plugin_definition = $definitions[$element_type]; $url = Url::fromRoute( 'entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key], ['query' => ['type' => $element_type]] ); - $form['elements'][$element_type] = $this->buildRow($plugin_definition, $webform_element, $url, $this->t('Change')); + $form['elements'][$element_type] = $this->buildRow($webform_element, $url, $this->t('Change')); } return $form; diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php index 056c602980..23647f0d01 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php @@ -98,6 +98,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $form['#prefix'] = '<div id="webform-ui-element-type-ajax-wrapper">'; $form['#suffix'] = '</div>'; + $form['#attached']['library'][] = 'webform/webform.admin'; $form['#attached']['library'][] = 'webform/webform.form'; $form['#attached']['library'][] = 'webform/webform.tooltip'; $form['#attached']['library'][] = 'webform_ui/webform_ui'; @@ -131,16 +132,28 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn '#attributes' => [ 'class' => ['webform-form-filter-text'], 'data-element' => '.webform-ui-element-type-table', + 'data-item-singlular' => $this->t('element'), + 'data-item-plural' => $this->t('elements'), + 'data-no-results' => '.webform-element-no-results', 'title' => $this->t('Enter a part of the element name to filter by.'), 'autofocus' => 'autofocus', ], ]; + // No results. + $form['no_results'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('No elements found. Try a different search.'), + '#message_type' => 'info', + '#attributes' => ['class' => ['webform-element-no-results']], + '#weight' => 1000, + ]; + return $form; } /** - * Never trigge validation. + * Never trigger validation. */ public function noValidate(array &$form, FormStateInterface $form_state) { $form_state->clearErrors(); @@ -170,8 +183,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * to a URL */ public function submitAjaxForm(array &$form, FormStateInterface $form_state) { - // Remove wrapper. - unset($form['#prefix'], $form['#suffix']); + // Remove #id from wrapper so that the form is still wrapped in a <div> + // and triggerable. + // @see js/webform.element.details.toggle.js + $form['#prefix'] = '<div>'; $response = new AjaxResponse(); $response->addCommand(new HtmlCommand('#webform-ui-element-type-ajax-wrapper', $form)); @@ -209,8 +224,6 @@ protected function getHeader() { /** * Build element type row. * - * @param array $plugin_definition - * Webform element plugin definition. * @param \Drupal\webform\Plugin\WebformElementInterface $webform_element * Webform element plugin. * @param \Drupal\Core\Url $url @@ -221,13 +234,13 @@ protected function getHeader() { * @return array * A renderable array containing the element type row. */ - protected function buildRow(array $plugin_definition, WebformElementInterface $webform_element, Url $url, $label) { + protected function buildRow(WebformElementInterface $webform_element, Url $url, $label) { $row = []; // Type. $row['type']['link'] = [ '#type' => 'link', - '#title' => $plugin_definition['label'], + '#title' => $webform_element->getPluginLabel(), '#url' => $url, '#attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), '#prefix' => '<span class="webform-form-filter-text-source">', @@ -235,7 +248,8 @@ protected function buildRow(array $plugin_definition, WebformElementInterface $w ]; $row['type']['help'] = [ '#type' => 'webform_help', - '#help' => $plugin_definition['description'], + '#help' => $webform_element->getPluginDescription(), + '#help_title' => $webform_element->getPluginLabel(), ]; // Preview. @@ -265,7 +279,7 @@ protected function buildRow(array $plugin_definition, WebformElementInterface $w } $row['type']['#attributes']['class'][] = 'js-webform-tooltip-link'; $row['type']['#attributes']['class'][] = 'webform-tooltip-link'; - $row['type']['#attributes']['title'] = $plugin_definition['description']; + $row['type']['#attributes']['title'] = $webform_element->getPluginDescription(); } return $row; @@ -363,8 +377,10 @@ protected function buildElementPreview(WebformElementInterface $webform_element) // Custom element type specific attributes. switch ($webform_element->getTypeName()) { + case 'details': case 'fieldset': case 'webform_email_confirm': + // Title needs to be displayed. unset($element['#title_display']); break; @@ -416,7 +432,7 @@ protected function buildElementPreview(WebformElementInterface $webform_element) ]; break; - case 'webform_location': + case 'webform_location_geocomplete': unset($element['#map'], $element['#geolocation']); break; @@ -479,11 +495,7 @@ protected function getDefinitions() { foreach ($grouped_definitions as $grouped_definition) { $sorted_definitions += $grouped_definition; } - foreach ($sorted_definitions as &$plugin_definition) { - if (empty($plugin_definition['category'])) { - $plugin_definition['category'] = $this->t('Other elements'); - } - } + return $sorted_definitions; } diff --git a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php index 87b523a49d..baadbbb658 100644 --- a/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php +++ b/web/modules/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php @@ -24,6 +24,12 @@ public function getFormId() { public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL) { $parent = $this->getRequest()->query->get('parent'); + if ($parent) { + $parent_element = $webform->getElement($parent); + $t_args = ['@parent' => $parent_element['#admin_title'] ?: $parent_element['#title'] ?: $parent_element['#webform_key']]; + $form['#title'] = $this->t('Select an element to add to "@parent"', $t_args); + } + $elements = $this->elementManager->getInstances(); $definitions = $this->getDefinitions(); $category_index = 0; @@ -31,29 +37,27 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $form = parent::buildForm($form, $form_state, $webform); - foreach ($definitions as $plugin_id => $plugin_definition) { - $element_type = $plugin_id; - + foreach (array_keys($definitions) as $element_type) { /** @var \Drupal\webform\Plugin\WebformElementInterface $webform_element */ $webform_element = $elements[$element_type]; - // Skip hidden plugins. - if ($webform_element->isHidden()) { + // Skip disabled or hidden. + if ($webform_element->isDisabled() || $webform_element->isHidden()) { continue; } // Skip wizard page which has a dedicated URL. - if ($element_type == 'webform_wizard_page') { + if ($element_type === 'webform_wizard_page') { continue; } - $category_name = (string) $plugin_definition['category']; + $category_name = (string) $webform_element->getPluginCategory(); if (!isset($categories[$category_name])) { $categories[$category_name] = $category_index++; $category_id = $categories[$category_name]; $form[$category_id] = [ '#type' => 'details', - '#title' => $plugin_definition['category'], + '#title' => $webform_element->getPluginCategory(), '#open' => TRUE, '#attributes' => ['data-webform-element-id' => 'webform-ui-element-type-' . $category_id], ]; @@ -76,7 +80,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn ['webform' => $webform->id(), 'type' => $element_type], ($parent) ? ['query' => ['parent' => $parent]] : [] ); - $form[$category_id]['elements'][$element_type] = $this->buildRow($plugin_definition, $webform_element, $url, $this->t('Add element')); + $form[$category_id]['elements'][$element_type] = $this->buildRow($webform_element, $url, $this->t('Add element')); } return $form; diff --git a/web/modules/webform/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php b/web/modules/webform/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php index 7df6c74903..51e1254bc5 100644 --- a/web/modules/webform/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php +++ b/web/modules/webform/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php @@ -15,7 +15,7 @@ class WebformUiPathProcessor implements OutboundPathProcessorInterface { * {@inheritdoc} */ public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { - if (strpos($path, '/webform/') === FALSE || !method_exists($request, 'getQueryString')) { + if (strpos($path, '/webform/') === FALSE || !method_exists($request, 'getQueryString')) { return $path; } diff --git a/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php b/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php index 571a202479..73e5886adf 100644 --- a/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php +++ b/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php @@ -29,7 +29,7 @@ class WebformUiElementPropertiesTest extends WebformTestBase { 'example_element_states', 'test_element', 'test_element_access', - 'test_form_states_triggers', + 'test_states_triggers', 'test_example_elements', 'test_example_elements_composite', ]; diff --git a/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php b/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php index 50e98984cd..629bd9ea03 100644 --- a/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php +++ b/web/modules/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php @@ -46,23 +46,23 @@ public function testElements() { $webform_contact = Webform::load('contact'); /**************************************************************************/ - // Multiple + // Multiple. /**************************************************************************/ // Check multiple enabled before submission. - $this->drupalGet('admin/structure/webform/manage/contact/element/name/edit'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/name/edit'); $this->assertRaw('<select data-drupal-selector="edit-properties-multiple-container-cardinality" id="edit-properties-multiple-container-cardinality" name="properties[multiple][container][cardinality]" class="form-select">'); $this->assertNoRaw('<em>There is data for this element in the database. This setting can no longer be changed.</em>'); // Check multiple disabled after submission. $this->postSubmissionTest($webform_contact); - $this->drupalGet('admin/structure/webform/manage/contact/element/name/edit'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/name/edit'); $this->assertNoRaw('<select data-drupal-selector="edit-properties-multiple-container-cardinality" id="edit-properties-multiple-container-cardinality" name="properties[multiple][container][cardinality]" class="form-select">'); $this->assertRaw('<select data-drupal-selector="edit-properties-multiple-container-cardinality" disabled="disabled" id="edit-properties-multiple-container-cardinality" name="properties[multiple][container][cardinality]" class="form-select">'); $this->assertRaw('<em>There is data for this element in the database. This setting can no longer be changed.</em>'); /**************************************************************************/ - // Reordering + // Reordering. /**************************************************************************/ // Check original contact element order. @@ -83,12 +83,55 @@ public function testElements() { $webform_contact = Webform::load('contact'); $this->assertEqual(['message', 'subject', 'email', 'name', 'actions'], array_keys($webform_contact->getElementsDecodedAndFlattened())); + /**************************************************************************/ + // Hierarchy. + /**************************************************************************/ + + // Create a simple test form. + $values = ['id' => 'test']; + $elements = [ + 'details_01' => [ + '#type' => 'details', + '#title' => 'details_01', + 'text_field_01' => [ + '#type' => 'textfield', + '#title' => 'textfield_01', + ], + ], + 'details_02' => [ + '#type' => 'details', + '#title' => 'details_02', + 'text_field_02' => [ + '#type' => 'textfield', + '#title' => 'textfield_02', + ], + ], + ]; + $this->createWebform($values, $elements); + $this->drupalGet('/admin/structure/webform/manage/test'); + + // Check setting container to itself displays an error. + $edit = [ + 'webform_ui_elements[details_01][parent_key]' => 'details_01', + ]; + $this->drupalPostForm('admin/structure/webform/manage/test', $edit, t('Save elements')); + $this->assertRaw('Parent <em class="placeholder">details_01</em> key is not valid.'); + + // Check setting containers to one another displays an error. + $edit = [ + 'webform_ui_elements[details_01][parent_key]' => 'details_02', + 'webform_ui_elements[details_02][parent_key]' => 'details_01', + ]; + $this->drupalPostForm('admin/structure/webform/manage/test', $edit, t('Save elements')); + $this->assertRaw('Parent <em class="placeholder">details_01</em> key is not valid.'); + $this->assertRaw('Parent <em class="placeholder">details_02</em> key is not valid.'); + /**************************************************************************/ // Required. /**************************************************************************/ // Check name is required. - $this->drupalGet('admin/structure/webform/manage/contact'); + $this->drupalGet('/admin/structure/webform/manage/contact'); $this->assertFieldChecked('edit-webform-ui-elements-name-required'); // Check name is not required. @@ -102,6 +145,12 @@ public function testElements() { // CRUD /**************************************************************************/ + // Check that 'Save + Add element' is only visible in dialogs. + $this->drupalGet('/admin/structure/webform/manage/contact/element/add/textfield'); + $this->assertNoRaw('Save + Add element'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/add/textfield', ['query' => ['_wrapper_format' => 'drupal_dialog']]); + $this->assertRaw('Save + Add element'); + // Create element. $this->drupalPostForm('admin/structure/webform/manage/contact/element/add/textfield', ['key' => 'test', 'properties[title]' => 'Test'], t('Save')); @@ -119,7 +168,7 @@ public function testElements() { $this->assertRaw('The machine-readable name is already in use. It must be unique.'); // Check read element. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('<label for="edit-test">Test</label>'); $this->assertRaw('<input data-drupal-selector="edit-test" type="text" id="edit-test" name="test" value="" size="60" maxlength="255" class="form-text" />'); @@ -130,7 +179,7 @@ public function testElements() { $this->assertUrl('admin/structure/webform/manage/contact', ['query' => ['update' => 'test']]); // Check element updated. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('<label for="edit-test">Test 123</label>'); $this->assertRaw('<input data-drupal-selector="edit-test" type="text" id="edit-test" name="test" value="This is a default value" size="60" maxlength="255" class="form-text" />'); @@ -140,13 +189,21 @@ public function testElements() { // Check delete element. $this->drupalPostForm('admin/structure/webform/manage/contact/element/test/delete', [], t('Delete')); - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertNoRaw('<label for="edit-test">Test 123</label>'); $this->assertNoRaw('<input data-drupal-selector="edit-test" type="text" id="edit-test" name="test" value="This is a default value" size="60" maxlength="255" class="form-text" />'); // Check that 'test' element values were deleted from the webform_submission_data table. $this->assertEqual(0, \Drupal::database()->query("SELECT COUNT(sid) FROM {webform_submission_data} WHERE webform_id='contact' AND name='test'")->fetchField()); + // Check access allowed to textfield element. + $this->drupalGet('/admin/structure/webform/manage/contact/element/add/textfield'); + $this->assertResponse(200); + + // Check access denied to password element, which is disabled by default. + $this->drupalGet('/admin/structure/webform/manage/contact/element/add/password'); + $this->assertResponse(403); + /**************************************************************************/ // Change type /**************************************************************************/ @@ -155,14 +212,14 @@ public function testElements() { $this->drupalPostForm('admin/structure/webform/manage/contact/element/add/textfield', ['key' => 'test', 'properties[title]' => 'Test'], t('Save')); // Check element type. - $this->drupalGet('admin/structure/webform/manage/contact/element/test/edit'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/test/edit'); // Check change element type link. - $this->assertRaw('Text field<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/change" class="button button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":800,"dialogClass":"webform-modal"}" data-drupal-selector="edit-change-type" id="edit-change-type">Change</a>'); + $this->assertRaw('Text field<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/change" class="button button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":800,"dialogClass":"webform-ui-dialog"}" data-drupal-selector="edit-change-type" id="edit-change-type">Change</a>'); // Check text field has description. $this->assertRaw(t('A short description of the element used as help for the user when he/she uses the webform.')); // Check change element types. - $this->drupalGet('admin/structure/webform/manage/contact/element/test/change'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/test/change'); $this->assertRaw(t('Hidden')); $this->assertCssSelect('a[href$="admin/structure/webform/manage/contact/element/test/edit?type=hidden"][data-dialog-type][data-dialog-options][data-drupal-selector="edit-elements-hidden-operation"]'); $this->assertRaw(t('value')); @@ -175,24 +232,24 @@ public function testElements() { $this->assertCssSelect('a[href$="admin/structure/webform/manage/contact/element/test/edit?type=url"][data-dialog-type][data-dialog-options][data-drupal-selector="edit-elements-url-operation"]'); // Check change element type. - $this->drupalGet('admin/structure/webform/manage/contact/element/test/edit', ['query' => ['type' => 'value']]); + $this->drupalGet('/admin/structure/webform/manage/contact/element/test/edit', ['query' => ['type' => 'value']]); // Check value has no description. $this->assertNoRaw(t('A short description of the element used as help for the user when he/she uses the webform.')); - $this->assertRaw('Value<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/edit" class="button button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":800,"dialogClass":"webform-modal"}" data-drupal-selector="edit-cancel" id="edit-cancel">Cancel</a>'); + $this->assertRaw('Value<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/edit" class="button button--small webform-ajax-link" data-dialog-type="dialog" data-dialog-renderer="off_canvas" data-dialog-options="{"width":600,"dialogClass":"ui-dialog-off-canvas webform-off-canvas"}" data-drupal-selector="edit-cancel" id="edit-cancel">Cancel</a>'); $this->assertRaw('(Changing from <em class="placeholder">Text field</em>)'); // Change the element type. $this->drupalPostForm('admin/structure/webform/manage/contact/element/test/edit', [], t('Save'), ['query' => ['type' => 'value']]); // Change the element type from 'textfield' to 'value'. - $this->drupalGet('admin/structure/webform/manage/contact/element/test/edit'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/test/edit'); // Check change element type link. - $this->assertRaw('Value<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/change" class="button button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":800,"dialogClass":"webform-modal"}" data-drupal-selector="edit-change-type" id="edit-change-type">Change</a>'); + $this->assertRaw('Value<a href="' . $base_path . 'admin/structure/webform/manage/contact/element/test/change" class="button button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":800,"dialogClass":"webform-ui-dialog"}" data-drupal-selector="edit-change-type" id="edit-change-type">Change</a>'); // Check color element that does not have related type and return 404. $this->drupalPostForm('admin/structure/webform/manage/contact/element/add/color', ['key' => 'test_color', 'properties[title]' => 'Test color'], t('Save')); - $this->drupalGet('admin/structure/webform/manage/contact/element/test_color/change'); + $this->drupalGet('/admin/structure/webform/manage/contact/element/test_color/change'); $this->assertResponse(404); /**************************************************************************/ @@ -217,7 +274,7 @@ public function testPermissions() { // permission. $account = $this->drupalCreateUser(['administer webform']); $this->drupalLogin($account); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/source'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/source'); $this->assertResponse(403); $this->drupalLogout(); @@ -225,7 +282,7 @@ public function testPermissions() { // without 'administer webform' permission. $account = $this->drupalCreateUser(['edit webform source']); $this->drupalLogin($account); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/source'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/source'); $this->assertResponse(403); $this->drupalLogout(); @@ -233,7 +290,7 @@ public function testPermissions() { // and 'administer webform' permission. $account = $this->drupalCreateUser(['administer webform', 'edit webform source']); $this->drupalLogin($account); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/source'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/source'); $this->assertResponse(200); $this->drupalLogout(); } diff --git a/web/modules/webform/modules/webform_ui/src/WebformUiEntityElementsForm.php b/web/modules/webform/modules/webform_ui/src/WebformUiEntityElementsForm.php index 3e921a2eef..f134419223 100644 --- a/web/modules/webform/modules/webform_ui/src/WebformUiEntityElementsForm.php +++ b/web/modules/webform/modules/webform_ui/src/WebformUiEntityElementsForm.php @@ -25,6 +25,18 @@ class WebformUiEntityElementsForm extends BundleEntityFormBase { use WebformEntityAjaxFormTrait; + /** + * Array of required states. + * + * @var array + */ + protected $requiredStates = [ + 'required' => 'required', + '!required' => '!required', + 'optional' => 'optional', + '!optional' => '!optional', + ]; + /** * The renderer. * @@ -100,74 +112,41 @@ public function buildForm(array $form, FormStateInterface $form_state) { $header = $this->getTableHeader(); - // Build table rows for elements. - $rows = []; $elements = $this->getOrderableElements(); + + // Get (weight) delta parent options. $delta = count($elements); + $parent_options = $this->getParentOptions($elements); + + // Build table rows for elements. + $rows = []; foreach ($elements as $element) { - $rows[$element['#webform_key']] = $this->getElementRow($element, $delta); - } - - // Must manually add local actions to the webform because we can't alter local - // actions and add the needed dialog attributes. - // @see https://www.drupal.org/node/2585169 - $local_actions = []; - $local_actions['add_element'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add element'), - 'url' => new Url('entity.webform_ui.element', ['webform' => $webform->id()]), - 'attributes' => WebformDialogHelper::getModalDialogAttributes(), - ], - ]; - if ($this->elementManager->createInstance('webform_wizard_page')->isEnabled()) { - $local_actions['add_page'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add page'), - 'url' => new Url('entity.webform_ui.element.add_form', ['webform' => $webform->id(), 'type' => 'webform_wizard_page']), - 'attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), - ], - ]; + $rows[$element['#webform_key']] = $this->getElementRow($element, $delta, $parent_options); } - if ($webform->hasFlexboxLayout()) { - $local_actions['add_layout'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add layout'), - 'url' => new Url('entity.webform_ui.element.add_form', ['webform' => $webform->id(), 'type' => 'webform_flexbox']), - 'attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), - ], - ]; - } - $form['local_actions'] = [ - '#prefix' => '<ul class="action-links">', - '#suffix' => '</ul>', - ] + $local_actions; $form['webform_ui_elements'] = [ - '#type' => 'table', - '#header' => $header, - '#empty' => $this->t('Please add elements to this webform.'), - '#attributes' => [ - 'class' => ['webform-ui-elements-table'], - ], - '#tabledrag' => [ - [ - 'action' => 'match', - 'relationship' => 'parent', - 'group' => 'row-parent-key', - 'source' => 'row-key', - 'hidden' => TRUE, /* hides the WEIGHT & PARENT tree columns below */ - 'limit' => FALSE, + '#type' => 'table', + '#header' => $header, + '#empty' => $this->t('Please add elements to this webform.'), + '#attributes' => [ + 'class' => ['webform-ui-elements-table'], ], - [ - 'action' => 'order', - 'relationship' => 'sibling', - 'group' => 'row-weight', + '#tabledrag' => [ + [ + 'action' => 'match', + 'relationship' => 'parent', + 'group' => 'row-parent-key', + 'source' => 'row-key', + 'hidden' => TRUE, /* hides the WEIGHT & PARENT tree columns below */ + 'limit' => FALSE, + ], + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'row-weight', + ], ], - ], - ] + $rows; + ] + $rows; if ($rows && !$webform->hasActions()) { $form['webform_ui_elements'] += ['webform_actions_default' => $this->getCustomizeActionsRow()]; @@ -175,6 +154,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Must preload libraries required by (modal) dialogs. WebformDialogHelper::attachLibraries($form); + $form['#attached']['library'][] = 'webform/webform.admin.tabledrag'; $form['#attached']['library'][] = 'webform_ui/webform_ui'; $form = parent::buildForm($form, $form_state); @@ -206,16 +186,37 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // Validate parent key and add children to ordered elements. foreach ($webform_ui_elements as $key => $table_element) { - $parent_key = $table_element['parent_key']; - // Validate the parent key. - if ($parent_key && !isset($elements_flattened[$parent_key])) { - $form_state->setError($form['webform_ui_elements'], $this->t('Parent %parent_key does not exist.', ['%parent_key' => $parent_key])); - return; + // Validate parent key. + if ($parent_key = $table_element['parent_key']) { + // Validate missing parent key. + if (!isset($elements_flattened[$parent_key])) { + $form_state->setError($form['webform_ui_elements'][$key]['parent']['parent_key'], $this->t('Parent %parent_key does not exist.', ['%parent_key' => $parent_key])); + continue; + } + + // Validate the parent keys and make sure there + // are no recursive parents. + $parent_keys = [$key]; + $current_parent_key = $parent_key; + while ($current_parent_key) { + if (in_array($current_parent_key, $parent_keys)) { + $form_state->setError($form['webform_ui_elements'][$key]['parent']['parent_key'], $this->t('Parent %parent_key key is not valid.', ['%parent_key' => $parent_key])); + break; + } + + $parent_keys[] = $current_parent_key; + $current_parent_key = (isset($webform_ui_elements[$current_parent_key]['parent_key'])) ? $webform_ui_elements[$current_parent_key]['parent_key'] : NULL; + } } // Set #required or remove the property. - if (isset($webform_ui_elements[$key]['required'])) { + $is_conditionally_required = isset($elements_flattened[$key]['#states']) && array_intersect_key($this->requiredStates, $elements_flattened[$key]['#states']); + if ($is_conditionally_required) { + // Always unset conditionally required elements. + unset($elements_flattened[$key]['#required']); + } + elseif (isset($webform_ui_elements[$key]['required'])) { if (empty($webform_ui_elements[$key]['required'])) { unset($elements_flattened[$key]['#required']); } @@ -228,6 +229,10 @@ public function validateForm(array &$form, FormStateInterface $form_state) { $webform_ui_elements[$parent_key]['children'][$key] = $key; } + if ($form_state->hasAnyErrors()) { + return; + } + // Rebuild elements to reflect new hierarchy. $elements_updated = []; // Preserve the original elements root properties. @@ -242,6 +247,21 @@ public function validateForm(array &$form, FormStateInterface $form_state) { // Update the webform's elements. $webform->setElements($elements_updated); + + // Validate only elements required, hierarchy, and rendering. + $validate_options = [ + 'required' => TRUE, + 'yaml' => FALSE, + 'array' => FALSE, + 'names' => FALSE, + 'properties' => FALSE, + 'submissions' => FALSE, + 'hierarchy' => TRUE, + 'rendering' => TRUE, + ]; + if ($this->elementsValidator->validate($webform, $validate_options)) { + $form_state->setErrorByName(NULL, $this->t('There has been error validating the elements.')); + } } /** @@ -259,7 +279,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $t_args = ['%label' => $webform->label()]; $this->logger('webform')->notice('Webform @label elements saved.', $context); - drupal_set_message($this->t('Webform %label elements saved.', $t_args)); + $this->messenger()->addStatus($this->t('Webform %label elements saved.', $t_args)); } /** @@ -382,8 +402,14 @@ protected function getTableHeader() { 'data' => $this->t('Required'), 'class' => ['webform-ui-element-required', RESPONSIVE_PRIORITY_LOW], ]; - $header['weight'] = $this->t('Weight'); - $header['parent'] = $this->t('Parent'); + $header['weight'] = [ + 'data' => $this->t('Weight'), + 'class' => ['webform-tabledrag-hide'], + ]; + $header['parent'] = [ + 'data' => $this->t('Parent'), + 'class' => ['webform-tabledrag-hide'], + ]; $header['operations'] = [ 'data' => $this->t('Operations'), 'class' => ['webform-ui-element-operations'], @@ -391,18 +417,41 @@ protected function getTableHeader() { return $header; } + /** + * Get parent (container) elements as options. + * + * @param array $elements + * A flattened array of elements. + * + * @return array + * Parent (container) elements as options. + */ + protected function getParentOptions(array $elements) { + $options = []; + foreach ($elements as $key => $element) { + $plugin_id = $this->elementManager->getElementPluginId($element); + $webform_element = $this->elementManager->createInstance($plugin_id); + if ($webform_element->isContainer($element)) { + $options[$key] = $element['#admin_title'] ?: $element['#title']; + } + } + return $options; + } + /** * Gets an row for a single element. * * @param array $element * Webform element. * @param int $delta - * The number of elements. @todo is this correct? + * The number of elements. + * @param array $parent_options + * An associative array of parent (container) options. * * @return array * The row for the element. */ - protected function getElementRow(array $element, $delta) { + protected function getElementRow(array $element, $delta, array $parent_options) { /** @var \Drupal\webform\WebformInterface $webform */ $webform = $this->getEntity(); @@ -411,7 +460,8 @@ protected function getElementRow(array $element, $delta) { $element_state_options = OptGroup::flattenOptions(WebformElementStates::getStateOptions()); $element_dialog_attributes = WebformDialogHelper::getOffCanvasDialogAttributes(); $key = $element['#webform_key']; - + $title = $element['#admin_title'] ?: $element['#title']; + $title = (is_array($title)) ? $this->renderer->render($title) : $title; $plugin_id = $this->elementManager->getElementPluginId($element); /** @var \Drupal\webform\Plugin\WebformElementInterface $webform_element */ @@ -499,10 +549,12 @@ protected function getElementRow(array $element, $delta) { ]; } + $is_conditionally_required = FALSE; if ($webform->hasConditions()) { $states = []; if (!empty($element['#states'])) { $states = array_intersect_key($element_state_options, $element['#states']); + $is_conditionally_required = array_intersect_key($this->requiredStates, $element['#states']); } $row['conditional'] = [ '#type' => 'link', @@ -515,6 +567,8 @@ protected function getElementRow(array $element, $delta) { // Add custom hash to current page's location. // @see Drupal.behaviors.webformAjaxLink 'data-hash' => 'webform-tab--conditions', + 'title' => $this->t('Edit @states conditional', ['@states' => implode('; ', $states)]), + 'aria-label' => $this->t('Edit @states conditional', ['@states' => implode('; ', $states)]), ], ]; } @@ -522,8 +576,14 @@ protected function getElementRow(array $element, $delta) { if ($webform_element->hasProperty('required')) { $row['required'] = [ '#type' => 'checkbox', + '#title' => $this->t('Required for @title', ['@title' => $title]), + '#title_display' => 'invisible', '#default_value' => (empty($element['#required'])) ? FALSE : TRUE, ]; + if ($is_conditionally_required) { + $row['required']['#default_value'] = TRUE; + $row['required']['#disabled'] = TRUE; + } } else { $row['required'] = ['#markup' => '']; @@ -531,15 +591,19 @@ protected function getElementRow(array $element, $delta) { $row['weight'] = [ '#type' => 'weight', - '#title' => $this->t('Weight for ID @id', ['@id' => $key]), + '#title' => $this->t('Weight for @title', ['@title' => $title]), '#title_display' => 'invisible', '#default_value' => $element['#weight'], + '#wrapper_attributes' => ['class' => ['webform-tabledrag-hide']], '#attributes' => [ 'class' => ['row-weight'], ], '#delta' => $delta, ]; + $row['parent'] = [ + '#wrapper_attributes' => ['class' => ['webform-tabledrag-hide']], + ]; $row['parent']['key'] = [ '#parents' => ['webform_ui_elements', $key, 'key'], '#type' => 'hidden', @@ -548,18 +612,30 @@ protected function getElementRow(array $element, $delta) { 'class' => ['row-key'], ], ]; - $row['parent']['parent_key'] = [ - '#parents' => ['webform_ui_elements', $key, 'parent_key'], - '#type' => 'textfield', - '#size' => 20, - '#title' => $this->t('Parent'), - '#title_display' => 'invisible', - '#default_value' => $element['#webform_parent_key'], - '#attributes' => [ - 'class' => ['row-parent-key'], - 'readonly' => 'readonly', - ], - ]; + if ($parent_options) { + $row['parent']['parent_key'] = [ + '#parents' => ['webform_ui_elements', $key, 'parent_key'], + '#type' => 'select', + '#options' => $parent_options, + '#empty_option' => '', + '#title' => $this->t('Parent element @title', ['@title' => $title]), + '#title_display' => 'invisible', + '#default_value' => $element['#webform_parent_key'], + '#attributes' => [ + 'class' => ['row-parent-key'], + ], + ]; + } + else { + $row['parent']['parent_key'] = [ + '#parents' => ['webform_ui_elements', $key, 'parent_key'], + '#type' => 'hidden', + '#default_value' => '', + '#attributes' => [ + 'class' => ['row-parent-key'], + ], + ]; + } $row['operations'] = [ '#type' => 'operations', @@ -643,8 +719,8 @@ protected function getCustomizeActionsRow() { $row['conditions'] = ['#markup' => '']; } $row['required'] = ['#markup' => '']; - $row['weight'] = ['#markup' => '']; - $row['parent'] = ['#markup' => '']; + $row['weight'] = ['#markup' => '', '#wrapper_attributes' => ['class' => ['webform-tabledrag-hide']]]; + $row['parent'] = ['#markup' => '', '#wrapper_attributes' => ['class' => ['webform-tabledrag-hide']]]; $row['operations'] = [ '#type' => 'operations', '#prefix' => '<div class="webform-dropbutton">', diff --git a/web/modules/webform/modules/webform_ui/src/WebformUiOptionsForm.php b/web/modules/webform/modules/webform_ui/src/WebformUiOptionsForm.php index 125dd4e662..4dba09de07 100644 --- a/web/modules/webform/modules/webform_ui/src/WebformUiOptionsForm.php +++ b/web/modules/webform/modules/webform_ui/src/WebformUiOptionsForm.php @@ -23,7 +23,7 @@ public function editForm(array $form, FormStateInterface $form_state) { '#description' => $this->t("Descriptions, which are only applicable to radios and checkboxes, can be delimited using ' -- '."), '#description_display' => 'before', '#empty_options' => 10, - '#add_more' => 10, + '#add_more_items' => 10, '#default_value' => $this->getOptions(), ]; return $form; diff --git a/web/modules/webform/modules/webform_ui/webform_ui.info.yml b/web/modules/webform/modules/webform_ui/webform_ui.info.yml index c2eb16a115..6ecc37c448 100644 --- a/web/modules/webform/modules/webform_ui/webform_ui.info.yml +++ b/web/modules/webform/modules/webform_ui/webform_ui.info.yml @@ -6,8 +6,8 @@ package: Webform dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/modules/webform_ui/webform_ui.libraries.yml b/web/modules/webform/modules/webform_ui/webform_ui.libraries.yml index cb2e05cd71..fc1abb8a13 100644 --- a/web/modules/webform/modules/webform_ui/webform_ui.libraries.yml +++ b/web/modules/webform/modules/webform_ui/webform_ui.libraries.yml @@ -4,4 +4,4 @@ webform_ui: theme: css/webform_ui.module.css: {} js: - js/webform_ui.js: {} + js/webform_ui.js: {} diff --git a/web/modules/webform/modules/webform_ui/webform_ui.links.action.yml b/web/modules/webform/modules/webform_ui/webform_ui.links.action.yml new file mode 100644 index 0000000000..99b05d9de2 --- /dev/null +++ b/web/modules/webform/modules/webform_ui/webform_ui.links.action.yml @@ -0,0 +1,29 @@ +entity.webform_ui.element: + route_name: entity.webform_ui.element + title: 'Add element' + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + dialog: normal + attributes: + id: 'webform-ui-add-element' + appears_on: + - entity.webform.edit_form + +entity.webform_ui.element.page: + route_name: entity.webform_ui.element.add_page + title: 'Add page' + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + off_canvas: normal + attributes: + id: 'webform-ui-add-page' + appears_on: + - entity.webform.edit_form + +entity.webform_ui.element.layout: + route_name: entity.webform_ui.element.add_layout + title: 'Add layout' + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + off_canvas: normal + attributes: + id: 'webform-ui-add-layout' + appears_on: + - entity.webform.edit_form diff --git a/web/modules/webform/modules/webform_ui/webform_ui.module b/web/modules/webform/modules/webform_ui/webform_ui.module index ffe2d631ac..bda58fa26d 100644 --- a/web/modules/webform/modules/webform_ui/webform_ui.module +++ b/web/modules/webform/modules/webform_ui/webform_ui.module @@ -5,9 +5,6 @@ * Provides a simple user interface for building and maintaining webforms. */ -use Drupal\Core\Form\FormStateInterface; -use Drupal\webform\Utility\WebformDialogHelper; - /** * Implements hook_entity_type_alter(). */ @@ -43,16 +40,20 @@ function webform_ui_entity_type_alter(array &$entity_types) { } /** - * Implements hook_webform_submission_form_alter(). + * Implements hook_preprocess_menu_local_action(). + * + * Add .button--secondary to add page and layout actions. + * + * @see Drupal.behaviors.webformUiElementsActionsSecondary */ -function webform_ui_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) { - // Attach dialog libraries to editable webforms to make sure that the - // quickedit dialog/system tray works as expected. - if (\Drupal::moduleHandler()->moduleExists('quickedit')) { - /** @var \Drupal\webform\WebformSubmissionForm $form_object */ - $form_object = $form_state->getFormObject(); - if ($form_object->getEntity()->access('update')) { - WebformDialogHelper::attachLibraries($form); - } +function webform_ui_preprocess_menu_local_action(&$variables) { + if (\Drupal::routeMatch()->getRouteName() != 'entity.webform.edit_form') { + return; + } + + if (!in_array($variables['link']['#url']->getRouteName(), ['entity.webform_ui.element.add_page', 'entity.webform_ui.element.add_layout'])) { + return; } + + $variables['link']['#options']['attributes']['class'][] = 'button--secondary'; } diff --git a/web/modules/webform/modules/webform_ui/webform_ui.permissions.yml b/web/modules/webform/modules/webform_ui/webform_ui.permissions.yml deleted file mode 100644 index 739141a465..0000000000 --- a/web/modules/webform/modules/webform_ui/webform_ui.permissions.yml +++ /dev/null @@ -1,4 +0,0 @@ -'edit webform source': - title: 'Edit webform source code' - description: 'Editing webform source code allows users alter and possibly break a webform''s render array.' - restrict access: true diff --git a/web/modules/webform/modules/webform_ui/webform_ui.routing.yml b/web/modules/webform/modules/webform_ui/webform_ui.routing.yml index 872a46f948..6926709ce3 100644 --- a/web/modules/webform/modules/webform_ui/webform_ui.routing.yml +++ b/web/modules/webform/modules/webform_ui/webform_ui.routing.yml @@ -41,7 +41,25 @@ entity.webform_ui.element.add_form: _form: '\Drupal\webform_ui\Form\WebformUiElementAddForm' _title: 'Add element' requirements: - _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformElementAccess' + +entity.webform_ui.element.add_page: + path: '/admin/structure/webform/manage/{webform}/element/add/page' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementAddForm' + _title: 'Add page' + type: webform_wizard_page + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformElementAccess' + +entity.webform_ui.element.add_layout: + path: '/admin/structure/webform/manage/{webform}/element/add/layout' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementAddForm' + _title: 'Add layout' + type: webform_flexbox + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformElementAccess' entity.webform_ui.element.edit_form: path: '/admin/structure/webform/manage/{webform}/element/{key}/edit' @@ -67,8 +85,8 @@ entity.webform_ui.element.delete_form: requirements: _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' -webform.element_plugins.test: - path: '/admin/structure/webform/config/elements/{type}/test' +webform.reports_plugins.elements.test: + path: '/admin/reports/webform-plugins/elements/{type}/test' defaults: _form: '\Drupal\webform_ui\Form\WebformUiElementTestForm' _title: 'Test element' diff --git a/web/modules/webform/reports/accessiblity/text/example_accessibility_advanced.txt b/web/modules/webform/reports/accessiblity/text/example_accessibility_advanced.txt new file mode 100644 index 0000000000..7678b6d50c --- /dev/null +++ b/web/modules/webform/reports/accessiblity/text/example_accessibility_advanced.txt @@ -0,0 +1,89 @@ + +Welcome to Pa11y + + > Running Pa11y on URL http://localhost/wf/webform/example_accessibility_advanced + +Results for URL: http://localhost/wf/webform/example_accessibility_advanced + + • Error: This searchinput element does not have a name available to an accessibility API. Valid names are: label element, title undefined, aria-label undefined, aria-labelledby undefined. + ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.InputSearch.Name + ├── #edit-option-elements > div > div:nth-child(2) > span > span:nth-child(1) > span > ul > li > input + └── <input class="select2-search__field" type="search" tabindex="0" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" aria-autocomplete="list" placeholder="" style="width: 0.75em;"> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-likert-table-q1-likert-question > label + └── <label>Please answer question 1?</label> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-likert-table-q2-likert-question > label + └── <label>How about now answering questio...</label> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-likert-table-q3-likert-question > label + └── <label>Finally, here is question 3?</label> + + • Error: Duplicate id attribute value "iti-item-gb" found on the web page. + ├── WCAG2AA.Principle4.Guideline4_1.4_1_1.F77 + ├── #iti-item-gb + └── <li class="country standard" id="iti-item-gb" role="option" data-dial-code="44" data-country-code="gb"><div class="flag-box"><div clas...</li> + + • Error: Duplicate id attribute value "iti-item-us" found on the web page. + ├── WCAG2AA.Principle4.Guideline4_1.4_1_1.F77 + ├── #iti-item-us + └── <li class="country standard" id="iti-item-us" role="option" data-dial-code="1" data-country-code="us"><div class="flag-box"><div clas...</li> + + • Error: This link points to a named anchor "terms" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-widget-elements > div > div:nth-child(4) > label > a + └── <a role="button" href="#terms">terms of service</a> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-tableselect > tbody > tr:nth-child(1) > td:nth-child(2) + └── <td>One</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-tableselect > tbody > tr:nth-child(2) > td:nth-child(2) + └── <td>Two</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-tableselect > tbody > tr:nth-child(3) > td:nth-child(2) + └── <td>Three</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-table-sort > tbody > tr:nth-child(1) > td:nth-child(1) + └── <td><a href="#" class="tabledrag-ha...</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-table-sort > tbody > tr:nth-child(2) > td:nth-child(1) + └── <td><a href="#" class="tabledrag-ha...</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-table-sort > tbody > tr:nth-child(3) > td:nth-child(1) + └── <td><a href="#" class="tabledrag-ha...</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-tableselect-sort > tbody > tr:nth-child(1) > td:nth-child(2) + └── <td>One</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-tableselect-sort > tbody > tr:nth-child(2) > td:nth-child(2) + └── <td>Two</td> + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 1.87:1. Recommendation: change text colour to #757575. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #edit-webform-tableselect-sort > tbody > tr:nth-child(3) > td:nth-child(2) + └── <td>Three</td> + +16 Errors + diff --git a/web/modules/webform/reports/accessiblity/text/example_accessibility_basic.txt b/web/modules/webform/reports/accessiblity/text/example_accessibility_basic.txt new file mode 100644 index 0000000000..587b594f2f --- /dev/null +++ b/web/modules/webform/reports/accessiblity/text/example_accessibility_basic.txt @@ -0,0 +1,19 @@ + +Welcome to Pa11y + + > Running Pa11y on URL http://localhost/wf/webform/example_accessibility_basic + +Results for URL: http://localhost/wf/webform/example_accessibility_basic + + • Error: Duplicate id attribute value "edit-managed-file-upload" found on the web page. + ├── WCAG2AA.Principle4.Guideline4_1.4_1_1.F77 + ├── #edit-managed-file-upload + └── <input data-drupal-selector="edit-managed-file-upload" type="file" id="edit-managed-file-upload" name="files[managed_file]" size="22" class="js-form-file form-file"> + + • Error: Duplicate id attribute value "edit-managed-file-multiple-upload" found on the web page. + ├── WCAG2AA.Principle4.Guideline4_1.4_1_1.F77 + ├── #edit-managed-file-multiple-upload + └── <input data-drupal-selector="edit-managed-file-multiple-upload" multiple="multiple" type="file" id="edit-managed-file-multiple-upload" name="files[managed_file_multiple][]" size="22" class="js-form-file form-file"> + +2 Errors + diff --git a/web/modules/webform/reports/accessiblity/text/example_accessibility_containers.txt b/web/modules/webform/reports/accessiblity/text/example_accessibility_containers.txt new file mode 100644 index 0000000000..0a84c6b781 --- /dev/null +++ b/web/modules/webform/reports/accessiblity/text/example_accessibility_containers.txt @@ -0,0 +1,24 @@ + +Welcome to Pa11y + + > Running Pa11y on URL http://localhost/wf/webform/example_accessibility_containers + +Results for URL: http://localhost/wf/webform/example_accessibility_containers + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-form-element--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-form-element--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-fieldset--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-fieldset--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-details--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-details--more--content">More</a> + +3 Errors + diff --git a/web/modules/webform/reports/accessiblity/text/example_accessibility_labels.txt b/web/modules/webform/reports/accessiblity/text/example_accessibility_labels.txt new file mode 100644 index 0000000000..ee8d3124fe --- /dev/null +++ b/web/modules/webform/reports/accessiblity/text/example_accessibility_labels.txt @@ -0,0 +1,44 @@ + +Welcome to Pa11y + + > Running Pa11y on URL http://localhost/wf/webform/example_accessibility_labels + +Results for URL: http://localhost/wf/webform/example_accessibility_labels + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-form-element--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-form-element--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-datelist-element--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-datelist-element--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-more--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-more--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-fieldset--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-fieldset--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-fieldset-more--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-fieldset-more--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-details--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-details--more--content">More</a> + + • Error: This link points to a named anchor "more" within the document, but no anchor exists with that name. + ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.G1,G123,G124.NoSuchID + ├── #edit-details-more--more > div:nth-child(1) > a + └── <a role="button" href="#more" aria-expanded="false" aria-controls="edit-details-more--more--content">More</a> + +7 Errors + diff --git a/web/modules/webform/reports/accessiblity/text/example_accessibility_wizard.txt b/web/modules/webform/reports/accessiblity/text/example_accessibility_wizard.txt new file mode 100644 index 0000000000..87a2e95ae6 --- /dev/null +++ b/web/modules/webform/reports/accessiblity/text/example_accessibility_wizard.txt @@ -0,0 +1,14 @@ + +Welcome to Pa11y + + > Running Pa11y on URL http://localhost/wf/webform/example_accessibility_wizard + +Results for URL: http://localhost/wf/webform/example_accessibility_wizard + + • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.12:1. Recommendation: change background to #0378d5. + ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail + ├── #webform-submission-example-accessibility-wizard-add-form > div:nth-child(7) > ul > li:nth-child(1) > span:nth-child(1) + └── <span class="progress-marker">1</span> + +1 Errors + diff --git a/web/modules/webform/src/Access/WebformAccessResult.php b/web/modules/webform/src/Access/WebformAccessResult.php new file mode 100644 index 0000000000..aaa2cfbed7 --- /dev/null +++ b/web/modules/webform/src/Access/WebformAccessResult.php @@ -0,0 +1,107 @@ +<?php + +namespace Drupal\webform\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityInterface; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Value object indicating an allowed access result, with cacheability metadata. + */ +class WebformAccessResult { + + /** + * Creates an allowed or neutral access result. + * + * @param bool $condition + * The condition to evaluate. + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission. + * @param bool $cache_per_user + * Cache per user. + * + * @return \Drupal\Core\Access\AccessResult + * If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral() + * will be TRUE. + */ + public static function allowedIf($condition, EntityInterface $webform_entity = NULL, $cache_per_user = FALSE) { + return $condition ? static::allowed($webform_entity, $cache_per_user) : static::neutral($webform_entity, $cache_per_user); + } + + /** + * Creates an AccessResultInterface object with isAllowed() === TRUE. + * + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission. + * @param bool $cache_per_user + * Cache per user. + * + * @return \Drupal\Core\Access\AccessResultAllowed + * isAllowed() will be TRUE. + */ + public static function allowed(EntityInterface $webform_entity = NULL, $cache_per_user = FALSE) { + return static::addDependencies(AccessResult::allowed(), $webform_entity, $cache_per_user); + } + + /** + * Creates an AccessResultInterface object with isNeutral() === TRUE. + * + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission. + * @param bool $cache_per_user + * Cache per user. + * + * @return \Drupal\Core\Access\AccessResultForbidden + * isNeutral() will be TRUE. + */ + public static function neutral(EntityInterface $webform_entity = NULL, $cache_per_user = FALSE) { + return static::addDependencies(AccessResult::neutral(), $webform_entity, $cache_per_user); + } + + /** + * Creates an AccessResultInterface object with isForbidden() === TRUE. + * + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission. + * @param bool $cache_per_user + * Cache per user. + * + * @return \Drupal\Core\Access\AccessResultForbidden + * isForbidden() will be TRUE. + */ + public static function forbidden(EntityInterface $webform_entity = NULL, $cache_per_user = FALSE) { + return static::addDependencies(AccessResult::forbidden(), $webform_entity, $cache_per_user); + } + + /** + * Adds dependencies to an access result. + * + * @param \Drupal\Core\Access\AccessResult $access_result + * The access result. + * @param \Drupal\Core\Entity\EntityInterface|null $webform_entity + * A webform or webform submission. + * @param bool $cache_per_user + * Cache per user. + * + * @return \Drupal\Core\Access\AccessResult + * The access result with dependencies. + */ + public static function addDependencies(AccessResult $access_result, EntityInterface $webform_entity = NULL, $cache_per_user = FALSE) { + $access_result->cachePerPermissions(); + + if ($cache_per_user) { + $access_result->cachePerUser(); + } + + if ($webform_entity) { + if ($webform_entity instanceof WebformSubmissionInterface) { + $access_result->addCacheableDependency($webform_entity->getWebform()); + } + $access_result->addCacheableDependency($webform_entity); + } + + return $access_result; + } + +} diff --git a/web/modules/webform/src/Access/WebformAccountAccess.php b/web/modules/webform/src/Access/WebformAccountAccess.php index 1b91a5955a..3461026853 100644 --- a/web/modules/webform/src/Access/WebformAccountAccess.php +++ b/web/modules/webform/src/Access/WebformAccountAccess.php @@ -20,7 +20,7 @@ class WebformAccountAccess { * The access result. */ public static function checkAdminAccess(AccountInterface $account) { - return AccessResult::allowedIf($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission')); + return AccessResult::allowedIfHasPermissions($account, ['administer webform', 'administer webform submission'], 'OR'); } /** @@ -33,7 +33,22 @@ public static function checkAdminAccess(AccountInterface $account) { * The access result. */ public static function checkOverviewAccess(AccountInterface $account) { - return AccessResult::allowedIf($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission') || $account->hasPermission('access webform overview')); + return AccessResult::allowedIfHasPermissions($account, ['administer webform', 'administer webform submission', 'access webform overview'], 'OR'); + } + + /** + * Check whether the user has 'overview' with 'create' permission. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkTemplatesAccess(AccountInterface $account) { + $condition = ($account->hasPermission('access webform overview') && + ($account->hasPermission('administer webform') || $account->hasPermission('create webform'))); + return AccessResult::allowedIf($condition)->cachePerPermissions(); } /** @@ -46,7 +61,22 @@ public static function checkOverviewAccess(AccountInterface $account) { * The access result. */ public static function checkSubmissionAccess(AccountInterface $account) { - return AccessResult::allowedIf($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission') || $account->hasPermission('view any webform submission')); + return AccessResult::allowedIfHasPermissions($account, ['administer webform', 'administer webform submission', 'view any webform submission'], 'OR'); + } + + /** + * Check whether the user can view own submissions. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkUserSubmissionsAccess(AccountInterface $account) { + $condition = ($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission') || $account->hasPermission('view any webform submission')) + || ($account->hasPermission('access webform submission user') && \Drupal::currentUser()->id() === $account->id()); + return AccessResult::allowedIf($condition)->cachePerPermissions(); } } diff --git a/web/modules/webform/src/Access/WebformEntityAccess.php b/web/modules/webform/src/Access/WebformEntityAccess.php index a755a4f4b7..7fa5cb2735 100644 --- a/web/modules/webform/src/Access/WebformEntityAccess.php +++ b/web/modules/webform/src/Access/WebformEntityAccess.php @@ -11,6 +11,36 @@ */ class WebformEntityAccess { + /** + * Check whether the webform has drafts. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * The source entity. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkDraftsAccess(WebformInterface $webform, EntityInterface $source_entity = NULL) { + $draft = $webform->getSetting('draft'); + switch ($draft) { + case WebformInterface::DRAFT_AUTHENTICATED: + $access_result = AccessResult::allowedIf(\Drupal::currentUser()->isAuthenticated()); + break; + + case WebformInterface::DRAFT_ALL: + $access_result = AccessResult::allowed(); + break; + + case WebformInterface::DRAFT_NONE: + default: + $access_result = AccessResult::forbidden(); + break; + } + return $access_result->addCacheableDependency($webform); + } + /** * Check whether the webform has results. * @@ -71,4 +101,22 @@ public static function checkLogAccess(WebformInterface $webform = NULL, EntityIn return $access_result->addCacheTags(['config:webform.settings']); } + /** + * Check whether a webform setting is set to specified value. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param string $setting + * A webform setting. + * @param string $value + * The setting value used to determine access. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkWebformSettingValue(WebformInterface $webform = NULL, $setting = NULL, $value = NULL) { + return AccessResult::allowedIf($webform->getSetting($setting) === $value) + ->addCacheableDependency($webform); + } + } diff --git a/web/modules/webform/src/Access/WebformHandlerAccess.php b/web/modules/webform/src/Access/WebformHandlerAccess.php new file mode 100644 index 0000000000..1517541bcc --- /dev/null +++ b/web/modules/webform/src/Access/WebformHandlerAccess.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\webform\Access; + +use Drupal\Core\Access\AccessResult; + +/** + * Defines the custom access control handler for the webform handlers. + */ +class WebformHandlerAccess { + + /** + * Check whether the webform handler is enabled. + * + * @param string $webform_handler + * A webform handler id. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkHandlerAccess($webform_handler = NULL) { + /** @var \Drupal\webform\Plugin\WebformHandlerManagerInterface $handler_manager */ + $handler_manager = \Drupal::service('plugin.manager.webform.handler'); + $handler_definitions = $handler_manager->getDefinitions(); + $handler_definitions = $handler_manager->removeExcludeDefinitions($handler_definitions); + if ($webform_handler) { + $access_result = AccessResult::allowedIf(!empty($handler_definitions[$webform_handler])); + } + else { + unset($handler_definitions['broken'], $handler_definitions['email']); + $access_result = AccessResult::allowedIf(!empty($handler_definitions)); + } + return $access_result->addCacheTags(['config:webform.settings']); + } + +} diff --git a/web/modules/webform/src/Access/WebformSourceEntityAccess.php b/web/modules/webform/src/Access/WebformSourceEntityAccess.php index 8f94d857ac..e9a9e4d2d0 100644 --- a/web/modules/webform/src/Access/WebformSourceEntityAccess.php +++ b/web/modules/webform/src/Access/WebformSourceEntityAccess.php @@ -26,7 +26,9 @@ public static function checkEntityResultsAccess(EntityInterface $entity, Account /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); - return AccessResult::allowedIf($entity->access('update', $account) && $entity_reference_manager->getWebform($entity)); + $access = $entity->access('update', $account, TRUE); + $access->andIf(AccessResult::allowedIf($entity_reference_manager->getWebform($entity))); + return $access; } } diff --git a/web/modules/webform/src/Access/WebformSubmissionAccess.php b/web/modules/webform/src/Access/WebformSubmissionAccess.php index c8c45ed61b..e58ad9796b 100644 --- a/web/modules/webform/src/Access/WebformSubmissionAccess.php +++ b/web/modules/webform/src/Access/WebformSubmissionAccess.php @@ -15,13 +15,14 @@ class WebformSubmissionAccess { * Check whether a webform submissions' webform has wizard pages. * * @param \Drupal\webform\WebformSubmissionInterface $webform_submission - * A webform submisison. + * A webform submission. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ public static function checkWizardPagesAccess(WebformSubmissionInterface $webform_submission) { - return AccessResult::allowedIf($webform_submission->getWebform()->hasWizardPages()); + $condition = $webform_submission->getWebform()->hasWizardPages(); + return AccessResult::allowedIf($condition); } /** @@ -36,11 +37,12 @@ public static function checkWizardPagesAccess(WebformSubmissionInterface $webfor * The access result. */ public static function checkResendAccess(WebformSubmissionInterface $webform_submission, AccountInterface $account) { - $webform = $webform_submission->getWebform(); - if ($webform->access('submission_update_any', $account) && $webform->hasMessageHandler()) { + if ($webform_submission->getWebform()->hasMessageHandler()) { return AccessResult::allowed(); } - return AccessResult::forbidden(); + else { + return AccessResult::forbidden(); + } } } diff --git a/web/modules/webform/src/Ajax/WebformAnnounceCommand.php b/web/modules/webform/src/Ajax/WebformAnnounceCommand.php new file mode 100644 index 0000000000..91567f0291 --- /dev/null +++ b/web/modules/webform/src/Ajax/WebformAnnounceCommand.php @@ -0,0 +1,55 @@ +<?php + +namespace Drupal\webform\Ajax; + +use Drupal\Core\Ajax\CommandInterface; + +/** + * Provides an Ajax command to trigger audio UAs to read the supplied text. + * + * This command is implemented in Drupal.AjaxCommands.prototype.webformAnnounce. + */ +class WebformAnnounceCommand implements CommandInterface { + + /** + * A string to be read by the UA. + * + * @var string + */ + protected $text; + + /** + * A string to indicate the priority of the message. + * + * Can be either 'polite' or 'assertive'. + * + * @var string + */ + protected $priority; + + /** + * Constructs a \Drupal\webform\Ajax\ScrollTopCommand object. + * + * @param string $text + * A string to be read by the UA. + * @param string $priority + * A string to indicate the priority of the message. Can be either + * 'polite' or 'assertive'. + */ + public function __construct($text, $priority = 'polite') { + $this->text = $text; + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function render() { + return [ + 'command' => 'webformAnnounce', + 'text' => $this->text, + 'priority' => $this->priority, + ]; + } + +} diff --git a/web/modules/webform/src/Annotation/WebformElement.php b/web/modules/webform/src/Annotation/WebformElement.php index bc3ee9cda2..85095c9597 100644 --- a/web/modules/webform/src/Annotation/WebformElement.php +++ b/web/modules/webform/src/Annotation/WebformElement.php @@ -110,4 +110,20 @@ class WebformElement extends Plugin { */ public $states_wrapper = FALSE; + /** + * Flag that indicates the element has been deprecated. + * + * @var bool + */ + public $deprecated = FALSE; + + /** + * Deprecated message. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + public $deprecated_message = ''; + } diff --git a/web/modules/webform/src/Annotation/WebformExporter.php b/web/modules/webform/src/Annotation/WebformExporter.php index 08c0692707..9bc96646ba 100644 --- a/web/modules/webform/src/Annotation/WebformExporter.php +++ b/web/modules/webform/src/Annotation/WebformExporter.php @@ -65,6 +65,13 @@ class WebformExporter extends Plugin { */ public $archive = FALSE; + /** + * Download uploaded files (in a zipped archive). + * + * @var bool + */ + public $files = TRUE; + /** * Using export options. * diff --git a/web/modules/webform/src/Annotation/WebformHandler.php b/web/modules/webform/src/Annotation/WebformHandler.php index 83f0d91124..096b95f502 100644 --- a/web/modules/webform/src/Annotation/WebformHandler.php +++ b/web/modules/webform/src/Annotation/WebformHandler.php @@ -80,7 +80,7 @@ class WebformHandler extends Plugin { public $results = WebformHandlerInterface::RESULTS_IGNORED; /** - * Indicated whether handler support condition logic. + * Indicated whether handler supports condition logic. * * Most handlers will support conditional logic, this flat allows custom * handlers and custom modules to easily disabled conditional logic for @@ -90,6 +90,13 @@ class WebformHandler extends Plugin { */ public $conditions = TRUE; + /** + * Indicated whether handler supports tokens. + * + * @var bool + */ + public $tokens = FALSE; + /** * Indicated whether submission must be stored in the database for this handler processes results. * diff --git a/web/modules/webform/src/Annotation/WebformSourceEntity.php b/web/modules/webform/src/Annotation/WebformSourceEntity.php index c9d1b50816..303bd1d865 100644 --- a/web/modules/webform/src/Annotation/WebformSourceEntity.php +++ b/web/modules/webform/src/Annotation/WebformSourceEntity.php @@ -54,4 +54,13 @@ class WebformSourceEntity extends Plugin { */ public $weight = 0; + /** + * The element's module dependencies. + * + * @var array + * + * @see webform_webform_element_info_alter() + */ + public $dependencies = []; + } diff --git a/web/modules/webform/src/Breadcrumb/WebformBreadcrumbBuilder.php b/web/modules/webform/src/Breadcrumb/WebformBreadcrumbBuilder.php index 9ea13646c8..250ebebaf7 100644 --- a/web/modules/webform/src/Breadcrumb/WebformBreadcrumbBuilder.php +++ b/web/modules/webform/src/Breadcrumb/WebformBreadcrumbBuilder.php @@ -82,11 +82,17 @@ public function applies(RouteMatchInterface $route_match) { $path = ''; } - if ((count($args) > 2) && $args[0] == 'entity' && ($args[2] == 'webform' || $args[2] == 'webform_submission')) { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = ($route_match->getParameter('webform') instanceof WebformInterface) ? $route_match->getParameter('webform') : NULL; + + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = ($route_match->getParameter('webform_submission') instanceof WebformSubmissionInterface) ? $route_match->getParameter('webform_submission') : NULL; + + if ((count($args) > 2) && $args[0] == 'entity' && ($args[2] == 'webform' || $args[2] == 'webform_submission')) { $this->type = 'webform_source_entity'; } - elseif ($route_name === 'webform.element_plugins.test') { - $this->type = 'webform_element_plugins'; + elseif ($route_name === 'webform.reports_plugins.elements.test') { + $this->type = 'webform_plugins_elements'; } elseif (strpos($route_name, 'webform.contribute') === 0) { $this->type = 'webform_contribute'; @@ -100,21 +106,19 @@ public function applies(RouteMatchInterface $route_match) { elseif (strpos($route_name, 'entity.webform.handler.') === 0) { $this->type = 'webform_handler'; } - elseif ($route_match->getParameter('webform_submission') instanceof WebformSubmissionInterface && strpos($route_name, 'webform.user.submission') !== FALSE) { + elseif ($webform_submission && strpos($route_name, '.webform.user.submission') !== FALSE) { $this->type = 'webform_user_submission'; } - elseif (strpos($route_match->getRouteName(), 'webform.user.submissions') !== FALSE) { + elseif (strpos($route_name, '.webform.user.submissions') !== FALSE) { $this->type = 'webform_user_submissions'; } - elseif (strpos($route_match->getRouteName(), 'webform.user.drafts') !== FALSE) { + elseif (strpos($route_name, '.webform.user.drafts') !== FALSE) { $this->type = 'webform_user_drafts'; } - elseif ($route_match->getParameter('webform_submission') instanceof WebformSubmissionInterface && $route_match->getParameter('webform_submission')->access('admin')) { + elseif ($webform_submission && $webform_submission->access('admin')) { $this->type = 'webform_submission'; } - elseif (($route_match->getParameter('webform') instanceof WebformInterface && $route_match->getParameter('webform')->access('admin'))) { - /** @var \Drupal\webform\WebformInterface $webform */ - $webform = $route_match->getParameter('webform'); + elseif ($webform && $webform->access('admin')) { $this->type = ($webform->isTemplate() && $this->moduleHandler->moduleExists('webform_templates')) ? 'webform_template' : 'webform'; } elseif (strpos($path, 'admin/structure/webform/test/') !== FALSE) { @@ -126,7 +130,6 @@ public function applies(RouteMatchInterface $route_match) { else { $this->type = NULL; } - return ($this->type) ? TRUE : FALSE; } @@ -163,6 +166,13 @@ public function build(RouteMatchInterface $route_match) { $breadcrumb->addLink(Link::createFromRoute($this->t('Help'), 'help.main')); $breadcrumb->addLink(Link::createFromRoute($this->t('Webform'), 'help.page', ['name' => 'webform'])); } + elseif ($this->type == 'webform_plugins_elements') { + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Administration'), 'system.admin')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Reports'), 'system.admin_reports')); + $breadcrumb->addLink(Link::createFromRoute($this->t('Elements'), 'webform.reports_plugins.elements')); + } else { $breadcrumb = new Breadcrumb(); $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>')); @@ -179,17 +189,11 @@ public function build(RouteMatchInterface $route_match) { $breadcrumb->addLink(Link::createFromRoute($this->t('Options'), 'entity.webform_options.collection')); } elseif (strpos($route_name, 'entity.webform_image_select_images') === 0 && $route_name !== 'entity.webform_image_select_imagess.collection') { - // @todo Refactor or move to webform_image_select.module. // @see webform_image_select.module. $breadcrumb->addLink(Link::createFromRoute($this->t('Images'), 'entity.webform_image_select_images.collection')); } break; - case 'webform_element_plugins': - $breadcrumb->addLink(Link::createFromRoute($this->t('Elements'), 'webform.element_plugins')); - - break; - case 'webform_test': $breadcrumb->addLink(Link::createFromRoute($this->t('Testing'), 'webform_test.index')); break; diff --git a/web/modules/webform/src/Commands/WebformCliService.php b/web/modules/webform/src/Commands/WebformCliService.php index 649313021e..f09511664c 100644 --- a/web/modules/webform/src/Commands/WebformCliService.php +++ b/web/modules/webform/src/Commands/WebformCliService.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Commands; -use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Variable; use Drupal\Core\Cache\Cache; use Drupal\Core\Mail\MailFormatHelper; @@ -11,7 +10,9 @@ use Drupal\webform\Entity\Webform; use Drupal\webform\Form\WebformResultsClearForm; use Drupal\webform\Form\WebformSubmissionsPurgeForm; +use Drupal\webform\Utility\WebformObjectHelper; use Drupal\webform\Utility\WebformYaml; +use Drupal\webform_submission_export_import\Form\WebformSubmissionExportImportUploadForm; use Drush\Commands\DrushCommands; use Psr\Log\LogLevel; @@ -90,6 +91,7 @@ public function webform_drush_command() { 'webform' => 'The webform ID you want to export (required unless --entity-type and --entity-id are specified)', ], 'options' => [ + 'exporter' => 'The type of export. (delimited, table, yaml, or json)', // Delimited export options. 'delimiter' => 'Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimiter="\t".', 'multiple-delimiter' => 'Delimiter between an element with multiple values (defaults to site-wide setting).', @@ -102,6 +104,8 @@ public function webform_drush_command() { 'options-multiple-format' => 'Set to "separate" (default) or "compact" to determine how multiple select list values are exported.', 'entity-reference-items' => 'Comma-separated list of entity reference items (id, title, and/or url) to be exported.', 'excluded-columns' => 'Comma-separated list of component IDs or webform keys to exclude.', + // CSV options + 'uuid' => ' Use UUIDs for all entity references. (Only applies to CSV download)', // Download options. 'entity-type' => 'The entity type to which this submission was submitted from.', 'entity-id' => 'The ID of the entity of which this webform submission was submitted from.', @@ -114,11 +118,30 @@ public function webform_drush_command() { 'sticky' => 'Flagged/starred submission status.', 'files' => 'Download files: "1" or "0" (default). If set to 1, the exported CSV file and any submission file uploads will be download in a gzipped tar file.', // Output options. - 'destination' => 'The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the commandline.', + 'destination' => 'The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the command line.', ], 'aliases' => ['wfx'], ]; + $items['webform-import'] = [ + 'description' => 'Imports webform submissions from a CSV file.', + 'core' => ['8+'], + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'arguments' => [ + 'webform' => 'The webform ID you want to import (required unless --entity-type and --entity-id are specified)', + 'import_uri' => 'The path or URI for the CSV file to be imported.', + ], + 'options' => [ + // Import options. + 'skip_validation' => 'Skip form validation.', + 'treat_warnings_as_errors' => 'Treat all warnings as errors.', + // Source entity options. + 'entity-type' => 'The entity type to which this submission was submitted from.', + 'entity-id' => 'The ID of the entity of which this webform submission was submitted from.', + ], + 'aliases' => ['wfi'], + ]; + $items['webform-purge'] = [ 'description' => "Purge webform submissions from the databases", 'core' => ['8+'], @@ -233,11 +256,11 @@ public function webform_drush_command() { /* Repair */ $items['webform-repair'] = [ - 'description' => 'Makes sure all Webform admin settings and webforms are up-to-date.', + 'description' => 'Makes sure all Webform admin configuration and webform settings are up-to-date.', 'core' => ['8+'], 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, 'examples' => [ - 'webform-repair' => 'Repairs admin settings and webforms are up-to-date.', + 'webform-repair' => 'Repairs admin configuration and webform settings are up-to-date.', ], 'aliases' => ['wfr'], ]; @@ -348,6 +371,70 @@ public function drush_webform_export($webform_id = NULL) { return NULL; } + /******************************************************************************/ + // Import. + /******************************************************************************/ + + /** + * {@inheritdoc} + */ + public function drush_webform_import_validate($webform_id = NULL, $import_uri = NULL) { + if (!\Drupal::moduleHandler()->moduleExists('webform_submission_export_import')) { + return $this->drush_set_error($this->dt('The Webform Submission Export/Import module must be enabled to perform imports.')); + } + + if ($errors = $this->_drush_webform_validate($webform_id)) { + return $errors; + } + + if (empty($import_uri)) { + return $this->drush_set_error($this->dt('Please include the CSV path or URI.')); + } + if (file_exists($import_uri)) { + return NULL; + } + + return NULL; + } + + /** + * {@inheritdoc} + */ + public function drush_webform_import($webform_id = NULL, $import_uri = NULL) { + /** @var \Drupal\webform_submission_export_import\WebformSubmissionExportImportImporterInterface $submission_importer */ + $submission_importer = \Drupal::service('webform_submission_export_import.importer'); + + // Get webform. + $webform = Webform::load($webform_id); + + // Get source entity. + $entity_type = $this->drush_get_option('entity-type'); + $entity_id = $this->drush_get_option('entity-id'); + if ($entity_type && $entity_id) { + $source_entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id); + } + else { + $source_entity = NULL; + } + + // Get import options + $import_options = $this->_drush_get_options($submission_importer->getDefaultImportOptions()); + + $submission_importer->setWebform($webform); + $submission_importer->setSourceEntity($source_entity); + $submission_importer->setImportOptions($import_options); + $submission_importer->setImportUri($import_uri);; + $t_args = ['@total' => $submission_importer->getTotal()]; + if (!$this->drush_confirm($this->dt('Are you sure you want to import @total submissions?', $t_args) . PHP_EOL . $this->dt('This action cannot be undone.'))) { + return $this->drush_user_abort(); + } + + WebformSubmissionExportImportUploadForm::batchSet($webform, $source_entity, $import_uri, $import_options); + $this->drush_backend_batch_process(); + + return NULL; + } + /******************************************************************************/ // Purge /******************************************************************************/ @@ -394,7 +481,8 @@ public function drush_webform_purge($webform_id = NULL) { // Make sure there are submissions that need to be deleted. if (!$submission_storage->getTotal($webform)) { - return $this->drush_set_error($this->dt('There are no submissions that need to be deleted.')); + $this->drush_print($this->dt('There are no submissions that need to be deleted.')); + return; } if (!$webform) { @@ -484,30 +572,44 @@ public function drush_webform_tidy($target = NULL) { $original_yaml = file_get_contents($file->uri); $tidied_yaml = $original_yaml; - // Add module dependency to exporter webform and webform options config entities. - if ($dependencies && preg_match('/^(webform\.webform\.|webform\.webform_options\.)/', $file->filename)) { + try { + $data = Yaml::decode($tidied_yaml); + } + catch (\Exception $exception) { + $message = 'Error parsing: ' . $file->filename . PHP_EOL . $exception->getMessage(); + if (strlen($message) > 255) { + $message = substr($message, 0, 255) . '…'; + } + $this->drush_log($message, LogLevel::ERROR); + $this->drush_print($message); + continue; + } + + // Tidy elements. + if (strpos($file->filename, 'webform.webform.') === 0 && isset($data['elements'])) { try { - $data = Yaml::decode($tidied_yaml); - if (empty($data['dependencies']['enforced']['module']) || !in_array($target, $data['dependencies']['enforced']['module'])) { - $this->drush_print($this->dt('Adding module dependency to @file...', ['@file' => $file->filename])); - $data['dependencies']['enforced']['module'][] = $target; - $tidied_yaml = Yaml::encode($data); - } + $elements = WebformYaml::tidy($data['elements']); + $data['elements'] = $elements; } catch (\Exception $exception) { - $message = 'Error parsing: ' . $file->filename . PHP_EOL . $exception->getMessage(); - if (strlen($message) > 255) { - $message = substr($message, 0, 255) . '...'; - } - $this->drush_log($message, LogLevel::ERROR); - $this->drush_print($message); + // Do nothing. } } + // Add module dependency to exporter webform and webform options config entities. + if ($dependencies && preg_match('/^(webform\.webform\.|webform\.webform_options\.)/', $file->filename)) { + if (empty($data['dependencies']['enforced']['module']) || !in_array($target, $data['dependencies']['enforced']['module'])) { + $this->drush_print($this->dt('Adding module dependency to @file…', ['@file' => $file->filename])); + $data['dependencies']['enforced']['module'][] = $target; + } + } + + $tidied_yaml = Yaml::encode($data); + // Tidy and add new line to the end of the tidied file. $tidied_yaml = WebformYaml::tidy($tidied_yaml) . PHP_EOL; if ($tidied_yaml != $original_yaml) { - $this->drush_print($this->dt('Tidying @file...', ['@file' => $file->filename])); + $this->drush_print($this->dt('Tidying @file…', ['@file' => $file->filename])); file_put_contents($file->uri, $tidied_yaml); $total++; } @@ -602,36 +704,26 @@ public function drush_webform_libraries_make() { * {@inheritdoc} */ public function drush_webform_libraries_composer() { - // Load existing composer.json file. - $data = json_decode('{ - "name": "drupal/webform", - "description": "Enables the creation of webforms and questionnaires.", - "type": "drupal-module", - "license": "GPL-2.0+", - "minimum-stability": "dev", - "homepage": "https://drupal.org/project/webform", - "authors": [ - { - "name": "Jacob Rockowitz (jrockowitz)", - "homepage": "https://www.drupal.org/u/jrockowitz", - "role": "Maintainer" - } - ], - "support": { - "issues": "https://drupal.org/project/issues/webform", - "source": "http://cgit.drupalcode.org/webform" - } -}', TRUE); + // Load existing composer.json file and unset certain properties. + $composer_path = drupal_get_path('module', 'webform') . '/composer.json'; + $json = file_get_contents($composer_path); + $data = json_decode($json , FALSE, $this->drush_webform_composer_get_json_encode_options()); + $data = (array) $data; + unset($data['extra'], $data['require-dev']); + $data = (object) $data; // Set disable tls. $this->drush_webform_composer_set_disable_tls($data); // Set libraries. - $data['repositories'] = []; - $data['require'] = []; - $this->drush_webform_composer_set_libraries($data['repositories'], $data['require']); - - $this->drush_print(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + $data->repositories = (object) []; + $data->require = (object) []; + $this->drush_webform_composer_set_libraries($data->repositories, $data->require); + // Remove _webform property. + foreach ($data->repositories as &$repository) { + unset($repository['_webform']); + } + $this->drush_print(json_encode($data, $this->drush_webform_composer_get_json_encode_options())); } /** @@ -640,7 +732,7 @@ public function drush_webform_libraries_composer() { public function drush_webform_libraries_download() { // Remove all existing libraries (including excluded). if ($this->drush_webform_libraries_remove(FALSE)) { - $this->drush_print($this->dt('Removing existing libraries...')); + $this->drush_print($this->dt('Removing existing libraries…')); } $temp_dir = $this->drush_tempdir(); @@ -649,6 +741,11 @@ public function drush_webform_libraries_download() { $libraries_manager = \Drupal::service('webform.libraries_manager'); $libraries = $libraries_manager->getLibraries(TRUE); foreach ($libraries as $library_name => $library) { + // Skip libraries installed by other modules. + if (!empty($library['module'])) { + continue; + } + // Download archive to temp directory. $download_url = $library['download_url']->toString(); $this->drush_print("Downloading $download_url"); @@ -692,13 +789,15 @@ public function drush_webform_libraries_download() { public function drush_webform_libraries_remove($status = NULL) { $status = ($status !== FALSE); if ($status) { - $this->drush_print($this->dt('Beginning to remove libraries...')); + $this->drush_print($this->dt('Beginning to remove libraries…')); } $removed = FALSE; /** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */ $libraries_manager = \Drupal::service('webform.libraries_manager'); $libraries = $libraries_manager->getLibraries(); + // Manually add deleted libraries, so that they will always be removed. + $libraries['jquery.word-and-character-counter'] = 'jquery.word-and-character-counter'; foreach ($libraries as $library_name => $library) { $library_path = '/libraries/' . $library_name; $library_exists = (file_exists(DRUPAL_ROOT . $library_path)) ? TRUE : FALSE; @@ -710,7 +809,7 @@ public function drush_webform_libraries_remove($status = NULL) { '@name' => $library_name, '@path' => $library_path, ]; - $this->drush_print($this->dt('@name removed from @path...', $t_args)); + $this->drush_print($this->dt('@name removed from @path…', $t_args)); } } } @@ -727,6 +826,8 @@ public function drush_webform_libraries_remove($status = NULL) { /** * {@inheritdoc} + * + * @see \Drupal\webform\Form\AdminConfig\WebformAdminConfigAdvancedForm::submitForm */ public function drush_webform_repair() { if (!$this->drush_confirm($this->dt("Are you sure you want repair the Webform module's admin settings and webforms?"))) { @@ -735,23 +836,26 @@ public function drush_webform_repair() { module_load_include('install', 'webform'); - $this->drush_print('Repairing admin settings...'); + $this->drush_print($this->dt('Repairing webform submission storage schema…')); + _webform_update_webform_submission_storage_schema(); + + $this->drush_print($this->dt('Repairing admin settings…')); _webform_update_admin_settings(TRUE); - $this->drush_print('Repairing webform settings...'); + $this->drush_print($this->dt('Repairing webform settings…')); _webform_update_webform_settings(); - $this->drush_print('Repairing webform handlers...'); - _webform_update_webform_handler_configuration(); + $this->drush_print($this->dt('Repairing webform handlers…')); + _webform_update_webform_handler_settings(); - $this->drush_print('Repairing webform field storage definitions...'); + $this->drush_print($this->dt('Repairing webform field storage definitions…')); _webform_update_field_storage_definitions(); - $this->drush_print('Repairing webform submission storage schema...'); + $this->drush_print($this->dt('Repairing webform submission storage schema…')); _webform_update_webform_submission_storage_schema(); // Validate all webform elements. - $this->drush_print('Validating webform elements...'); + $this->drush_print($this->dt('Validating webform elements…')); /** @var \Drupal\webform\WebformEntityElementsValidatorInterface $elements_validator */ $elements_validator = \Drupal::service('webform.elements_validator'); @@ -759,7 +863,7 @@ public function drush_webform_repair() { $webforms = Webform::loadMultiple(); foreach ($webforms as $webform) { if ($messages = $elements_validator->validate($webform)) { - $this->drush_print(' ' . t('@title (@id): Found element validation errors.', ['@title' => $webform->label(), '@id' => $webform->id()])); + $this->drush_print(' ' . $this->dt('@title (@id): Found element validation errors.', ['@title' => $webform->label(), '@id' => $webform->id()])); foreach ($messages as $message) { $this->drush_print(' - ' . strip_tags($message)); } @@ -865,7 +969,7 @@ protected function _drush_webform_docs_tidy($html) { // Configuration. // - http://us3.php.net/manual/en/book.tidy.php // - http://tidy.sourceforge.net/docs/quickref.html#wrap - $config = ['show-body-only' => TRUE, 'wrap' => '0']; + $config = ['show-body-only' => TRUE, 'wrap' => '10000']; $tidy = new \tidy(); $tidy->parseString($html, $config, 'utf8'); @@ -879,6 +983,9 @@ protected function _drush_webform_docs_tidy($html) { $html = preg_replace('#<pre><code>\s*#', "<code>\n", $html); $html = preg_replace('#\s*</code></pre>#', "\n</code>", $html); + // Fix code in webform-libraries.html. + $html = str_replace(' > ', ' > ', $html); + // Remove space after <br> tags. $html = preg_replace('/(<br[^>]*>)\s+/', '\1', $html); @@ -936,25 +1043,29 @@ public function drush_webform_composer_update() { $composer_directory = $this->composer_directory; $json = file_get_contents($composer_json); - $data = Json::decode($json) + [ - 'repositories' => [], - 'require' => [], - ]; + $data = json_decode($json, FALSE, $this->drush_webform_composer_get_json_encode_options()); + if (!isset($data->repositories)) { + $data->repositories = (object) []; + } + if (!isset($data->require)) { + $data->repositories = (object) []; + } // Add drupal-library to installer paths. if (strpos($json, 'type:drupal-library') === FALSE) { - $data['extra']['installer-paths'][$composer_directory . 'libraries/{$name}'][] = 'type:drupal-library'; + $library_path = $composer_directory . 'libraries/{$name}'; + $data->extra->{'installer-paths'}->{$library_path}[] = 'type:drupal-library'; } // Get repositories and require. - $repositories = &$data['repositories']; - $require = &$data['require']; + $repositories = &$data->repositories; + $require = &$data->require; // Remove all existing _webform repositories. foreach ($repositories as $repository_name => $repository) { - if (!empty($repository['_webform'])) { - $package_name = $repositories[$repository_name]['package']['name']; - unset($repositories[$repository_name], $require[$package_name]); + if (!empty($repository->_webform)) { + $package_name = $repositories->{$repository_name}->package->name; + unset($repositories->{$repository_name}, $require->{$package_name}); } } @@ -964,55 +1075,78 @@ public function drush_webform_composer_update() { // Set libraries. $this->drush_webform_composer_set_libraries($repositories, $require); - $json_options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; - file_put_contents($composer_json, json_encode($data, $json_options)); + file_put_contents($composer_json, json_encode($data, $this->drush_webform_composer_get_json_encode_options())); $this->drush_print("$composer_json updated."); - $this->drush_print('Make sure to run `composer update`.'); + $this->drush_print('Make sure to run `composer update --lock`.'); } + /** + * Get Composer specific JSON encode options. + * + * @return int + * Composer specific JSON encode options. + * + * @see https://getcomposer.org/apidoc/1.6.2/Composer/Json/JsonFile.html#method_encode + */ + protected function drush_webform_composer_get_json_encode_options() { + return JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE; + } /** * Set composer disable tls. * * This is needed when CKEditor's HTTPS server's SSL is not working properly. * - * @param array $data + * @param object $data * Composer JSON data. */ - protected function drush_webform_composer_set_disable_tls(array &$data) { + protected function drush_webform_composer_set_disable_tls(&$data) { // Remove disable-tls config. - if (isset($data['config']) && isset($data['config']['disable-tls'])) { - unset($data['config']['disable-tls']); + if (isset($data->config) && isset($data->config->{'disable-tls'})) { + unset($data->config->{'disable-tls'}); } if ($this->drush_get_option('disable-tls')) { - $data['config']['disable-tls'] = TRUE; + $data->config->{'disable-tls'} = TRUE; } } /** * Set composer libraries. * - * @param array $repositories + * @param object $repositories * Composer repositories. - * @param array $require + * @param object $require * Composer require. */ - protected function drush_webform_composer_set_libraries(array &$repositories, array &$require) { + protected function drush_webform_composer_set_libraries(&$repositories, &$require) { /** @var \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager */ $libraries_manager = \Drupal::service('webform.libraries_manager'); $libraries = $libraries_manager->getLibraries(TRUE); foreach ($libraries as $library_name => $library) { // Never overwrite existing repositories. - if (isset($repositories[$library_name])) { + if (isset($repositories->{$library_name})) { + continue; + } + + // Skip libraries installed by other modules. + if (!empty($library['module'])) { continue; } $dist_url = $library['download_url']->toString(); - $dist_type = (preg_match('/\.zip$/', $dist_url)) ? 'zip' : 'file'; + if (preg_match('/\.zip$/', $dist_url)) { + $dist_type = 'zip'; + } + elseif (preg_match('/\.tgz$/', $dist_url)) { + $dist_type = 'tar'; + } + else { + $dist_type = 'file'; + } $package_version = $library['version']; $package_name = (strpos($library_name, '.') === FALSE) ? "$library_name/$library_name" : str_replace('.', '/', $library_name); - $repositories[$library_name] = [ + $repositories->$library_name = [ '_webform' => TRUE, 'type' => 'package', 'package' => [ @@ -1032,10 +1166,10 @@ protected function drush_webform_composer_set_libraries(array &$repositories, ar ], ]; - $require[$package_name] = $package_version; + $require->$package_name = $package_version; } - ksort($repositories); - ksort($require); + $repositories = WebformObjectHelper::sortByProperty($repositories); + $require = WebformObjectHelper::sortByProperty($require); } /******************************************************************************/ @@ -1079,8 +1213,8 @@ protected function drush_webform_generate_commands_drush8() { /******************************************************************************/"; // Validate. - $validate_method = 'drush_' . str_replace('-','_', $command_key) . '_validate'; - $validate_hook = 'drush_' . str_replace('-','_', $command_key) . '_validate'; + $validate_method = 'drush_' . str_replace('-', '_', $command_key) . '_validate'; + $validate_hook = 'drush_' . str_replace('-', '_', $command_key) . '_validate'; if (method_exists($this, $validate_method)) { $functions[] = " /** @@ -1092,8 +1226,8 @@ function $validate_hook() { } // Commands. - $command_method = 'drush_' . str_replace('-','_', $command_key); - $command_hook = 'drush_' . str_replace('-','_', $command_key); + $command_method = 'drush_' . str_replace('-', '_', $command_key); + $command_hook = 'drush_' . str_replace('-', '_', $command_key); if (method_exists($this, $command_method)) { $functions[] = " /** @@ -1105,7 +1239,7 @@ function $command_hook() { } } - // Build commands + // Build commands. $commands = Variable::export($this->webform_drush_command()); // Remove [datatypes] which are only needed for Drush 9.x. $commands = preg_replace('/\[(boolean)\]\s+/', '', $commands); @@ -1148,7 +1282,7 @@ protected function drush_webform_generate_commands_drush9() { $methods = []; foreach ($items as $command_key => $command_item) { - $command_name = str_replace('-',':', $command_key); + $command_name = str_replace('-', ':', $command_key); // Set defaults. $command_item += [ @@ -1165,7 +1299,7 @@ protected function drush_webform_generate_commands_drush9() { /****************************************************************************/"; // Validate. - $validate_method = 'drush_' . str_replace('-','_', $command_key) . '_validate'; + $validate_method = 'drush_' . str_replace('-', '_', $command_key) . '_validate'; if (method_exists($this, $validate_method)) { $methods[] = " /** @@ -1179,7 +1313,7 @@ public function $validate_method(CommandData \$commandData) { } // Command. - $command_method = 'drush_' . str_replace('-','_', $command_key); + $command_method = 'drush_' . str_replace('-', '_', $command_key); if (method_exists($this, $command_method)) { $command_params = []; $command_arguments = []; @@ -1208,7 +1342,7 @@ public function $validate_method(CommandData \$commandData) { } $command_annotations[] = "@option $option_name $option_description"; - $command_options[$option_name] = $option_default ; + $command_options[$option_name] = $option_default; } if ($command_options) { $command_options = Variable::export($command_options); @@ -1220,6 +1354,7 @@ public function $validate_method(CommandData \$commandData) { // usage. foreach ($command_item['examples'] as $example_name => $example_description) { + $example_name = str_replace('-', ':', $example_name); $command_annotations[] = "@usage $example_name"; $command_annotations[] = " $example_description"; } @@ -1318,16 +1453,40 @@ protected function _drush_webform_validate($webform_id = NULL) { return NULL; } + /** + * Get drush command options with dashed converted to underscores. + * + * @param array $default_options + * The commands default options + * + * @return array + * An associative array of options. + */ + protected function _drush_get_options(array $default_options) { + $options = $this->drush_redispatch_get_options(); + // Convert dashes to underscores. + foreach ($options as $key => $value) { + unset($options[$key]); + if (isset($default_options[$key]) && is_array($default_options[$key])) { + $value = explode(',', $value); + } + $options[str_replace('-', '_', $key)] = $value; + } + $options += $default_options; + return $options; + } + } // Add dt() function so that the WebformEditorialController can extract // editorial. // @see \Drupal\webform_editorial\Controller\WebformEditorialController::drush if (!function_exists('dt')) { + /** * Rudimentary replacement for Drupal API t() function. * - * @param string + * @param string $string * String to process, possibly with replacement item. * * @return string @@ -1336,5 +1495,5 @@ protected function _drush_webform_validate($webform_id = NULL) { function dt($string) { return $string; } -} +} diff --git a/web/modules/webform/src/Commands/WebformCommands.php b/web/modules/webform/src/Commands/WebformCommands.php index 9d94030bca..afab32a5a3 100644 --- a/web/modules/webform/src/Commands/WebformCommands.php +++ b/web/modules/webform/src/Commands/WebformCommands.php @@ -34,6 +34,7 @@ public function drush_webform_export_validate(CommandData $commandData) { * * @command webform:export * @param $webform The webform ID you want to export (required unless --entity-type and --entity-id are specified) + * @option exporter The type of export. (delimited, table, yaml, or json) * @option delimiter Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimiter="\t". * @option multiple-delimiter Delimiter between an element with multiple values (defaults to site-wide setting). * @option file-name File name used to export submission and uploaded filed. You may use tokens. @@ -43,6 +44,7 @@ public function drush_webform_export_validate(CommandData $commandData) { * @option options-multiple-format Set to "separate" (default) or "compact" to determine how multiple select list values are exported. * @option entity-reference-items Comma-separated list of entity reference items (id, title, and/or url) to be exported. * @option excluded-columns Comma-separated list of component IDs or webform keys to exclude. + * @option uuid Use UUIDs for all entity references. (Only applies to CSV download) * @option entity-type The entity type to which this submission was submitted from. * @option entity-id The ID of the entity of which this webform submission was submitted from. * @option range-type Range of submissions to export: "all", "latest", "serial", "sid", or "date". @@ -53,13 +55,42 @@ public function drush_webform_export_validate(CommandData $commandData) { * @option state Submission state to be included: "completed", "draft" or "all" (default). * @option sticky Flagged/starred submission status. * @option files Download files: "1" or "0" (default). If set to 1, the exported CSV file and any submission file uploads will be download in a gzipped tar file. - * @option destination The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the commandline. + * @option destination The full path and filename in which the CSV or archive should be stored. If omitted the CSV file or archive will be outputted to the command line. * @aliases wfx */ - public function drush_webform_export($webform = NULL, array $options = ['delimiter' => NULL, 'multiple-delimiter' => NULL, 'file-name' => NULL, 'header-format' => NULL, 'options-item-format' => NULL, 'options-single-format' => NULL, 'options-multiple-format' => NULL, 'entity-reference-items' => NULL, 'excluded-columns' => NULL, 'entity-type' => NULL, 'entity-id' => NULL, 'range-type' => NULL, 'range-latest' => NULL, 'range-start' => NULL, 'range-end' => NULL, 'order' => NULL, 'state' => NULL, 'sticky' => NULL, 'files' => NULL, 'destination' => NULL]) { + public function drush_webform_export($webform = NULL, array $options = ['exporter' => NULL, 'delimiter' => NULL, 'multiple-delimiter' => NULL, 'file-name' => NULL, 'header-format' => NULL, 'options-item-format' => NULL, 'options-single-format' => NULL, 'options-multiple-format' => NULL, 'entity-reference-items' => NULL, 'excluded-columns' => NULL, 'uuid' => NULL, 'entity-type' => NULL, 'entity-id' => NULL, 'range-type' => NULL, 'range-latest' => NULL, 'range-start' => NULL, 'range-end' => NULL, 'order' => NULL, 'state' => NULL, 'sticky' => NULL, 'files' => NULL, 'destination' => NULL]) { $this->cliService->drush_webform_export($webform); } + /****************************************************************************/ + // drush webform:import. DO NOT EDIT. + /****************************************************************************/ + + /** + * @hook validate webform:import + */ + public function drush_webform_import_validate(CommandData $commandData) { + $arguments = $commandData->arguments(); + array_shift($arguments); + call_user_func_array([$this->cliService, 'drush_webform_import_validate'], $arguments); + } + + /** + * Imports webform submissions from a CSV file. + * + * @command webform:import + * @param $webform The webform ID you want to import (required unless --entity-type and --entity-id are specified) + * @param $import_uri The path or URI for the CSV file to be imported. + * @option skip_validation Skip form validation. + * @option treat_warnings_as_errors Treat all warnings as errors. + * @option entity-type The entity type to which this submission was submitted from. + * @option entity-id The ID of the entity of which this webform submission was submitted from. + * @aliases wfi + */ + public function drush_webform_import($webform = NULL, $import_uri = NULL, array $options = ['skip_validation' => NULL, 'treat_warnings_as_errors' => NULL, 'entity-type' => NULL, 'entity-id' => NULL]) { + $this->cliService->drush_webform_import($webform, $import_uri); + } + /****************************************************************************/ // drush webform:purge. DO NOT EDIT. /****************************************************************************/ @@ -81,11 +112,11 @@ public function drush_webform_purge_validate(CommandData $commandData) { * @option all Flush all submissions * @option entity-type The entity type for webform submissions to be purged * @option entity-id The ID of the entity for webform submissions to be purged - * @usage drush webform-purge + * @usage drush webform:purge * Pick a webform and then purge its submissions. - * @usage drush webform-purge contact + * @usage drush webform:purge contact * Delete 'Contact' webform submissions. - * @usage drush webform-purge --all + * @usage drush webform:purge ::all * Purge all webform submissions. * @aliases wfp */ @@ -113,7 +144,7 @@ public function drush_webform_tidy_validate(CommandData $commandData) { * @param $target The module (config/install), config directory (sync), or path (/some/path) that needs its YAML configuration files tidied. (Defaults to webform) * @option dependencies Add module dependencies to installed webform and options configuration entities. * @option prefix Prefix for file names to be tidied. (Defaults to webform) - * @usage drush webform-tidy webform + * @usage drush webform:tidy webform * Tidies YAML configuration files in 'webform/config' for the Webform module * @aliases wft */ @@ -129,7 +160,7 @@ public function drush_webform_tidy($target = NULL, array $options = ['dependenci * Displays the status of third party libraries required by the Webform module. * * @command webform:libraries:status - * @usage webform-libraries-status + * @usage webform:libraries:status * Displays the status of third party libraries required by the Webform module. * @aliases wfls */ @@ -145,7 +176,7 @@ public function drush_webform_libraries_status() { * Generates libraries YAML to be included in a drush.make.yml files. * * @command webform:libraries:make - * @usage webform-libraries-make + * @usage webform:libraries:make * Generates libraries YAML to be included in a drush.make.yml file. * @aliases wflm */ @@ -162,7 +193,7 @@ public function drush_webform_libraries_make() { * * @command webform:libraries:composer * @option disable-tls If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. - * @usage webform-libraries-composer + * @usage webform:libraries:composer * Generates the Webform module's composer.json with libraries as repositories. * @aliases wflc */ @@ -178,7 +209,7 @@ public function drush_webform_libraries_composer(array $options = ['disable-tls' * Download third party libraries required by the Webform module. * * @command webform:libraries:download - * @usage webform-libraries-download + * @usage webform:libraries:download * Download third party libraries required by the Webform module. * @aliases wfld */ @@ -194,7 +225,7 @@ public function drush_webform_libraries_download() { * Removes all downloaded third party libraries required by the Webform module. * * @command webform:libraries:remove - * @usage webform-libraries-remove + * @usage webform:libraries:remove * Removes all downloaded third party libraries required by the Webform module. * @aliases wflr */ @@ -236,11 +267,11 @@ public function drush_webform_generate($webform_id = NULL, $num = NULL, array $o /****************************************************************************/ /** - * Makes sure all Webform admin settings and webforms are up-to-date. + * Makes sure all Webform admin configuration and webform settings are up-to-date. * * @command webform:repair - * @usage webform-repair - * Repairs admin settings and webforms are up-to-date. + * @usage webform:repair + * Repairs admin configuration and webform settings are up-to-date. * @aliases wfr */ public function drush_webform_repair() { @@ -264,7 +295,7 @@ public function drush_webform_docs_validate(CommandData $commandData) { * Generates HTML documentation. * * @command webform:docs - * @usage webform-repair + * @usage webform:repair * Generates HTML documentation used by the Webform module's documentation pages. * @aliases wfd */ @@ -290,7 +321,7 @@ public function drush_webform_composer_update_validate(CommandData $commandData) * * @command webform:composer:update * @option disable-tls If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. - * @usage webform-composer-update + * @usage webform:composer:update * Updates the Drupal installation's composer.json to include the Webform module's selected libraries as repositories. * @aliases wfcu */ @@ -306,7 +337,7 @@ public function drush_webform_composer_update(array $options = ['disable-tls' => * Generate Drush commands from webform.drush.inc for Drush 8.x to WebformCommands for Drush 9.x. * * @command webform:generate:commands - * @usage drush webform-generate-commands + * @usage drush webform:generate:commands * Generate Drush commands from webform.drush.inc for Drush 8.x to WebformCommands for Drush 9.x. * @aliases wfgc */ diff --git a/web/modules/webform/src/Commands/WebformCommandsBase.php b/web/modules/webform/src/Commands/WebformCommandsBase.php index b7f28b4179..bbbc623eaa 100644 --- a/web/modules/webform/src/Commands/WebformCommandsBase.php +++ b/web/modules/webform/src/Commands/WebformCommandsBase.php @@ -110,9 +110,17 @@ public function drush_mkdir($path) { public function drush_tarball_extract($path, $destination = FALSE) { $this->drush_mkdir($destination); - $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination); - if (!$return) { - throw new \Exception(dt('Unable to unzip !filename.', ['!filename' => $path])); + if (preg_match('/\.tgz$/', $path)) { + $return = drush_shell_cd_and_exec(dirname($path), "tar -xvzf %s -C %s", $path, $destination); + if (!$return) { + throw new \Exception(dt('Unable to extract !filename.' . PHP_EOL . implode(PHP_EOL, drush_shell_exec_output()), ['!filename' => $path])); + } + } + else { + $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination); + if (!$return) { + throw new \Exception(dt('Unable to extract !filename.' . PHP_EOL . implode(PHP_EOL, drush_shell_exec_output()), ['!filename' => $path])); + } } return $return; } diff --git a/web/modules/webform/src/Controller/WebformAddonsController.php b/web/modules/webform/src/Controller/WebformAddonsController.php index e1771d8791..c6eb16ab10 100644 --- a/web/modules/webform/src/Controller/WebformAddonsController.php +++ b/web/modules/webform/src/Controller/WebformAddonsController.php @@ -7,12 +7,20 @@ use Drupal\webform\Element\WebformMessage; use Drupal\webform\WebformAddonsManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; /** * Provides route responses for webform add-on. */ class WebformAddonsController extends ControllerBase implements ContainerInjectionInterface { + /** + * The current request. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + /** * The webform add-ons manager. * @@ -23,10 +31,13 @@ class WebformAddonsController extends ControllerBase implements ContainerInjecti /** * Constructs a WebformAddonsController object. * + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. * @param \Drupal\webform\WebformAddonsManagerInterface $addons * The webform add-ons manager. */ - public function __construct(WebformAddonsManagerInterface $addons) { + public function __construct(RequestStack $request_stack, WebformAddonsManagerInterface $addons) { + $this->request = $request_stack->getCurrentRequest(); $this->addons = $addons; } @@ -35,6 +46,7 @@ public function __construct(WebformAddonsManagerInterface $addons) { */ public static function create(ContainerInterface $container) { return new static( + $container->get('request_stack'), $container->get('webform.addons_manager') ); } @@ -53,6 +65,34 @@ public function index() { ], ]; + // Filter. + $build['filter'] = [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by keyword'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-summary' => '.webform-addons-summary', + 'data-item-singlular' => $this->t('add-on'), + 'data-item-plural' => $this->t('add-ons'), + 'data-no-results' => '.webform-addons-no-results', + 'data-element' => '.admin-list', + 'data-source' => 'li', + 'data-parent' => 'li', + 'title' => $this->t('Enter a keyword to filter by.'), + 'autofocus' => 'autofocus', + ], + ]; + + // Display info. + $build['info'] = [ + '#markup' => $this->t('@total add-ons', ['@total' => count($this->addons->getProjects())]), + '#prefix' => '<p class="webform-addons-summary">', + '#suffix' => '</p>', + ]; + // Projects. $build['projects'] = [ '#type' => 'container', @@ -60,7 +100,11 @@ public function index() { 'class' => ['webform-addons-projects', 'js-webform-details-toggle', 'webform-details-toggle'], ], ]; - $build['projects']['#attached']['library'][] = 'webform/webform.addons'; + + // Store and disable compact mode. + // @see system_admin_compact_mode + $system_admin_compact_mode = system_admin_compact_mode(); + $this->request->cookies->set('Drupal_visitor_admin_compact_mode', FALSE); $categories = $this->addons->getCategories(); foreach ($categories as $category_name => $category) { @@ -88,7 +132,8 @@ public function index() { '#message_type' => 'warning', '#message_close' => TRUE, '#message_storage' => WebformMessage::STORAGE_USER, - '#message_message' => $this->t('Please install to the <a href=":href">@title</a> project to improve the Webform module\'s user experience.', [':href' => $project['url']->toString(), '@title' => $project['title']]), + '#message_message' => $this->t('Please install to the <a href=":href">@title</a> project to improve the Webform module\'s user experience.', [':href' => $project['url']->toString(), '@title' => $project['title']]) . + ' <em>' . $project['install'] . '</em>', '#weight' => -100, ]; } @@ -101,6 +146,20 @@ public function index() { ]; } + // Reset compact mode to stored setting. + $this->request->cookies->get('Drupal_visitor_admin_compact_mode', $system_admin_compact_mode); + + // No results. + $build['no_results'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('No add-ons found. Try a different search.'), + '#message_type' => 'info', + '#attributes' => ['class' => ['webform-addons-no-results']], + ]; + + $build['#attached']['library'][] = 'webform/webform.addons'; + $build['#attached']['library'][] = 'webform/webform.admin'; + return $build; } diff --git a/web/modules/webform/src/Controller/WebformContributeController.php b/web/modules/webform/src/Controller/WebformContributeController.php index a6cf6bfd96..67813ca08a 100644 --- a/web/modules/webform/src/Controller/WebformContributeController.php +++ b/web/modules/webform/src/Controller/WebformContributeController.php @@ -5,8 +5,8 @@ use Drupal\Component\Serialization\Json; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Url; +use Drupal\webform\WebformContributeManagerInterface; use GuzzleHttp\ClientInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -18,30 +18,30 @@ class WebformContributeController extends ControllerBase implements ContainerInjectionInterface { /** - * The configuration object factory. + * The Guzzle HTTP client. * - * @var \Drupal\Core\Config\ConfigFactoryInterface + * @var \GuzzleHttp\ClientInterface */ - protected $configFactory; + protected $httpClient; /** * The Guzzle HTTP client. * - * @var \GuzzleHttp\ClientInterface + * @var \Drupal\webform\WebformContributeManagerInterface */ - protected $httpClient; + protected $contributeManager; /** - * Constructs a WebfomrContributeController object. + * Constructs a WebformContributeController object. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The configuration object factory. * @param \GuzzleHttp\ClientInterface $http_client * The Guzzle HTTP client. + * @param \Drupal\webform\WebformContributeManagerInterface $contribute_manager + * The webform contribute manager. */ - public function __construct(ConfigFactoryInterface $config_factory, ClientInterface $http_client) { - $this->configFactory = $config_factory; + public function __construct(ClientInterface $http_client, WebformContributeManagerInterface $contribute_manager) { $this->httpClient = $http_client; + $this->contributeManager = $contribute_manager; } /** @@ -49,8 +49,8 @@ public function __construct(ConfigFactoryInterface $config_factory, ClientInterf */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory'), - $container->get('http_client') + $container->get('http_client'), + $container->get('webform.contribute_manager') ); } @@ -64,9 +64,6 @@ public static function create(ContainerInterface $container) { * A renderable array containing a webform about page. */ public function index(Request $request) { - /** @var \Drupal\webform\WebformContributeManagerInterface $contribute_manager */ - $contribute_manager = \Drupal::service('webform.contribute_manager'); - // Message. $build['message'] = []; $build['message']['divide'] = $this->buildDivider(); @@ -79,9 +76,9 @@ public function index(Request $request) { // Community Information. $build['community_info'] = [ '#theme' => 'webform_contribute', - '#account' => $contribute_manager->getAccount(), - '#membership' => $contribute_manager->getMembership(), - '#contribution' => $contribute_manager->getContribution(), + '#account' => $this->contributeManager->getAccount(), + '#membership' => $this->contributeManager->getMembership(), + '#contribution' => $this->contributeManager->getContribution(), ]; // Drupal. @@ -211,7 +208,6 @@ public function autocomplete(Request $request, $account_type = 'user') { } } - /****************************************************************************/ // Build methods. /****************************************************************************/ @@ -263,7 +259,7 @@ protected function buildLink($title, $url, array $class = ['button']) { * A video player, linked button, or an empty array if videos are disabled. */ protected function buildVideo($youtube_id) { - $video_display = $this->configFactory->get('webform.settings')->get('ui.video_display'); + $video_display = $this->config('webform.settings')->get('ui.video_display'); switch ($video_display) { case 'dialog': return [ diff --git a/web/modules/webform/src/Controller/WebformElementController.php b/web/modules/webform/src/Controller/WebformElementController.php index 6efed57f9d..5a92435f77 100644 --- a/web/modules/webform/src/Controller/WebformElementController.php +++ b/web/modules/webform/src/Controller/WebformElementController.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Controller; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Database\Database; @@ -77,7 +76,7 @@ public function autocomplete(Request $request, WebformInterface $webform, $key) ]; // Check minimum number of characters. - if (Unicode::strlen($q) < (int) $element['#autocomplete_match']) { + if (mb_strlen($q) < (int) $element['#autocomplete_match']) { return new JsonResponse([]); } diff --git a/web/modules/webform/src/Controller/WebformEntityController.php b/web/modules/webform/src/Controller/WebformEntityController.php index b4733ccd4d..efe87b3df3 100644 --- a/web/modules/webform/src/Controller/WebformEntityController.php +++ b/web/modules/webform/src/Controller/WebformEntityController.php @@ -6,6 +6,7 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Render\RendererInterface; +use Drupal\webform\Element\Webform as WebformElement; use Drupal\webform\WebformInterface; use Drupal\webform\WebformRequestInterface; use Drupal\webform\WebformSubmissionInterface; @@ -183,11 +184,13 @@ public function confirmation(Request $request, WebformInterface $webform = NULL, * The current request. * @param bool $templates * If TRUE, limit autocomplete matches to webform templates. + * @param bool $archived + * If TRUE, limit autocomplete matches to archived webforms and templates. * * @return \Symfony\Component\HttpFoundation\JsonResponse * The JSON response. */ - public function autocomplete(Request $request, $templates = FALSE) { + public function autocomplete(Request $request, $templates = FALSE, $archived = FALSE) { $q = $request->query->get('q'); $webform_storage = $this->entityTypeManager()->getStorage('webform'); @@ -210,6 +213,9 @@ public function autocomplete(Request $request, $templates = FALSE) { $query->condition('template', FALSE); } + // Limit query to archived. + $query->condition('archive', $archived); + $entity_ids = $query->execute(); if (empty($entity_ids)) { @@ -228,6 +234,32 @@ public function autocomplete(Request $request, $templates = FALSE) { return new JsonResponse($matches); } + /** + * Returns a webform's access denied page. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return array + * A renderable array containing an access denied page. + */ + public function accessDenied(WebformInterface $webform) { + return WebformElement::buildAccessDenied($webform); + } + + /** + * Returns a webform's access denied title. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup + * The webform submissions's access denied title. + */ + public function accessDeniedTitle(WebformInterface $webform) { + return $webform->getSetting('form_access_denied_title') ?: $this->t('Access denied'); + } + /** * Route title callback. * @@ -245,7 +277,42 @@ public function title(WebformInterface $webform = NULL) { else { $source_entity = $this->requestHandler->getCurrentSourceEntity('webform'); } - return ($source_entity) ? $source_entity->label() : $webform->label(); + + // If source entity does not exist or does not have a label always use + // the webform's label. + if (!$source_entity || !method_exists($source_entity, 'label')) { + return $webform->label(); + } + + // Handler duplicate titles. + if ($source_entity->label() === $webform->label()) { + return $webform->label(); + } + + // Get the webform's title. + switch ($webform->getSetting('form_title')) { + case WebformInterface::TITLE_SOURCE_ENTITY: + return $source_entity->label(); + + case WebformInterface::TITLE_WEBFORM: + return $webform->label(); + + case WebformInterface::TITLE_WEBFORM_SOURCE_ENTITY: + $t_args = [ + '@source_entity' => $source_entity->label(), + '@webform' => $webform->label(), + ]; + return $this->t('@webform: @source_entity', $t_args); + + case WebformInterface::TITLE_SOURCE_ENTITY_WEBFORM: + default: + $t_args = [ + '@source_entity' => $source_entity->label(), + '@webform' => $webform->label(), + ]; + return $this->t('@source_entity: @webform', $t_args); + + } } } diff --git a/web/modules/webform/src/Controller/WebformHelpController.php b/web/modules/webform/src/Controller/WebformHelpController.php new file mode 100644 index 0000000000..92b9110da7 --- /dev/null +++ b/web/modules/webform/src/Controller/WebformHelpController.php @@ -0,0 +1,51 @@ +<?php + +namespace Drupal\webform\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\webform\WebformHelpManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides route responses for webform help. + */ +class WebformHelpController extends ControllerBase implements ContainerInjectionInterface { + + /** + * The webform help manager. + * + * @var \Drupal\webform\WebformHelpManagerInterface + */ + protected $help; + + /** + * Constructs a WebformHelpController object. + * + * @param \Drupal\webform\WebformHelpManagerInterface $help + * The webform help manager. + */ + public function __construct(WebformHelpManagerInterface $help) { + $this->help = $help; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.help_manager') + ); + } + + /** + * Returns the Webform extend page. + * + * @return array + * The webform submission webform. + */ + public function index() { + return $this->help->buildIndex(); + } + +} diff --git a/web/modules/webform/src/Controller/WebformOptionsController.php b/web/modules/webform/src/Controller/WebformOptionsController.php index 01a5b7b100..ed02d24094 100644 --- a/web/modules/webform/src/Controller/WebformOptionsController.php +++ b/web/modules/webform/src/Controller/WebformOptionsController.php @@ -2,80 +2,54 @@ namespace Drupal\webform\Controller; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Controller\ControllerBase; -use Drupal\webform\Entity\WebformOptions; -use Drupal\webform\WebformInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; /** - * Provides route responses for webform options. + * Provides route responses for webform options options. */ class WebformOptionsController extends ControllerBase { /** - * Returns response for the element autocompletion. + * Returns response for the webform options autocompletion. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object containing the search string. - * @param \Drupal\webform\WebformInterface $webform - * A webform. - * @param string $key - * Webform element key. * * @return \Symfony\Component\HttpFoundation\JsonResponse * A JSON response containing the autocomplete suggestions. */ - public function autocomplete(Request $request, WebformInterface $webform, $key) { + public function autocomplete(Request $request) { $q = $request->query->get('q'); - // Make sure the current user can access this webform. - if (!$webform->access('view')) { - return new JsonResponse([]); - } + $webform_options_storage = $this->entityTypeManager()->getStorage('webform_options'); - // Get the webform element element. - $elements = $webform->getElementsInitializedAndFlattened(); - if (!isset($elements[$key])) { - return new JsonResponse([]); - } + $query = $webform_options_storage->getQuery() + ->range(0, 10) + ->sort('label'); - // Get the element's webform options. - $element = $elements[$key]; - $element['#options'] = $element['#autocomplete']; - $options = WebformOptions::getElementOptions($element); - if (empty($options)) { + // Query title and id. + $or = $query->orConditionGroup() + ->condition('id', $q, 'CONTAINS') + ->condition('label', $q, 'CONTAINS'); + $query->condition($or); + + $entity_ids = $query->execute(); + + if (empty($entity_ids)) { return new JsonResponse([]); } + $webform_options = $webform_options_storage->loadMultiple($entity_ids); - // Filter and convert options to autocomplete matches. $matches = []; - $this->appendOptionsToMatchesRecursive($q, $options, $matches); - return new JsonResponse($matches); - } - - /** - * Append webform options to autocomplete matches. - * - * @param string $q - * String to filter option's label by. - * @param array $options - * An associative array of webform options. - * @param array $matches - * An associative array of autocomplete matches. - */ - protected function appendOptionsToMatchesRecursive($q, array $options, array &$matches) { - foreach ($options as $value => $label) { - if (is_array($label)) { - $this->appendOptionsToMatchesRecursive($q, $label, $matches); - } - elseif (stripos($label, $q) !== FALSE) { - $matches[] = [ - 'value' => $value, - 'label' => $label, - ]; - } + foreach ($webform_options as $webform_option) { + $value = new FormattableMarkup('@label (@id)', ['@label' => $webform_option->label(), '@id' => $webform_option->id()]); + $matches[] = ['value' => $value, 'label' => $value]; } + + return new JsonResponse($matches); } } diff --git a/web/modules/webform/src/Controller/WebformPluginElementController.php b/web/modules/webform/src/Controller/WebformPluginElementController.php index a2160a8393..375511c1df 100644 --- a/web/modules/webform/src/Controller/WebformPluginElementController.php +++ b/web/modules/webform/src/Controller/WebformPluginElementController.php @@ -3,7 +3,6 @@ namespace Drupal\webform\Controller; use Drupal\Component\Render\FormattableMarkup; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Controller\ControllerBase; @@ -18,13 +17,6 @@ */ class WebformPluginElementController extends ControllerBase implements ContainerInjectionInterface { - /** - * The config factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - /** * The module handler. * @@ -49,8 +41,6 @@ class WebformPluginElementController extends ControllerBase implements Container /** * Constructs a WebformPluginElementController object. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info @@ -58,8 +48,7 @@ class WebformPluginElementController extends ControllerBase implements Container * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager * A webform element plugin manager. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager) { - $this->configFactory = $config_factory; + public function __construct(ModuleHandlerInterface $module_handler, ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager) { $this->moduleHandler = $module_handler; $this->elementInfo = $element_info; $this->elementManager = $element_manager; @@ -70,7 +59,6 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory'), $container->get('module_handler'), $container->get('plugin.manager.element_info'), $container->get('plugin.manager.webform.element') @@ -144,7 +132,7 @@ public function index() { 'data' => [ '#type' => 'link', '#title' => $element_plugin_id, - '#url' => new Url('webform.element_plugins.test', ['type' => $element_plugin_id]), + '#url' => new Url('webform.reports_plugins.elements.test', ['type' => $element_plugin_id]), '#attributes' => ['class' => ['webform-form-filter-text-source']], ], ]; @@ -154,7 +142,19 @@ public function index() { } // Description. - $description = new FormattableMarkup('<strong>@label</strong><br />@description', ['@label' => $webform_element->getPluginLabel(), '@description' => $webform_element->getPluginDescription()]); + $description = [ + 'data' => [ + 'title_description' => ['#markup' => new FormattableMarkup('<strong>@label</strong><br />@description', ['@label' => $webform_element->getPluginLabel(), '@description' => $webform_element->getPluginDescription()])], + ], + ]; + // Add deprecated warning. + if (!empty($webform_element_plugin_definition['deprecated'])) { + $description['data']['deprecated'] = [ + '#type' => 'webform_message', + '#message_message' => $webform_element_plugin_definition['deprecated_message'], + '#message_type' => 'warning', + ]; + } // Parent classes. $parent_classes = WebformReflectionHelper::getParentClasses($webform_element, 'WebformElementBase'); @@ -180,17 +180,19 @@ public function index() { 'container' => $webform_element->isContainer($element), 'root' => $webform_element->isRoot(), 'hidden' => $webform_element->isHidden(), + 'composite' => $webform_element->isComposite(), 'multiple' => $webform_element->supportsMultipleValues(), 'multiline' => $webform_element->isMultiline($element), 'default_key' => $webform_element_plugin_definition['default_key'], 'states_wrapper' => $webform_element_plugin_definition['states_wrapper'], + 'deprecated' => $webform_element_plugin_definition['deprecated'], ]; $webform_info = []; foreach ($webform_info_definitions as $key => $value) { $webform_info[] = '<b>' . $key . '</b>: ' . ($value ? $this->t('Yes') : $this->t('No')); } - // Wlement info. + // Element info. $element_info_definitions = [ 'input' => (empty($webform_element_info['#input'])) ? $this->t('No') : $this->t('Yes'), 'theme' => (isset($webform_element_info['#theme'])) ? $webform_element_info['#theme'] : 'N/A', @@ -215,7 +217,7 @@ public function index() { } $properties += $element_default_properties; if (count($properties) >= 20) { - $properties = array_slice($properties, 0, 20) + ['...' => '...']; + $properties = array_slice($properties, 0, 20) + ['…' => '…']; } // Operations. @@ -223,7 +225,7 @@ public function index() { if ($test_element_enabled) { $operations['test'] = [ 'title' => $this->t('Test'), - 'url' => new Url('webform.element_plugins.test', ['type' => $element_plugin_id]), + 'url' => new Url('webform.reports_plugins.elements.test', ['type' => $element_plugin_id]), ]; } if ($api_url = $webform_element->getPluginApiUrl()) { @@ -246,12 +248,14 @@ public function index() { $dependencies ? ['data' => ['#markup' => '• ' . implode('<br />• ', $dependencies)], 'nowrap' => 'nowrap'] : '', $element_plugin_definition['provider'], $webform_element_plugin_definition['provider'], - $operations ? ['data' => [ - '#type' => 'operations', - '#links' => $operations, - '#prefix' => '<div class="webform-dropbutton">', - '#suffix' => '</div>', - ]] : '', + $operations ? [ + 'data' => [ + '#type' => 'operations', + '#links' => $operations, + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ], + ] : '', ], ]; if (isset($excluded_elements[$element_plugin_id])) { @@ -276,7 +280,10 @@ public function index() { '#placeholder' => $this->t('Filter by element name'), '#attributes' => [ 'class' => ['webform-form-filter-text'], - 'data-element' => '.webform-element-plugin', + 'data-element' => '.webform-element-plugin-table', + 'data-summary' => '.webform-element-plugin-summary', + 'data-item-singlular' => $this->t('element'), + 'data-item-plural' => $this->t('elements'), 'title' => $this->t('Enter a part of the element type to filter by.'), 'autofocus' => 'autofocus', ], @@ -293,7 +300,7 @@ public function index() { // Display info. $build['info'] = [ '#markup' => $this->t('@total elements', ['@total' => count($webform_form_element_rows)]), - '#prefix' => '<p>', + '#prefix' => '<p class="webform-element-plugin-summary">', '#suffix' => '</p>', ]; @@ -315,8 +322,9 @@ public function index() { $this->t('Operations'), ], '#rows' => $webform_form_element_rows, + '#sticky' => TRUE, '#attributes' => [ - 'class' => ['webform-element-plugin'], + 'class' => ['webform-element-plugin-table'], ], ]; diff --git a/web/modules/webform/src/Controller/WebformPluginExporterController.php b/web/modules/webform/src/Controller/WebformPluginExporterController.php index 6ba7953990..f6920a1122 100644 --- a/web/modules/webform/src/Controller/WebformPluginExporterController.php +++ b/web/modules/webform/src/Controller/WebformPluginExporterController.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Controller; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; @@ -14,13 +13,6 @@ */ class WebformPluginExporterController extends ControllerBase implements ContainerInjectionInterface { - /** - * The config factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - /** * A results exporter plugin manager. * @@ -31,13 +23,10 @@ class WebformPluginExporterController extends ControllerBase implements Containe /** * Constructs a WebformPluginExporterController object. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager * A results exporter plugin manager. */ - public function __construct(ConfigFactoryInterface $config_factory, PluginManagerInterface $plugin_manager) { - $this->configFactory = $config_factory; + public function __construct(PluginManagerInterface $plugin_manager) { $this->pluginManager = $plugin_manager; } @@ -46,7 +35,6 @@ public function __construct(ConfigFactoryInterface $config_factory, PluginManage */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory'), $container->get('plugin.manager.webform.exporter') ); } diff --git a/web/modules/webform/src/Controller/WebformPluginHandlerController.php b/web/modules/webform/src/Controller/WebformPluginHandlerController.php index fb0dc6583a..df999058a0 100644 --- a/web/modules/webform/src/Controller/WebformPluginHandlerController.php +++ b/web/modules/webform/src/Controller/WebformPluginHandlerController.php @@ -3,7 +3,6 @@ namespace Drupal\webform\Controller; use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Url; @@ -18,13 +17,6 @@ */ class WebformPluginHandlerController extends ControllerBase implements ContainerInjectionInterface { - /** - * The config factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - /** * A webform handler plugin manager. * @@ -33,15 +25,12 @@ class WebformPluginHandlerController extends ControllerBase implements Container protected $pluginManager; /** - * Constructs a WebformPluginHanderController object. + * Constructs a WebformPluginHandlerController object. * - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The factory for configuration objects. * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager * A webform handler plugin manager. */ - public function __construct(ConfigFactoryInterface $config_factory, PluginManagerInterface $plugin_manager) { - $this->configFactory = $config_factory; + public function __construct(PluginManagerInterface $plugin_manager) { $this->pluginManager = $plugin_manager; } @@ -50,7 +39,6 @@ public function __construct(ConfigFactoryInterface $config_factory, PluginManage */ public static function create(ContainerInterface $container) { return new static( - $container->get('config.factory'), $container->get('plugin.manager.webform.handler') ); } @@ -88,7 +76,7 @@ public function index() { $build = []; - // Settings + // Settings. $build['settings'] = [ '#type' => 'link', '#title' => $this->t('Edit configuration'), @@ -231,6 +219,8 @@ public function listHandlers(Request $request, WebformInterface $webform) { '#attributes' => [ 'class' => ['webform-form-filter-text'], 'data-element' => '.webform-handler-add-table', + 'data-item-singlular' => $this->t('handler'), + 'data-item-plural' => $this->t('handlers'), 'title' => $this->t('Enter a part of the handler name to filter by.'), 'autofocus' => 'autofocus', ], @@ -240,6 +230,7 @@ public function listHandlers(Request $request, WebformInterface $webform) { '#type' => 'table', '#header' => $headers, '#rows' => $rows, + '#sticky' => TRUE, '#empty' => $this->t('No handler available.'), '#attributes' => [ 'class' => ['webform-handler-add-table'], diff --git a/web/modules/webform/src/Controller/WebformResultsExportController.php b/web/modules/webform/src/Controller/WebformResultsExportController.php index e062c82168..4fbdfb6e22 100644 --- a/web/modules/webform/src/Controller/WebformResultsExportController.php +++ b/web/modules/webform/src/Controller/WebformResultsExportController.php @@ -95,7 +95,7 @@ public function index(Request $request) { $route_name = $this->requestHandler->getRouteName($webform, $source_entity, 'webform.results_export_file'); $route_parameters = $this->requestHandler->getRouteParameters($webform, $source_entity) + ['filename' => $query['filename']]; $file_url = Url::fromRoute($route_name, $route_parameters, ['absolute' => TRUE])->toString(); - drupal_set_message($this->t('Export creation complete. Your download should begin now. If it does not start, <a href=":href">download the file here</a>. This file may only be downloaded once.', [':href' => $file_url])); + $this->messenger()->addStatus($this->t('Export creation complete. Your download should begin now. If it does not start, <a href=":href">download the file here</a>. This file may only be downloaded once.', [':href' => $file_url])); $build['#attached']['html_head'][] = [ [ '#tag' => 'meta', @@ -110,7 +110,7 @@ public function index(Request $request) { return $build; } - elseif ($query && empty($query['ajax_form'])) { + elseif ($query && empty($query['ajax_form']) && isset($query['download'])) { $default_options = $this->submissionExporter->getDefaultExportOptions(); foreach ($query as $key => $value) { if (isset($default_options[$key]) && is_array($default_options[$key]) && is_string($value)) { @@ -181,7 +181,15 @@ public function file(Request $request, $filename) { * A response object containing the CSV file. */ public function downloadFile($file_path, $download = TRUE) { - $response = new BinaryFileResponse($file_path, 200, [], FALSE, $download ? 'attachment' : 'inline'); + $headers = []; + + // If the file is not meant to be downloaded, allow CSV files to be + // displayed as plain text. + if (!$download && preg_match('/\.csv$/', $file_path)) { + $headers['Content-Type'] = 'text/plain'; + } + + $response = new BinaryFileResponse($file_path, 200, $headers, FALSE, $download ? 'attachment' : 'inline'); $response->deleteFileAfterSend(TRUE); return $response; } @@ -255,7 +263,7 @@ public static function batchProcess(WebformInterface $webform, EntityInterface $ if (empty($context['sandbox'])) { $context['sandbox']['progress'] = 0; - $context['sandbox']['current_sid'] = 0; + $context['sandbox']['offset'] = 0; $context['sandbox']['max'] = $submission_exporter->getQuery()->count()->execute(); // Store entity ids and not the actual webform or source entity in the // $context to prevent "The container was serialized" errors. @@ -269,17 +277,16 @@ public static function batchProcess(WebformInterface $webform, EntityInterface $ // Write CSV records. $query = $submission_exporter->getQuery(); - $query->condition('sid', $context['sandbox']['current_sid'], '>'); - $query->range(0, $submission_exporter->getBatchLimit()); + $query->range($context['sandbox']['offset'], $submission_exporter->getBatchLimit()); $entity_ids = $query->execute(); $webform_submissions = WebformSubmission::loadMultiple($entity_ids); $submission_exporter->writeRecords($webform_submissions); // Track progress. $context['sandbox']['progress'] += count($webform_submissions); - $context['sandbox']['current_sid'] = ($webform_submissions) ? end($webform_submissions)->id() : 0; + $context['sandbox']['offset'] += $submission_exporter->getBatchLimit(); - $context['message'] = t('Exported @count of @total submissions...', ['@count' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]); + $context['message'] = t('Exported @count of @total submissions…', ['@count' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]); // Track finished. if ($context['sandbox']['progress'] != $context['sandbox']['max']) { @@ -323,7 +330,7 @@ public static function batchFinish($success, array $results, array $operations) @unlink($file_path); $archive_path = $submission_exporter->getArchiveFilePath(); @unlink($archive_path); - drupal_set_message(t('Finished with an error.')); + \Drupal::messenger()->addStatus(t('Finished with an error.')); } else { $submission_exporter->writeFooter(); diff --git a/web/modules/webform/src/Controller/WebformSubmissionController.php b/web/modules/webform/src/Controller/WebformSubmissionController.php index 80f733bdc0..431109d166 100644 --- a/web/modules/webform/src/Controller/WebformSubmissionController.php +++ b/web/modules/webform/src/Controller/WebformSubmissionController.php @@ -5,15 +5,69 @@ use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\HtmlCommand; -use Drupal\Core\Ajax\InvokeCommand; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Render\RendererInterface; +use Drupal\webform\Ajax\WebformAnnounceCommand; +use Drupal\webform\Element\WebformHtmlEditor; +use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform\WebformRequestInterface; +use Drupal\webform\WebformTokenManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides route responses for webform submissions. */ class WebformSubmissionController extends ControllerBase { + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * Webform request handler. + * + * @var \Drupal\webform\WebformRequestInterface + */ + protected $requestHandler; + + /** + * The webform token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + + /** + * Constructs a WebformSubmissionController object. + * + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + * @param \Drupal\webform\WebformRequestInterface $request_handler + * The webform request handler. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. + */ + public function __construct(RendererInterface $renderer, WebformRequestInterface $request_handler, WebformTokenManagerInterface $token_manager) { + $this->renderer = $renderer; + $this->requestHandler = $request_handler; + $this->tokenManager = $token_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('renderer'), + $container->get('webform.request'), + $container->get('webform.token_manager') + ); + } + /** * Toggle webform submission sticky. * @@ -27,14 +81,19 @@ public function sticky(WebformSubmissionInterface $webform_submission) { // Toggle sticky. $webform_submission->setSticky(!$webform_submission->isSticky())->save(); - // Get state. - $state = $webform_submission->isSticky() ? 'on' : 'off'; + // Get selector. + $selector = '#webform-submission-' . $webform_submission->id() . '-sticky'; $response = new AjaxResponse(); - $response->addCommand(new HtmlCommand( - '#webform-submission-' . $webform_submission->id() . '-sticky', - new FormattableMarkup('<span class="webform-icon webform-icon-sticky webform-icon-sticky--@state"></span>', ['@state' => $state]) - )); + + // Update sticky. + $response->addCommand(new HtmlCommand($selector, static::buildSticky($webform_submission))); + + // Announce sticky status. + $t_args = ['@label' => $webform_submission->label()]; + $text = $webform_submission->isSticky() ? $this->t('@label flagged/starred.', $t_args) : $this->t('@label unflagged/unstarred.', $t_args); + $response->addCommand(new WebformAnnounceCommand($text)); + return $response; } @@ -51,16 +110,104 @@ public function locked(WebformSubmissionInterface $webform_submission) { // Toggle locked. $webform_submission->setLocked(!$webform_submission->isLocked())->save(); - // Get state. - $state = $webform_submission->isLocked() ? 'on' : 'off'; - // Get selector. $selector = '#webform-submission-' . $webform_submission->id() . '-locked'; $response = new AjaxResponse(); - $response->addCommand(new HtmlCommand($selector, new FormattableMarkup('<span class="webform-icon webform-icon-lock webform-icon-locked--@state"></span>', ['@state' => $state]))); - $response->addCommand(new InvokeCommand($selector, 'trigger', ['blur'])); + + // Update lock. + $response->addCommand(new HtmlCommand($selector, static::buildLocked($webform_submission))); + + // Announce lock status. + $t_args = ['@label' => $webform_submission->label()]; + $text = $webform_submission->isLocked() ? $this->t('@label locked.', $t_args) : $this->t('@label unlocked.', $t_args); + $response->addCommand(new WebformAnnounceCommand($text)); return $response; } + /** + * Build sticky icon. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return \Drupal\Component\Render\FormattableMarkup + * Sticky icon. + */ + public static function buildSticky(WebformSubmissionInterface $webform_submission) { + $t_args = ['@label' => $webform_submission->label()]; + $args = [ + '@state' => $webform_submission->isSticky() ? 'on' : 'off', + '@label' => $webform_submission->isSticky() ? t('Unstar/Unflag @label', $t_args) : t('Star/flag @label', $t_args), + ]; + return new FormattableMarkup('<span class="webform-icon webform-icon-sticky webform-icon-sticky--@state"></span><span class="visually-hidden">@label</span>', $args); + } + + /** + * Build locked icon. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return \Drupal\Component\Render\FormattableMarkup + * Locked icon. + */ + public static function buildLocked(WebformSubmissionInterface $webform_submission) { + $t_args = ['@label' => $webform_submission->label()]; + $args = [ + '@state' => $webform_submission->isLocked() ? 'on' : 'off', + '@label' => $webform_submission->isLocked() ? t('Unlock @label', $t_args) : t('Lock @label', $t_args), + ]; + return new FormattableMarkup('<span class="webform-icon webform-icon-lock webform-icon-locked--@state"></span><span class="visually-hidden">@label</span>', $args); + } + + /** + * Returns a webform submissions's access denied page. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return array + * A renderable array containing an access denied page. + */ + public function accessDenied(WebformInterface $webform, WebformSubmissionInterface $webform_submission) { + // Message. + $config = $this->config('webform.settings'); + $message = $webform->getSetting('submission_access_denied_message') + ?: $config->get('settings.default_submission_access_denied_message'); + $message = $this->tokenManager->replace($message, $webform_submission); + + // Attributes. + $attributes = $webform->getSetting('submission_access_denied_attributes'); + $attributes['class'][] = 'webform-submission-access-denied'; + + // Build message. + $build = [ + '#type' => 'container', + '#attributes' => $attributes, + 'message' => WebformHtmlEditor::checkMarkup($message), + ]; + + // Add config and webform to cache contexts. + $this->renderer->addCacheableDependency($build, $config); + $this->renderer->addCacheableDependency($build, $webform); + + return $build; + } + + /** + * Returns a webform 's access denied title. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup + * The webform's access denied title. + */ + public function accessDeniedTitle(WebformInterface $webform) { + return $webform->getSetting('submission_access_denied_title') ?: $this->t('Access denied'); + } + } diff --git a/web/modules/webform/src/Controller/WebformSubmissionViewController.php b/web/modules/webform/src/Controller/WebformSubmissionViewController.php index 7120296e9b..baeaf69e94 100644 --- a/web/modules/webform/src/Controller/WebformSubmissionViewController.php +++ b/web/modules/webform/src/Controller/WebformSubmissionViewController.php @@ -30,7 +30,7 @@ class WebformSubmissionViewController extends EntityViewController { * * @var \Drupal\webform\WebformRequestInterface */ - protected $requestManager; + protected $requestHandler; /** * Creates an WebformSubmissionViewController object. @@ -47,7 +47,7 @@ class WebformSubmissionViewController extends EntityViewController { public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AccountInterface $current_user, WebformRequestInterface $webform_request) { parent::__construct($entity_manager, $renderer); $this->currentUser = $current_user; - $this->requestManager = $webform_request; + $this->requestHandler = $webform_request; } /** @@ -66,18 +66,25 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function view(EntityInterface $webform_submission, $view_mode = 'default', $langcode = NULL) { - $webform = $this->requestManager->getCurrentWebform(); - $source_entity = $this->requestManager->getCurrentSourceEntity('webform_submission'); + $webform = $this->requestHandler->getCurrentWebform(); + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform_submission'); + + // Set webform submission template. + $build = [ + '#theme' => 'webform_submission', + '#view_mode' => $view_mode, + '#webform_submission' => $webform_submission, + ]; // Navigation. $build['navigation'] = [ - '#theme' => 'webform_submission_navigation', + '#type' => 'webform_submission_navigation', '#webform_submission' => $webform_submission, ]; // Information. $build['information'] = [ - '#theme' => 'webform_submission_information', + '#type' => 'webform_submission_information', '#webform_submission' => $webform_submission, '#source_entity' => $source_entity, ]; diff --git a/web/modules/webform/src/Controller/WebformSubmissionsController.php b/web/modules/webform/src/Controller/WebformSubmissionsController.php new file mode 100644 index 0000000000..8ca03579cd --- /dev/null +++ b/web/modules/webform/src/Controller/WebformSubmissionsController.php @@ -0,0 +1,71 @@ +<?php + +namespace Drupal\webform\Controller; + +use Drupal\Component\Utility\Html; +use Drupal\Core\Controller\ControllerBase; +use Drupal\webform\WebformInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * Provides route responses for webform submissions. + */ +class WebformSubmissionsController extends ControllerBase { + + /** + * Returns response for the source entity autocompletion. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request object containing the search string. + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * A JSON response containing the autocomplete suggestions. + */ + public function sourceEntityAutocomplete(Request $request, WebformInterface $webform) { + $match = $request->query->get('q'); + + $webform_submission_storage = $this->entityTypeManager()->getStorage('webform_submission'); + $source_entities = $webform_submission_storage->getSourceEntities($webform); + $matches = []; + + // @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection::buildEntityQuery + foreach ($source_entities as $source_entity_type => $source_entity_ids) { + $definition = $this->entityTypeManager()->getDefinition($source_entity_type); + $storage = $this->entityTypeManager()->getStorage($source_entity_type); + + if (empty($definition->getKey('id')) || empty($definition->getKey('label'))) { + continue; + } + + $query = $storage->getQuery(); + $query->range(0, 10); + $query->condition($definition->getKey('id'), $source_entity_ids, 'IN'); + $query->condition($query->orConditionGroup() + ->condition($definition->getKey('label'), $match, 'CONTAINS') + ->condition($definition->getKey('id'), $match, 'CONTAINS') + ); + $query->addTag($source_entity_type . '_access'); + $entity_ids = $query->execute(); + + $entities = $storage->loadMultiple($entity_ids); + foreach ($entities as $source_entity_id => $source_entity) { + $label = Html::escape($this->entityManager()->getTranslationFromContext($source_entity)->label()); + $value = "$label ($source_entity_type:$source_entity_id)"; + $matches[] = [ + 'value' => $value, + 'label' => $label, + ]; + + if (count($matches) === 10) { + new JsonResponse($matches); + } + } + } + + return new JsonResponse($matches); + } + +} diff --git a/web/modules/webform/src/Controller/WebformTestController.php b/web/modules/webform/src/Controller/WebformTestController.php index f89450898b..8e08f31681 100644 --- a/web/modules/webform/src/Controller/WebformTestController.php +++ b/web/modules/webform/src/Controller/WebformTestController.php @@ -4,17 +4,27 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\webform\Plugin\WebformHandler\EmailWebformHandler; use Drupal\webform\WebformInterface; use Drupal\webform\WebformRequestInterface; use Drupal\webform\WebformSubmissionGenerateInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * Provides route responses for webform testing. */ class WebformTestController extends ControllerBase implements ContainerInjectionInterface { + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Webform request handler. * @@ -32,12 +42,15 @@ class WebformTestController extends ControllerBase implements ContainerInjection /** * Constructs a WebformTestController object. * + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. * @param \Drupal\webform\WebformRequestInterface $request_handler * The webform request handler. * @param \Drupal\webform\WebformSubmissionGenerateInterface $submission_generate * The webform submission generation service. */ - public function __construct(WebformRequestInterface $request_handler, WebformSubmissionGenerateInterface $submission_generate) { + public function __construct(MessengerInterface $messenger, WebformRequestInterface $request_handler, WebformSubmissionGenerateInterface $submission_generate) { + $this->messenger = $messenger; $this->requestHandler = $request_handler; $this->generate = $submission_generate; } @@ -47,6 +60,7 @@ public function __construct(WebformRequestInterface $request_handler, WebformSub */ public static function create(ContainerInterface $container) { return new static( + $container->get('messenger'), $container->get('webform.request'), $container->get('webform_submission.generate') ); @@ -65,6 +79,45 @@ public function testForm(Request $request) { /** @var \Drupal\webform\WebformInterface $webform */ /** @var \Drupal\Core\Entity\EntityInterface $source_entity */ list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + + // Test a single webform handler which is set via + // ?_webform_handler={handler_id}. + $test_webform_handler = $request->query->get('_webform_handler'); + if ($test_webform_handler) { + // Make sure the handler exists. + if (!$webform->getHandlers()->has($test_webform_handler)) { + $t_args = [ + '%webform' => $webform->label(), + '%handler' => $test_webform_handler, + ]; + $this->messenger->addWarning($this->t('The %handler email/handler for the %webform webform does not exist.', $t_args)); + throw new AccessDeniedHttpException(); + } + + // Enable only the selected handler for testing + // and disable all other handlers. + $handlers = $webform->getHandlers(); + foreach ($handlers as $handler_id => $handler) { + if ($handler_id === $test_webform_handler) { + $handler->setStatus(TRUE); + $t_args = [ + '%webform' => $webform->label(), + '%handler' => $handler->label(), + '@type' => ($handler instanceof EmailWebformHandler) ? $this->t('email') : $this->t('handler'), + ]; + $this->messenger->addWarning($this->t('Testing the %webform webform %handler @type. <strong>All other emails/handlers are disabled.</strong>', $t_args)); + } + else { + $handler->setStatus(FALSE); + } + } + + // Set override to prevent the webform's altered handler statuses + // from being saved. + $webform->setOverride(TRUE); + } + + // Set values. $values = []; // Set source entity type and id. diff --git a/web/modules/webform/src/Element/Webform.php b/web/modules/webform/src/Element/Webform.php index dbe34d9729..6ec769fbb5 100644 --- a/web/modules/webform/src/Element/Webform.php +++ b/web/modules/webform/src/Element/Webform.php @@ -2,6 +2,7 @@ namespace Drupal\webform\Element; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Render\Element\RenderElement; use Drupal\webform\Entity\Webform as WebformEntity; use Drupal\webform\WebformInterface; @@ -24,7 +25,7 @@ public function getInfo() { ], '#webform' => NULL, '#default_data' => [], - '#cache' => ['max-age' => 0], + '#action' => NULL, ]; } @@ -33,13 +34,84 @@ public function getInfo() { */ public static function preRenderWebformElement($element) { $webform = ($element['#webform'] instanceof WebformInterface) ? $element['#webform'] : WebformEntity::load($element['#webform']); - if (!$webform || !$webform->access('submission_create')) { + if (!$webform) { return $element; } - $values = ['data' => $element['#default_data']]; - $element['webform_build'] = $webform->getSubmissionForm($values); + if ($webform->access('submission_create')) { + $values = []; + + // Set data. + $values['data'] = $element['#default_data']; + + // Set source entity type and id. + if (!empty($element['#entity']) && $element['#entity'] instanceof EntityInterface) { + $values['entity_type'] = $element['#entity']->getEntityTypeId(); + $values['entity_id'] = $element['#entity']->id(); + } + elseif (!empty($element['#entity_type']) && !empty($element['#entity_id'])) { + $values['entity_type'] = $element['#entity_type']; + $values['entity_id'] = $element['#entity_id']; + } + + // Build the webform. + $element['webform_build'] = $webform->getSubmissionForm($values); + + // Set custom form action. + if (!empty($element['#action'])) { + $element['webform_build']['#action'] = $element['#action']; + } + } + elseif ($webform->getSetting('form_access_denied') !== WebformInterface::ACCESS_DENIED_DEFAULT) { + // Set access denied message. + $element['webform_access_denied'] = static::buildAccessDenied($webform); + } + else { + // Add config and webform to cache contexts. + $config = \Drupal::configFactory()->get('webform.settings'); + $renderer = \Drupal::service('renderer'); + $renderer->addCacheableDependency($element, $config); + $renderer->addCacheableDependency($element, $webform); + } + return $element; } + /** + * Build access denied message for a webform. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return array + * A renderable array containing thea ccess denied message for a webform. + */ + public static function buildAccessDenied(WebformInterface $webform) { + /** @var \Drupal\webform\WebformTokenManagerInterface $webform_token_manager */ + $webform_token_manager = \Drupal::service('webform.token_manager'); + + // Message. + $config = \Drupal::configFactory()->get('webform.settings'); + $message = $webform->getSetting('form_access_denied_message') + ?: $config->get('settings.default_form_access_denied_message'); + $message = $webform_token_manager->replace($message, $webform); + + // Attributes. + $attributes = $webform->getSetting('form_access_denied_attributes'); + $attributes['class'][] = 'webform-access-denied'; + + $build = [ + '#type' => 'container', + '#attributes' => $attributes, + 'message' => WebformHtmlEditor::checkMarkup($message), + ]; + + // Add config and webform to cache contexts. + $renderer = \Drupal::service('renderer'); + $renderer->addCacheableDependency($build, $config); + $renderer->addCacheableDependency($build, $webform); + + return $build; + } + } diff --git a/web/modules/webform/src/Element/WebformActions.php b/web/modules/webform/src/Element/WebformActions.php index 53d63f3c9a..a6d31a471d 100644 --- a/web/modules/webform/src/Element/WebformActions.php +++ b/web/modules/webform/src/Element/WebformActions.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\Container; +use Drupal\webform\Utility\WebformElementHelper; /** * Provides a wrapper element to group one or more Webform buttons in a form. @@ -62,7 +63,7 @@ public static function processWebformActions(&$element, FormStateInterface $form $prefix = ($element['#webform_key']) ? 'edit-' . $element['#webform_key'] . '-' : ''; // Add class names only if form['actions']['#type'] is set to 'actions'. - if (isset($complete_form['actions']['#type']) && $complete_form['actions']['#type'] == 'actions') { + if (WebformElementHelper::isType($complete_form['actions'], 'actions')) { $element['#attributes']['class'][] = 'form-actions'; $element['#attributes']['class'][] = 'webform-actions'; } diff --git a/web/modules/webform/src/Element/WebformAddress.php b/web/modules/webform/src/Element/WebformAddress.php index 8710c8af9e..30d4a2f43f 100644 --- a/web/modules/webform/src/Element/WebformAddress.php +++ b/web/modules/webform/src/Element/WebformAddress.php @@ -43,7 +43,7 @@ public static function getCompositeElements(array $element) { ]; $elements['postal_code'] = [ '#type' => 'textfield', - '#title' => t('Zip/Postal Code'), + '#title' => t('ZIP/Postal Code'), ]; // Any webform options prefixed with 'country' will automatically // be included within the Composite Element UI. diff --git a/web/modules/webform/src/Element/WebformButtons.php b/web/modules/webform/src/Element/WebformButtons.php index 2ea08207cb..cf3a5398e1 100644 --- a/web/modules/webform/src/Element/WebformButtons.php +++ b/web/modules/webform/src/Element/WebformButtons.php @@ -22,14 +22,7 @@ public static function processRadios(&$element, FormStateInterface $form_state, $element['#attributes']['class'][] = 'webform-buttons'; $element['#options_display'] = 'side_by_side'; - if (floatval(\Drupal::VERSION) < 8.4) { - // Buttonset is deprecated jQueryUI 1.12 - // https://api.jqueryui.com/buttonset/ - $element['#attached']['library'][] = 'webform/webform.element.buttons.buttonset'; - } - else { - $element['#attached']['library'][] = 'webform/webform.element.buttons.checkboxradio'; - } + $element['#attached']['library'][] = 'webform/webform.element.buttons'; return $element; } diff --git a/web/modules/webform/src/Element/WebformButtonsOther.php b/web/modules/webform/src/Element/WebformButtonsOther.php index ca2af45d85..086beaba1c 100644 --- a/web/modules/webform/src/Element/WebformButtonsOther.php +++ b/web/modules/webform/src/Element/WebformButtonsOther.php @@ -25,13 +25,8 @@ class WebformButtonsOther extends WebformOtherBase { */ public static function processWebformOther(&$element, FormStateInterface $form_state, &$complete_form) { // Attach element buttons before other handler. - // @see \Drupal\webform\Element\WebformButtons::processRadios - if (floatval(\Drupal::VERSION) < 8.4) { - $element['#attached']['library'][] = 'webform/webform.element.buttons.buttonset'; - } - else { - $element['#attached']['library'][] = 'webform/webform.element.buttons.checkboxradio'; - } + $element['#attached']['library'][] = 'webform/webform.element.buttons'; + $element['#wrapper_attributes']['class'][] = "js-webform-buttons"; $element['#wrapper_attributes']['class'][] = "webform-buttons"; diff --git a/web/modules/webform/src/Element/WebformCodeMirror.php b/web/modules/webform/src/Element/WebformCodeMirror.php index ed4030d9f1..466456d8cb 100644 --- a/web/modules/webform/src/Element/WebformCodeMirror.php +++ b/web/modules/webform/src/Element/WebformCodeMirror.php @@ -5,6 +5,7 @@ use Drupal\Core\Serialization\Yaml; use Drupal\Core\Render\Element\Textarea; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Twig\TwigExtension; use Drupal\webform\Utility\WebformYaml; @@ -47,6 +48,7 @@ public function getInfo() { '#skip_validation' => FALSE, '#cols' => 60, '#rows' => 5, + '#wrap' => TRUE, '#resizable' => 'vertical', '#process' => [ [$class, 'processWebformCodeMirror'], @@ -69,7 +71,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form if ($input === FALSE && $element['#mode'] == 'yaml' && isset($element['#default_value'])) { // Convert associative array in default value to YAML. if (is_array($element['#default_value'])) { - $element['#default_value'] = WebformYaml::tidy(Yaml::encode($element['#default_value'])); + $element['#default_value'] = WebformYaml::encode($element['#default_value']); } // Convert empty YAML into an empty string. if ($element['#default_value'] == '{ }') { @@ -102,6 +104,11 @@ public static function processWebformCodeMirror(&$element, FormStateInterface $f } } + // Set wrap off. + if (empty($element['#wrap'])) { + $element['#attributes']['wrap'] = 'off'; + } + // Add validate callback. $element += ['#element_validate' => []]; array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformCodeMirror']); @@ -175,51 +182,13 @@ protected static function getErrors(&$element, FormStateInterface $form_state, & switch ($element['#mode']) { case 'html': - // @see: http://stackoverflow.com/questions/3167074/which-function-in-php-validate-if-the-string-is-valid-html - // @see: http://stackoverflow.com/questions/5030392/x-html-validator-in-php - libxml_use_internal_errors(TRUE); - if (simplexml_load_string('<fragment>' . $element['#value'] . '</fragment>')) { - return NULL; - } - - $errors = libxml_get_errors(); - libxml_clear_errors(); - if (!$errors) { - return NULL; - } - - $messages = []; - foreach ($errors as $error) { - $messages[] = $error->message; - } - return $messages; + return static::validateHtml($element, $form_state, $complete_form); case 'yaml': - try { - $value = $element['#value']; - $data = Yaml::decode($value); - if (!is_array($data) && $value) { - throw new \Exception(t('YAML must contain an associative array of elements.')); - } - return NULL; - } - catch (\Exception $exception) { - return [$exception->getMessage()]; - } + return static::validateYaml($element, $form_state, $complete_form); case 'twig': - try { - $build = [ - '#type' => 'inline_template', - '#template' => $element['#value'], - '#context' => [], - ]; - \Drupal::service('renderer')->renderPlain($build); - return NULL; - } - catch (\Exception $exception) { - return [$exception->getMessage()]; - } + return static::validateTwig($element, $form_state, $complete_form); default: return NULL; @@ -239,4 +208,119 @@ public static function getMode($mode) { return (isset(static::$modes[$mode])) ? static::$modes[$mode] : static::$modes['text']; } + /****************************************************************************/ + // Language/markup validation callback. + /****************************************************************************/ + + /** + * Validate HTML. + * + * @param array $element + * The form element whose value is being validated. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array|null + * An array of error messages. + */ + protected static function validateHtml($element, FormStateInterface $form_state, $complete_form) { + // @see: http://stackoverflow.com/questions/3167074/which-function-in-php-validate-if-the-string-is-valid-html + // @see: http://stackoverflow.com/questions/5030392/x-html-validator-in-php + libxml_use_internal_errors(TRUE); + if (simplexml_load_string('<fragment>' . $element['#value'] . '</fragment>')) { + return NULL; + } + + $errors = libxml_get_errors(); + libxml_clear_errors(); + if (!$errors) { + return NULL; + } + + $messages = []; + foreach ($errors as $error) { + $messages[] = $error->message; + } + return $messages; + } + + /** + * Validate Twig. + * + * @param array $element + * The form element whose value is being validated. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array|null + * An array of error messages. + */ + protected static function validateTwig($element, FormStateInterface $form_state, $complete_form) { + $template = $element['#value']; + $form_object = $form_state->getFormObject(); + try { + // If form object has ::getWebform method validate Twig template + // using a temporary webform submission context. + if (method_exists($form_object, 'getWebform')) { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $form_object->getWebform(); + + // Get a temporary webform submission. + /** @var \Drupal\webform\WebformSubmissionGenerateInterface $webform_submission_generate */ + $webform_submission_generate = \Drupal::service('webform_submission.generate'); + $values = [ + // Set sid to 0 to prevent validation errors. + 'sid' => 0, + 'webform_id' => $webform->id(), + 'data' => $webform_submission_generate->getData($webform), + ]; + $webform_submission = WebformSubmission::create($values); + $build = TwigExtension::buildTwigTemplate($webform_submission, $template, []); + } + else { + $build = [ + '#type' => 'inline_template', + '#template' => $element['#value'], + '#context' => [], + ]; + } + \Drupal::service('renderer')->renderPlain($build); + return NULL; + } + catch (\Exception $exception) { + return [$exception->getMessage()]; + } + } + + /** + * Validate YAML. + * + * @param array $element + * The form element whose value is being validated. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array|null + * An array of error messages. + */ + protected static function validateYaml($element, FormStateInterface $form_state, $complete_form) { + try { + $value = $element['#value']; + $data = Yaml::decode($value); + if (!is_array($data) && $value) { + throw new \Exception(t('YAML must contain an associative array of elements.')); + } + return NULL; + } + catch (\Exception $exception) { + return [$exception->getMessage()]; + } + } + } diff --git a/web/modules/webform/src/Element/WebformCompositeBase.php b/web/modules/webform/src/Element/WebformCompositeBase.php index 1207c6541a..9465ec443c 100644 --- a/web/modules/webform/src/Element/WebformCompositeBase.php +++ b/web/modules/webform/src/Element/WebformCompositeBase.php @@ -6,7 +6,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\FormElement; -use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase as WebformManagedFileBasePlugin; use Drupal\webform\Utility\WebformElementHelper; /** @@ -14,6 +13,8 @@ */ abstract class WebformCompositeBase extends FormElement implements WebformCompositeInterface { + use WebformCompositeFormElementTrait; + /** * {@inheritdoc} */ @@ -27,7 +28,7 @@ public function getInfo() { [$class, 'processAjaxForm'], ], '#pre_render' => [ - [$class, 'preRenderCompositeFormElement'], + [$class, 'preRenderWebformCompositeFormElement'], ], '#title_display' => 'invisible', '#required' => FALSE, @@ -213,13 +214,9 @@ public static function initializeCompositeElements(array &$element) { * * @param array $element * A render array for the current element. - * @param array $element + * @param array $composite_elements * A render array containing a composite's elements. * - * @return array - * A renderable array of webform elements, containing the base properties - * for the composite's webform elements. - * * @throws \Exception * Throws exception when unsupported element type is used with a composite * element. @@ -257,13 +254,12 @@ protected static function initializeCompositeElementsRecursive(array &$element, $composite_element['#empty_option'] = $composite_element['#placeholder']; } - // Note: File uploads are not supported because uploaded file - // destination save and delete callbacks are not setup. - // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::postSave - // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::postDelete - if ($element_plugin instanceof WebformManagedFileBasePlugin) { - throw new \Exception('File upload element is not supported within composite elements.'); + // Apply #select2 and #chosen to select elements. + if (isset($composite_element['#type']) && strpos($composite_element['#type'], 'select') !== FALSE) { + $select_properties = ['#select2' => '#select2', '#chosen' => '#chosen']; + $composite_element += array_intersect_key($element, $select_properties); } + if ($element_plugin->hasMultipleValues($composite_element)) { throw new \Exception('Multiple elements are not supported within composite elements.'); } diff --git a/web/modules/webform/src/Element/WebformCompositeFormElementTrait.php b/web/modules/webform/src/Element/WebformCompositeFormElementTrait.php new file mode 100644 index 0000000000..9dcb459252 --- /dev/null +++ b/web/modules/webform/src/Element/WebformCompositeFormElementTrait.php @@ -0,0 +1,124 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Render\Element; + +/** + * Provides a trait for webform composite form elements. + * + * Any form element that is comprised of several distinct parts can use this + * trait to add support for a composite title or description. + * + * The Webform overrides any element that is using the CompositeFormElementTrait + * and applies the below pre renderer which adds support for + * #wrapper_attributes and additional some classes. + * + * @see \Drupal\Core\Render\Element\CompositeFormElementTrait + * @see \Drupal\webform\Plugin\WebformElementBase::prepareCompositeFormElement + */ +trait WebformCompositeFormElementTrait { + + /** + * Adds form element theming to an element if its title or description is set. + * + * This is used as a pre render function for checkboxes and radios. + */ + public static function preRenderWebformCompositeFormElement($element) { + $has_content = (isset($element['#title']) || isset($element['#description'])); + if (!$has_content) { + return $element; + } + + // Set attributes. + if (!isset($element['#attributes'])) { + $element['#attributes'] = []; + } + + // Apply wrapper attributes to attributes. + if (isset($element['#wrapper_attributes'])) { + $element['#attributes'] = NestedArray::mergeDeep($element['#attributes'], $element['#wrapper_attributes']); + } + + // Set id and classes. + if (!isset($element['#attributes']['id'])) { + $element['#attributes']['id'] = $element['#id'] . '--wrapper'; + } + $element['#attributes']['class'][] = Html::getClass($element['#type']) . '--wrapper'; + $element['#attributes']['class'][] = 'fieldgroup'; + $element['#attributes']['class'][] = 'form-composite'; + + // Add composite library. + $element['#attached']['library'][] = 'webform/webform.composite'; + + // Set theme wrapper to wrapper type. + $wrapper_type = (isset($element['#wrapper_type'])) ? $element['#wrapper_type'] : 'fieldset'; + $element['#theme_wrappers'][] = $wrapper_type; + + // Apply wrapper specific enhancements. + switch ($wrapper_type) { + case 'fieldset': + // Set the element's title attribute to show #title as a tooltip, if needed. + if (isset($element['#title']) && $element['#title_display'] == 'attribute') { + $element['#attributes']['title'] = $element['#title']; + if (!empty($element['#required'])) { + // Append an indication that this fieldset is required. + $element['#attributes']['title'] .= ' (' . t('Required') . ')'; + } + } + + // Add hidden and visible title class to fix composite fieldset + // top/bottom margins. + if (isset($element['#title'])) { + if (!empty($element['#title_display']) && in_array($element['#title_display'], ['invisible', 'attribute'])) { + $element['#attributes']['class'][] = 'webform-composite-hidden-title'; + } + else { + $element['#attributes']['class'][] = 'webform-composite-visible-title'; + } + } + break; + + case 'form_element': + // Process #states for #wrapper_attributes. + // @see template_preprocess_form_element(). + webform_process_states($element, '#wrapper_attributes'); + break; + } + + // Issue #3007132: [accessibility] Radios and checkboxes the WAI-ARIA + // 'aria-describedby' attribute has a reference to an ID that does not + // exist or an ID that is not unique + // https://www.drupal.org/project/webform/issues/3007132 + // @see \Drupal\Core\Form\FormBuilder::doBuildForm + if (!empty($element['#description'])) { + $fix_aria_describedby = (preg_match('/^(?:webform_)?(?:radios|checkboxes|buttons)(?:_other)?$/', $element['#type'])); + foreach (Element::children($element) as $key) { + // Skip if child element has a dedicated description. + if (!empty($element[$key]['#description'])) { + continue; + } + + // Skip if 'aria-describedby' is not set. + if (empty($element[$key]['#attributes']['aria-describedby'])) { + continue; + } + + // Only fix 'aria-describedby' attribute if it pointing to a broken id. + if ($element[$key]['#attributes']['aria-describedby'] === $element['#id'] . '--description') { + if ($fix_aria_describedby) { + $element[$key]['#attributes']['aria-describedby'] = $element['#attributes']['id'] . '--description'; + } + else { + unset($element[$key]['#attributes']['aria-describedby']); + } + } + } + } + + return $element; + } + +} diff --git a/web/modules/webform/src/Element/WebformComputedBase.php b/web/modules/webform/src/Element/WebformComputedBase.php index c8fe2111cd..45ad89a5e2 100644 --- a/web/modules/webform/src/Element/WebformComputedBase.php +++ b/web/modules/webform/src/Element/WebformComputedBase.php @@ -2,10 +2,14 @@ namespace Drupal\webform\Element; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; +use Drupal\Core\Template\Attribute; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Utility\WebformHtmlHelper; +use Drupal\webform\Utility\WebformXss; use Drupal\webform\WebformSubmissionForm; use Drupal\webform\WebformSubmissionInterface; @@ -35,6 +39,13 @@ abstract class WebformComputedBase extends FormElement { */ const MODE_AUTO = 'auto'; + /** + * Cache of submissions being processed. + * + * @var array + */ + protected static $submissions = []; + /** * {@inheritdoc} */ @@ -45,8 +56,13 @@ public function getInfo() { [$class, 'processWebformComputed'], ], '#input' => TRUE, - '#value' => '', + '#template' => '', '#mode' => NULL, + '#hide_empty' => FALSE, + // Note: Computed elements do not use the default #ajax wrapper, which is + // why we can use #ajax as a boolean. + // @see \Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm + '#ajax' => FALSE, '#webform_submission' => NULL, '#theme_wrappers' => ['form_element'], ]; @@ -66,31 +82,80 @@ public function getInfo() { * The processed element. */ public static function processWebformComputed(&$element, FormStateInterface $form_state, &$complete_form) { - $webform_submission = static::getWebformSubmission($element, $form_state); + $webform_submission = static::getWebformSubmission($element, $form_state, $complete_form); if ($webform_submission) { - $value = static::processValue($element, $webform_submission);; - - // Display markup. - $element['value']['#markup'] = $value; - - // Include hidden element so that computed value will be available to - // conditions (#states). + // Set tree. $element['#tree'] = TRUE; - $element['hidden'] = [ - '#type' => 'hidden', - '#value' => ['#markup' => $value], - '#parents' => $element['#parents'], - ]; // Set #type to item to trigger #states behavior. // @see drupal_process_states; $element['#type'] = 'item'; + + $value = static::processValue($element, $webform_submission); + static::setWebformComputedElementValue($element, $value); } if (!empty($element['#states'])) { webform_process_states($element, '#wrapper_attributes'); } + // Add validate callback. + $element += ['#element_validate' => []]; + array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformComputed']); + + /**************************************************************************/ + // Ajax support + /**************************************************************************/ + + // Enabled Ajax support only for computed elements associated with a + // webform submission form. + if ($element['#ajax'] && $form_state->getFormObject() instanceof WebformSubmissionForm) { + // Get button name and wrapper id. + $button_name = 'webform-computed-' . implode('-', $element['#parents']) . '-button'; + $wrapper_id = 'webform-computed-' . implode('-', $element['#parents']) . '-wrapper'; + + // Get computed value element keys which are used to trigger Ajax updates. + preg_match_all('/(?:\[webform_submission:values:|data\.)([_a-z]+)/', $element['#template'], $matches); + $element_keys = $matches[1] ?: []; + $element_keys = array_unique($element_keys); + + // Wrapping the computed element is two div tags. + // div.js-webform-computed is used to initialize the Ajax updates. + // div#wrapper_id is used to display response from the Ajax updates. + $element['#wrapper_id'] = $wrapper_id; + $element['#prefix'] = '<div class="js-webform-computed" data-webform-element-keys="' . implode(',', $element_keys) . '">' . + '<div class="js-webform-computed-wrapper" id="' . $wrapper_id . '">'; + $element['#suffix'] = '</div></div>'; + + // Add hidden update button. + $element['update'] = [ + '#type' => 'submit', + '#value' => t('Update'), + '#validate' => [[get_called_class(), 'validateWebformComputedCallback']], + '#submit' => [[get_called_class(), 'submitWebformComputedCallback']], + '#ajax' => [ + 'callback' => [get_called_class(), 'ajaxWebformComputedCallback'], + 'wrapper' => $wrapper_id, + 'progress' => ['type' => 'none'], + ], + // Disable validation, hide button, add submit button trigger class. + '#attributes' => [ + 'formnovalidate' => 'formnovalidate', + 'class' => [ + 'js-hide', + 'js-webform-computed-submit', + ], + ], + // Issue #1342066 Document that buttons with the same #value need a unique + // #name for the Form API to distinguish them, or change the Form API to + // assign unique #names automatically. + '#name' => $button_name, + ]; + + // Attached computed element Ajax library. + $element['#attached']['library'][] = 'webform/webform.element.computed'; + } + return $element; } @@ -106,9 +171,151 @@ public static function processWebformComputed(&$element, FormStateInterface $for * The string with tokens replaced. */ public static function processValue(array $element, WebformSubmissionInterface $webform_submission) { - return $element['#value']; + return $element['#template']; } + /** + * Validates an computed element. + */ + public static function validateWebformComputed(&$element, FormStateInterface $form_state, &$complete_form) { + // Make sure the form's state value uses the computed value and not the + // raw #value. This ensures conditional handlers are triggered using + // the accurate computed value. + $webform_submission = static::getWebformSubmission($element, $form_state, $complete_form); + if ($webform_submission) { + $value = static::processValue($element, $webform_submission); + $form_state->setValueForElement($element['value'], NULL); + $form_state->setValueForElement($element['hidden'], NULL); + $form_state->setValueForElement($element, $value); + } + } + + /****************************************************************************/ + // Form/Ajax callbacks. + /****************************************************************************/ + + /** + * Set computed element's value. + * + * @param array $element + * A computed element. + * @param string $value + * A computer value. + */ + protected static function setWebformComputedElementValue(array &$element, $value) { + // Hide empty computed element using display:none so that #states API + // can still use the empty computed value. + if ($element['#hide_empty']) { + $element += ['#wrapper_attributes' => []]; + $element['#wrapper_attributes'] += ['style' => '']; + if ($value === '') { + $element['#wrapper_attributes']['style'] .= ($element['#wrapper_attributes']['style'] ? ';' : '') . 'display:none'; + } + else { + $element['#wrapper_attributes']['style'] = preg_replace('/;?display:none/', '', $element['#wrapper_attributes']['style']); + } + } + + // Display markup. + $element['value']['#markup'] = $value; + $element['value']['#allowed_tags'] = WebformXss::getAdminTagList(); + + // Include hidden element so that computed value will be available to + // conditions (#states). + $element['hidden']['#type'] = 'hidden'; + $element['hidden']['#value'] = ['#markup' => $value]; + $element['hidden']['#parents'] = $element['#parents']; + } + + /** + * Determine if the current request is using Ajax. + */ + protected static function isAjax() { + return (\Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_ajax'); + } + + /** + * Webform computed element validate callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public static function validateWebformComputedCallback(array $form, FormStateInterface $form_state) { + $form_state->clearErrors(); + } + + /** + * Webform computed element submit callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public static function submitWebformComputedCallback(array $form, FormStateInterface $form_state) { + // Only rebuild if the request is not using Ajax. + if (!static::isAjax()) { + $form_state->setRebuild(); + } + } + + /** + * Webform computed element Ajax callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The computed element element. + */ + public static function ajaxWebformComputedCallback(array $form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); + + // Set element value and #markup after the form has been validated. + $webform_submission = static::getWebformSubmission($element, $form_state, $form); + $value = static::processValue($element, $webform_submission); + static::setWebformComputedElementValue($element, $value); + + // Only return the wrapper id, this prevents the computed element from + // being reinitialized via JS after each update. + // @see js/webform.element.computed.js + // + // The announce attribute allows FAPI Ajax callbacks to easily + // trigger announcements. + // @see js/webform.announce.js + $t_args = ['@title' => $element['#title'], '@value' => strip_tags($value)]; + $attributes = [ + 'class' => ['js-webform-computed-wrapper'], + 'id' => $element['#wrapper_id'], + 'data-webform-announce' => t('@title is @value', $t_args), + ]; + $element['#prefix'] = '<div' . new Attribute($attributes) . '>'; + + $element['#suffix'] = '</div>'; + + // Remove flexbox wrapper because it already been render outside this + // computed element's ajax wrapper. + // @see \Drupal\webform\Plugin\WebformElementBase::prepareWrapper + // @see \Drupal\webform\Plugin\WebformElementBase::preRenderFixFlexboxWrapper + $preRenderFixFlexWrapper = ['Drupal\webform\Plugin\WebformElement\WebformComputedTwig', 'preRenderFixFlexboxWrapper']; + foreach ($element['#pre_render'] as $index => $pre_render) { + if (is_array($pre_render) && $pre_render === $preRenderFixFlexWrapper) { + unset($element['#pre_render'][$index]); + } + } + + return $element; + } + + /****************************************************************************/ + // Form/Ajax helpers and callbacks. + /****************************************************************************/ + /** * Get an element's value mode/type. * @@ -120,7 +327,7 @@ public static function processValue(array $element, WebformSubmissionInterface $ */ public static function getMode(array $element) { if (empty($element['#mode']) || $element['#mode'] === static::MODE_AUTO) { - return (WebformHtmlHelper::containsHtml($element['#value'])) ? static::MODE_HTML : static::MODE_TEXT; + return (WebformHtmlHelper::containsHtml($element['#template'])) ? static::MODE_HTML : static::MODE_TEXT; } else { return $element['#mode']; @@ -134,13 +341,35 @@ public static function getMode(array $element) { * The element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * @param array $complete_form + * The complete form structure. * * @return \Drupal\webform\WebformSubmissionInterface|null * A webform submission. */ - protected static function getWebformSubmission(array $element, FormStateInterface $form_state) { + protected static function getWebformSubmission(array $element, FormStateInterface $form_state, array &$complete_form) { $form_object = $form_state->getFormObject(); - if (isset($element['#webform_submission'])) { + if ($form_object instanceof WebformSubmissionForm) { + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $form_object->getEntity(); + + // We must continually copy validated form values to the + // webform submission since a computed element's value can be based on + // another computed element's value. + // + // Therefore, we are creating a single clone of the webform submission + // and only copying the submitted form values to the cached submission. + if ($form_state->isValidationComplete()) { + if (!isset(static::$submissions[$webform_submission->uuid()])) { + static::$submissions[$webform_submission->uuid()] = clone $form_object->getEntity(); + } + $webform_submission = static::$submissions[$webform_submission->uuid()]; + $form_object->copyFormValuesToEntity($webform_submission, $complete_form, $form_state); + } + + return $webform_submission; + } + elseif (isset($element['#webform_submission'])) { if (is_string($element['#webform_submission'])) { return WebformSubmission::load($element['#webform_submission']); } @@ -148,9 +377,6 @@ protected static function getWebformSubmission(array $element, FormStateInterfac return $element['#webform_submission']; } } - elseif ($form_object instanceof WebformSubmissionForm) { - return $form_object->getEntity(); - } else { return NULL; } diff --git a/web/modules/webform/src/Element/WebformComputedToken.php b/web/modules/webform/src/Element/WebformComputedToken.php index 0195047cdc..782242d0ad 100644 --- a/web/modules/webform/src/Element/WebformComputedToken.php +++ b/web/modules/webform/src/Element/WebformComputedToken.php @@ -21,7 +21,7 @@ public static function processValue(array $element, WebformSubmissionInterface $ $token_manager = \Drupal::service('webform.token_manager'); // Replace tokens in value. - return $token_manager->replace($element['#value'], $webform_submission, [], ['html' => ($mode == static::MODE_HTML)]); + return $token_manager->replace($element['#template'], $webform_submission, [], ['html' => ($mode == static::MODE_HTML)]); } } diff --git a/web/modules/webform/src/Element/WebformComputedTwig.php b/web/modules/webform/src/Element/WebformComputedTwig.php index 70bc1acf35..69bd1ac483 100644 --- a/web/modules/webform/src/Element/WebformComputedTwig.php +++ b/web/modules/webform/src/Element/WebformComputedTwig.php @@ -12,14 +12,42 @@ */ class WebformComputedTwig extends WebformComputedBase { + /** + * Whitespace spaceless. + * + * Remove whitespace around the computed value and between HTML tags. + */ + const WHITESPACE_SPACELESS = 'spaceless'; + + /** + * Whitespace trim. + * + * Remove whitespace around the computed value. + */ + const WHITESPACE_TRIM = 'trim'; + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#whitespace' => '', + ]; + } + /** * {@inheritdoc} */ public static function processValue(array $element, WebformSubmissionInterface $webform_submission) { - $template = $element['#value']; + $whitespace = (!empty($element['#whitespace'])) ? $element['#whitespace'] : ''; + + $template = ($whitespace === static::WHITESPACE_SPACELESS) ? '{% spaceless %}' . $element['#template'] . '{% endspaceless %}' : $element['#template']; + $options = ['html' => (static::getMode($element) === static::MODE_HTML)]; - return TwigExtension::renderTwigTemplate($webform_submission, $template, $options); + $value = TwigExtension::renderTwigTemplate($webform_submission, $template, $options); + + return ($whitespace === static::WHITESPACE_TRIM) ? trim($value) : $value; } } diff --git a/web/modules/webform/src/Element/WebformContact.php b/web/modules/webform/src/Element/WebformContact.php index 02b083c3ef..436a7c3e00 100644 --- a/web/modules/webform/src/Element/WebformContact.php +++ b/web/modules/webform/src/Element/WebformContact.php @@ -56,7 +56,7 @@ public static function getCompositeElements(array $element) { ]; $elements['postal_code'] = [ '#type' => 'textfield', - '#title' => t('Zip/Postal Code'), + '#title' => t('ZIP/Postal Code'), ]; $elements['country'] = [ '#type' => 'select', diff --git a/web/modules/webform/src/Element/WebformElementAttributes.php b/web/modules/webform/src/Element/WebformElementAttributes.php index 9903c1583e..7e98231642 100644 --- a/web/modules/webform/src/Element/WebformElementAttributes.php +++ b/web/modules/webform/src/Element/WebformElementAttributes.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Element; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Serialization\Yaml; @@ -61,7 +60,7 @@ public static function processWebformElementAttributes(&$element, FormStateInter $t_args = [ '@title' => $element['#title'], - '@type' => Unicode::strtolower($type), + '@type' => mb_strtolower($type), ]; // Class. @@ -71,18 +70,20 @@ public static function processWebformElementAttributes(&$element, FormStateInter $element['class'] = [ '#type' => 'webform_select_other', '#title' => t('@title CSS classes', $t_args), - '#description' => t("Apply classes to the @type. Select 'custom...' to enter custom classes.", $t_args), + '#description' => t("Apply classes to the @type. Select 'custom…' to enter custom classes.", $t_args), '#multiple' => TRUE, - '#options' => [WebformSelectOther::OTHER_OPTION => t('custom...')] + array_combine($classes, $classes), + '#options' => [WebformSelectOther::OTHER_OPTION => t('custom…')] + array_combine($classes, $classes), + '#other__placeholder' => t('Enter custom classes…'), '#other__option_delimiter' => ' ', '#attributes' => [ 'class' => [ 'js-' . $element['#id'] . '-attributes-style', ], ], + '#select2' => TRUE, '#default_value' => $element['#default_value']['class'], ]; - WebformElementHelper::enhanceSelect($element['class'], TRUE); + WebformElementHelper::process($element['class']); } else { $element['class'] = [ @@ -96,7 +97,7 @@ public static function processWebformElementAttributes(&$element, FormStateInter // Custom options. $element['custom'] = [ '#type' => 'texfield', - '#placeholder' => t('Enter custom classes...'), + '#placeholder' => t('Enter custom classes…'), '#states' => [ 'visible' => [ 'select.js-' . $element['#id'] . '-attributes-style' => ['value' => '_custom_'], @@ -123,7 +124,7 @@ public static function processWebformElementAttributes(&$element, FormStateInter '#title' => t('@title custom attributes (YAML)', $t_args), '#description' => t('Enter additional attributes to be added the @type.', $t_args), '#attributes__access' => (!\Drupal::moduleHandler()->moduleExists('webform_ui') || \Drupal::currentUser()->hasPermission('edit webform source')), - '#default_value' => WebformYaml::tidy(Yaml::encode($attributes)), + '#default_value' => WebformYaml::encode($attributes), ]; // Apply custom properties. Typically used for descriptions. diff --git a/web/modules/webform/src/Element/WebformElementComposite.php b/web/modules/webform/src/Element/WebformElementComposite.php index bfeb6c9b24..c21bfea633 100644 --- a/web/modules/webform/src/Element/WebformElementComposite.php +++ b/web/modules/webform/src/Element/WebformElementComposite.php @@ -2,12 +2,13 @@ namespace Drupal\webform\Element; -use Drupal\Component\Serialization\Yaml; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; +use Drupal\Core\Serialization\Yaml; use Drupal\webform\Plugin\WebformElement\WebformCompositeBase as WebformCompositeBaseElement; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformYaml; /** * Provides a element for the composite elements. @@ -17,7 +18,7 @@ class WebformElementComposite extends FormElement { /** - * List of supported element properties. + * List of supported element properties. * * @var array */ @@ -66,7 +67,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form $composite_properties = array_intersect_key($composite_element, static::$supportedProperties); // Move 'unsupported' properties to 'custom'. $custom_properties = array_diff_key($composite_element, static::$supportedProperties); - $composite_properties['custom'] = $custom_properties ? trim(Yaml::encode($custom_properties)) : ''; + $composite_properties['custom'] = $custom_properties ? WebformYaml::encode($custom_properties) : ''; $default_value[] = $composite_properties; } $element['#default_value'] = $default_value; @@ -122,26 +123,29 @@ public static function processWebformElementComposite(&$element, FormStateInterf $element['elements'] = [ '#type' => 'webform_multiple', '#title' => t('Elements'), - '#title_display' => t('Invisible'), + '#title_display' => 'invisible', '#label' => t('element'), '#labels' => t('elements'), '#empty_items' => 0, + '#min_items' => 1, '#header' => TRUE, + '#add' => FALSE, '#default_value' => (isset($element['#default_value'])) ? $element['#default_value'] : NULL, '#error_no_message' => TRUE, '#element' => [ - 'key_type_options' => [ + 'settings' => [ '#type' => 'container', - '#title' => ($edit_source) ? t('Key / Type / Options / Custom Properties') : t('Key / Type / Options '), + '#title' => t('Settings'), '#help' => '<b>' . t('Key') . ':</b> ' . t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.') . - '<br/><br/>' . '<b>' . t('Type') . ':</b> ' . t('The type of element to be displayed.') . - '<br/><br/>' . '<b>' . t('Options') . ':</b> ' . t('Please select predefined options or enter custom options.') . ' ' . t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.') . - ($edit_source ? '<br/><br/>' . '<b>' . t('Custom Properties') . ':</b> ' . t('Properties do not have to be prepended with a hash (#) character, the hash character will be automatically added to the custom properties.') : ''), + '<hr/>' . '<b>' . t('Type') . ':</b> ' . t('The type of element to be displayed.') . + '<hr/>' . '<b>' . t('Options') . ':</b> ' . t('Please select predefined options or enter custom options.') . ' ' . t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.') . + ($edit_source ? '<hr/>' . '<b>' . t('Custom Properties') . ':</b> ' . t('Properties do not have to be prepended with a hash (#) character, the hash character will be automatically added to the custom properties.') : '') . + '<hr/>' . '<b>' . t('Required') . ':</b> ' . t('Check this option if the user must enter a value.'), 'key' => [ '#type' => 'textfield', '#title' => t('Key'), '#title_display' => 'invisible', - '#placeholder' => t('Enter key'), + '#placeholder' => t('Enter key…'), '#pattern' => '^[a-z0-9_]+$', '#attributes' => [ 'title' => t('Enter a unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), @@ -153,6 +157,8 @@ public static function processWebformElementComposite(&$element, FormStateInterf '#type' => 'select', '#title' => t('Type'), '#title_display' => 'invisible', + '#description' => t('The type of element to be displayed.'), + '#description_display' => 'invisible', '#options' => $type_options, '#empty_option' => t('- Select type -'), '#required' => TRUE, @@ -164,6 +170,8 @@ public static function processWebformElementComposite(&$element, FormStateInterf '#yaml' => TRUE, '#title' => t('Options'), '#title_display' => 'invisible', + '#description' => t('Please select predefined options or enter custom options.') . ' ' . t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.'), + '#description_display' => 'invisible', '#wrapper_attributes' => [ 'data-composite-types' => implode(',', $options_elements), 'data-composite-required' => 'data-composite-required', @@ -181,25 +189,36 @@ public static function processWebformElementComposite(&$element, FormStateInterf '#mode' => 'yaml', '#title' => t('Custom properties'), '#title_display' => 'invisible', - '#placeholder' => t('Enter custom properties'), + '#description' => t('Properties do not have to be prepended with a hash (#) character, the hash character will be automatically added to the custom properties.'), + '#description_display' => 'invisible', + '#placeholder' => t('Enter custom properties…'), '#error_no_message' => TRUE, ] : [ '#type' => 'hidden', ], + // Note: Setting #return_value: TRUE is not returning any value. + 'required' => [ + '#type' => 'checkbox', + '#title' => t('Required'), + '#description' => t('Check this option if the user must enter a value.'), + '#description_display' => 'invisible', + '#error_no_message' => TRUE, + ], ], - 'title_placeholder_description_help' => [ + 'labels' => [ '#type' => 'container', - '#title' => t('Title / Placeholder / Description / Help'), - '#help' => '<b>' . t('Title') . ':</b> ' . t('This is used as a descriptive label when displaying this webform element.') . '<br/><br/>' . - '<b>' . t('Placeholder') . ':</b> ' . t('The placeholder will be shown in the element until the user starts entering a value.') . '<br/><br/>' . - '<b>' . t('Description') . ':</b> ' . t('A short description of the element used as help for the user when he/she uses the webform.') . '<br/><br/>' . - '<b>' . t('Help text') . ':</b> ' . t('A tooltip displayed after the title.'), - + '#title' => t('Labels'), + '#help' => '<b>' . t('Title') . ':</b> ' . t('This is used as a descriptive label when displaying this webform element.') . + '<hr/><b>' . t('Placeholder') . ':</b> ' . t('The placeholder will be shown in the element until the user starts entering a value.') . + '<hr/><b>' . t('Description') . ':</b> ' . t('A short description of the element used as help for the user when he/she uses the webform.') . + '<hr/><b>' . t('Help text') . ':</b> ' . t('A tooltip displayed after the title.'), 'title' => [ '#type' => 'textfield', '#title' => t('Title'), '#title_display' => 'invisible', - '#placeholder' => t('Enter title'), + '#description' => t('This is used as a descriptive label when displaying this webform element.'), + '#description_display' => 'invisible', + '#placeholder' => t('Enter title…'), '#required' => TRUE, '#error_no_message' => TRUE, ], @@ -207,15 +226,19 @@ public static function processWebformElementComposite(&$element, FormStateInterf '#type' => 'textfield', '#title' => t('Placeholder'), '#title_display' => 'invisible', - '#placeholder' => t('Enter placeholder'), + '#description' => t('The placeholder will be shown in the element until the user starts entering a value.'), + '#description_display' => 'invisible', + '#placeholder' => t('Enter placeholder…'), '#attributes' => ['data-composite-types' => implode(',', $placeholder_elements)], '#error_no_message' => TRUE, ], 'description' => [ '#type' => 'textarea', '#title' => t('Description'), + '#description' => t('A short description of the element used as help for the user when he/she uses the webform.'), + '#description_display' => 'invisible', '#title_display' => 'invisible', - '#placeholder' => t('Enter description'), + '#placeholder' => t('Enter description…'), '#rows' => 2, '#error_no_message' => TRUE, ], @@ -223,19 +246,13 @@ public static function processWebformElementComposite(&$element, FormStateInterf '#type' => 'textarea', '#title' => t('Help text'), '#title_display' => 'invisible', - '#placeholder' => t('Enter help text'), + '#description' => t('A tooltip displayed after the title.'), + '#description_display' => 'invisible', + '#placeholder' => t('Enter help text…'), '#rows' => 2, '#error_no_message' => TRUE, ], ], - // Note: Setting #return_value: TRUE is not returning any value. - 'required' => [ - '#type' => 'checkbox', - '#title' => t('Req.'), - '#title_display' => 'invisible', - '#help' => '<b>' . t('Required') . ':</b> ' . t('Check this option if the user must enter a value.'), - '#error_no_message' => TRUE, - ], ], ]; diff --git a/web/modules/webform/src/Element/WebformElementOptions.php b/web/modules/webform/src/Element/WebformElementOptions.php index 617c20a8f6..0726c91c9b 100644 --- a/web/modules/webform/src/Element/WebformElementOptions.php +++ b/web/modules/webform/src/Element/WebformElementOptions.php @@ -9,6 +9,7 @@ use Drupal\Core\Url; use Drupal\webform\Entity\WebformOptions as WebformOptionsEntity; use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\Utility\WebformOptionsHelper; /** * Provides a form element for managing webform element options. @@ -84,20 +85,22 @@ public static function processWebformElementOptions(&$element, FormStateInterfac ':href' => Url::fromRoute('entity.webform_options.collection')->toString(), ]; + $has_options = (count($options)) ? TRUE : FALSE; + // Select options. $element['options'] = [ '#type' => 'select', '#description' => t('Please select <a href=":href">predefined @type</a> or enter custom @type.', $t_args), '#options' => [ - self::CUSTOM_OPTION => t('Custom @type...', $t_args), + self::CUSTOM_OPTION => t('Custom @type…', $t_args), ] + $options, '#attributes' => [ 'class' => ['js-' . $element['#id'] . '-options'], ], '#error_no_message' => TRUE, - '#access' => count($options) ? TRUE : FALSE, - '#default_value' => (isset($element['#default_value']) && !is_array($element['#default_value'])) ? $element['#default_value'] : '', + '#access' => $has_options, + '#default_value' => (isset($element['#default_value']) && !is_array($element['#default_value']) && WebformOptionsHelper::hasOption($element['#default_value'], $options)) ? $element['#default_value'] : '', ]; // Custom options. @@ -106,11 +109,6 @@ public static function processWebformElementOptions(&$element, FormStateInterfac '#type' => 'webform_multiple', '#title' => $element['#title'], '#title_display' => 'invisible', - '#states' => [ - 'visible' => [ - 'select.js-' . $element['#id'] . '-options' => ['value' => ''], - ], - ], '#error_no_message' => TRUE, '#default_value' => (isset($element['#default_value']) && !is_string($element['#default_value'])) ? $element['#default_value'] : [], ]; @@ -123,16 +121,19 @@ public static function processWebformElementOptions(&$element, FormStateInterfac '#title_display' => 'invisible', '#label' => ($element['#likert']) ? t('answer') : t('option'), '#labels' => ($element['#likert']) ? t('answers') : t('options'), - '#states' => [ - 'visible' => [ - 'select.js-' . $element['#id'] . '-options' => ['value' => ''], - ], - ], '#error_no_message' => TRUE, '#options_description' => $element['#options_description'], '#default_value' => (isset($element['#default_value']) && !is_string($element['#default_value'])) ? $element['#default_value'] : [], ]; } + // If there are options set #states. + if ($has_options) { + $element['custom']['#states'] = [ + 'visible' => [ + 'select.js-' . $element['#id'] . '-options' => ['value' => self::CUSTOM_OPTION], + ], + ]; + } // Add validate callback. $element += ['#element_validate' => []]; diff --git a/web/modules/webform/src/Element/WebformElementStates.php b/web/modules/webform/src/Element/WebformElementStates.php index 2515667f9a..8830f8ea34 100644 --- a/web/modules/webform/src/Element/WebformElementStates.php +++ b/web/modules/webform/src/Element/WebformElementStates.php @@ -2,12 +2,13 @@ namespace Drupal\webform\Element; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\OptGroup; use Drupal\Core\Serialization\Yaml; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformAccessibilityHelper; use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformYaml; @@ -26,6 +27,7 @@ public function getInfo() { return [ '#input' => TRUE, '#selector_options' => [], + '#selector_sources' => [], '#empty_states' => 3, '#process' => [ [$class, 'processWebformStates'], @@ -65,6 +67,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form * Expand an email confirm field into two HTML5 email elements. */ public static function processWebformStates(&$element, FormStateInterface $form_state, &$complete_form) { + // Define default #state_options and #trigger_options. // There are also defined by \Drupal\webform\Plugin\WebformElementBase::form. $element += [ @@ -74,23 +77,43 @@ public static function processWebformStates(&$element, FormStateInterface $form_ $element['#tree'] = TRUE; + $edit_source = $form_state->get(static::getStorageKey($element, 'edit_source')); + // Add validate callback that extracts the associative array of states. $element += ['#element_validate' => []]; array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformElementStates']); // For customized #states display a CodeMirror YAML editor. - if ($warning_message = static::isDefaultValueCustomizedFormApiStates($element)) { - $warning_message .= ' ' . t('Form API #states must be manually entered.'); - $element['messages'] = [ - '#type' => 'webform_message', - '#message_type' => 'warning', - '#message_message' => $warning_message, - ]; + $warning_message = static::isDefaultValueCustomizedFormApiStates($element); + if ($warning_message || $edit_source) { + if ($warning_message) { + $warning_message .= ' ' . t('Form API #states must be manually entered.'); + $element['warning_messages'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $warning_message, + ]; + } + + if ($edit_source) { + $element['edit_source_message'] = [ + '#type' => 'webform_message', + '#message_message' => t('Creating custom conditional logic (Form API #states) with nested conditions or custom selectors will disable the conditional logic builder. This will require that Form API #states be manually entered.'), + '#message_type' => 'info', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } $element['states'] = [ '#type' => 'webform_codemirror', + '#title' => t('Conditional Logic (YAML)'), + '#title_display' => 'invisible', '#mode' => 'yaml', - '#default_value' => WebformYaml::tidy(Yaml::encode($element['#default_value'])), + '#default_value' => WebformYaml::encode($element['#default_value']), '#description' => t('Learn more about Drupal\'s <a href=":href">Form API #states</a>.', [':href' => 'https://www.lullabot.com/articles/form-api-states']), + '#webform_element' => TRUE, + '#more_title' => t('Help'), + '#more' => static::buildSourceHelp($element), ]; return $element; } @@ -121,9 +144,9 @@ public static function processWebformStates(&$element, FormStateInterface $form_ // Build header. $header = [ ['data' => t('State'), 'width' => '25%'], - ['data' => t('Element/Selector'), 'width' => '50%'], + ['data' => t('Element'), 'width' => '50%'], ['data' => t('Trigger/Value'), 'width' => '25%'], - ['data' => ''], + ['data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Operations'))], ]; // Get states and number of rows. @@ -134,11 +157,15 @@ public static function processWebformStates(&$element, FormStateInterface $form_ $states = (isset($element['#default_value'])) ? static::convertFormApiStatesToStatesArray($element['#default_value']) : []; } + // Track state row indexes for disable/enabled warning message. + $state_row_indexes = []; + // Build state and conditions rows. $row_index = 0; $rows = []; foreach ($states as $state_settings) { $rows[$row_index] = static::buildStateRow($element, $state_settings, $table_id, $row_index, $ajax_settings); + $state_row_indexes[] = $row_index; $row_index++; foreach ($state_settings['conditions'] as $condition) { $rows[$row_index] = static::buildConditionRow($element, $condition, $table_id, $row_index, $ajax_settings); @@ -148,7 +175,8 @@ public static function processWebformStates(&$element, FormStateInterface $form_ // Generator empty state with conditions rows. if ($row_index < $number_of_rows) { - $rows[$row_index] = static::buildStateRow($element, [], $table_id, $row_index, $ajax_settings);; + $rows[$row_index] = static::buildStateRow($element, [], $table_id, $row_index, $ajax_settings); + $state_row_indexes[] = $row_index; $row_index++; while ($row_index < $number_of_rows) { $rows[$row_index] = static::buildConditionRow($element, [], $table_id, $row_index, $ajax_settings); @@ -156,17 +184,22 @@ public static function processWebformStates(&$element, FormStateInterface $form_ } } + // Add wrapper to the element. + $element += ['#prefix' => '', '#suffix' => '']; + $element['#prefix'] = '<div id="' . $table_id . '">' . $element['#prefix']; + $element['#suffix'] .= '</div>'; + // Build table. $element['states'] = [ - '#prefix' => '<div id="' . $table_id . '" class="webform-states-table">', - '#suffix' => '</div>', '#type' => 'table', '#header' => $header, + '#attributes' => ['class' => ['webform-states-table']], ] + $rows; + $element['actions'] = ['#type' => 'container']; // Build add state action. if ($element['#multiple']) { - $element['add'] = [ + $element['actions']['add'] = [ '#type' => 'submit', '#value' => t('Add another state'), '#limit_validation_errors' => [], @@ -176,11 +209,145 @@ public static function processWebformStates(&$element, FormStateInterface $form_ ]; } + // Edit source. + if (\Drupal::currentUser()->hasPermission('edit webform source')) { + $element['actions']['source'] = [ + '#type' => 'submit', + '#value' => t('Edit source'), + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'editSourceSubmit']], + '#ajax' => $ajax_settings, + '#attributes' => ['class' => ['button', 'button--danger']], + '#name' => $table_id . '_source', + ]; + } + + // Display a warning message when any state is set to disabled or enabled. + if (!empty($element['#disabled_message'])) { + $total_state_row_indexes = count($state_row_indexes); + $triggers = []; + foreach ($state_row_indexes as $index => $row_index) { + $id = Html::getId('edit-' . implode('-', $element['#parents']) . '-states-' . $row_index . '-state'); + $triggers[] = [':input[data-drupal-selector="' . $id . '"]' => ['value' => ['pattern' => '^(disabled|enabled)$']]]; + if (($index + 1) < $total_state_row_indexes) { + $triggers[] = 'or'; + } + } + if ($triggers) { + $element['disabled_message'] = [ + '#type' => 'webform_message', + '#message_message' => t('<a href="https://www.w3schools.com/tags/att_input_disabled.asp">Disabled</a> elements do not submit data back to the server and the element\'s server-side default or current value will be preserved and saved to the database.'), + '#message_type' => 'warning', + '#states' => ['visible' => $triggers], + ]; + } + } + $element['#attached']['library'][] = 'webform/webform.element.states'; + // Convert #options to jQuery autocomplete source format. + // @see http://api.jqueryui.com/autocomplete/#option-source + $selectors = []; + $sources = []; + if ($element['#selector_sources']) { + foreach ($element['#selector_sources'] as $selector => $values) { + $sources_key = md5(serialize($values)); + $selectors[$selector] = $sources_key; + if (!isset($sources[$sources_key])) { + foreach ($values as $key => $value) { + $sources[$sources_key][] = [ + 'label' => (string) $value . ($value != $key ? ' (' . $key . ')' : ''), + 'value' => (string) $key, + ]; + } + } + } + } + $element['#attached']['drupalSettings']['webformElementStates'] = [ + 'selectors' => $selectors, + 'sources' => $sources, + ]; + return $element; } + /** + * Build edit source help. + * + * @param array $element + * An element. + * + * @return array + * A renderable array. + */ + protected static function buildSourceHelp(array $element) { + $build = []; + $build['states'] = [ + 'title' => [ + '#markup' => t('Available states'), + '#prefix' => '<strong>', + '#suffix' => '</strong>', + ], + 'items' => static::convertOptionToItemList($element['#state_options']), + ]; + if ($element['#selector_options']) { + $build['selectors'] = [ + 'title' => [ + '#markup' => t('Available selectors'), + '#prefix' => '<strong>', + '#suffix' => '</strong>', + ], + 'items' => static::convertOptionToItemList($element['#selector_options']), + ]; + } + $build['triggers'] = [ + 'title' => [ + '#markup' => t('Available triggers'), + '#prefix' => '<strong>', + '#suffix' => '</strong>', + ], + 'items' => static::convertOptionToItemList($element['#trigger_options']), + ]; + return $build; + } + + /** + * Convert options with optgroup to item list. + * + * @param array $options + * An array of options. + * + * @return array + * A renderable array. + */ + protected static function convertOptionToItemList(array $options) { + $items = []; + foreach ($options as $option_name => $option_value) { + if (is_array($option_value)) { + $items[$option_name] = [ + 'title' => [ + '#markup' => $option_name, + ], + 'children' => [ + '#theme' => 'item_list', + '#items' => array_keys($option_value), + ], + ]; + } + else { + $items[$option_name] = [ + '#markup' => $option_name, + '#prefix' => '<div>', + '#suffix' => '</div>', + ]; + } + } + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + } + /** * Build state row. * @@ -207,13 +374,18 @@ protected static function buildStateRow(array $element, array $state, $table_id, ]; $row['state'] = [ '#type' => 'select', + '#title' => t('State'), + '#title_display' => 'invisible', '#options' => $element['#state_options'], '#default_value' => $state['state'], '#empty_option' => t('- Select -'), '#wrapper_attributes' => ['class' => ['webform-states-table--state']], + '#error_no_message' => TRUE, ]; $row['operator'] = [ '#type' => 'select', + '#title' => t('Operator'), + '#title_display' => 'invisible', '#options' => [ 'and' => t('All'), 'or' => t('Any'), @@ -223,6 +395,7 @@ protected static function buildStateRow(array $element, array $state, $table_id, '#field_prefix' => t('if'), '#field_suffix' => t('of the following is met:'), '#wrapper_attributes' => ['class' => ['webform-states-table--operator'], 'colspan' => 2, 'align' => 'left'], + '#error_no_message' => TRUE, ]; $row['operations'] = static::buildOperations($table_id, $row_index, $ajax_settings); if (!$element['#multiple']) { @@ -262,10 +435,13 @@ protected static function buildConditionRow(array $element, array $condition, $t $row['state'] = []; $row['selector'] = [ '#type' => 'select', + '#title' => t('Selector'), + '#title_display' => 'invisible', '#options' => $element['#selector_options'], '#wrapper_attributes' => ['class' => ['webform-states-table--selector']], '#default_value' => $condition['selector'], '#empty_option' => t('- Select -'), + '#error_no_message' => TRUE, ]; if (!isset($element['#selector_options'][$condition['selector']])) { $row['selector']['#options'][$condition['selector']] = $condition['selector']; @@ -275,11 +451,14 @@ protected static function buildConditionRow(array $element, array $condition, $t ]; $row['condition']['trigger'] = [ '#type' => 'select', + '#title' => t('Trigger'), + '#title_display' => 'invisible', '#options' => $element['#trigger_options'], '#default_value' => $condition['trigger'], '#empty_option' => t('- Select -'), '#parents' => [$element_name, 'states', $row_index , 'trigger'], '#wrapper_attributes' => ['class' => ['webform-states-table--trigger']], + '#error_no_message' => TRUE, ]; $row['condition']['value'] = [ '#type' => 'textfield', @@ -287,7 +466,7 @@ protected static function buildConditionRow(array $element, array $condition, $t '#title_display' => 'invisible', '#size' => 25, '#default_value' => $condition['value'], - '#placeholder' => t('Enter value...'), + '#placeholder' => t('Enter value…'), '#states' => [ 'visible' => [ [$trigger_selector => ['value' => 'value']], @@ -305,6 +484,7 @@ protected static function buildConditionRow(array $element, array $condition, $t ], '#wrapper_attributes' => ['class' => ['webform-states-table--value']], '#parents' => [$element_name, 'states', $row_index , 'value'], + '#error_no_message' => TRUE, ]; $row['condition']['pattern'] = [ '#type' => 'container', @@ -341,6 +521,7 @@ protected static function buildOperations($table_id, $row_index, array $ajax_set ]; $operations['add'] = [ '#type' => 'image_button', + '#title' => t('Add'), '#src' => drupal_get_path('module', 'webform') . '/images/icons/plus.svg', '#limit_validation_errors' => [], '#submit' => [[get_called_class(), 'addConditionSubmit']], @@ -350,6 +531,7 @@ protected static function buildOperations($table_id, $row_index, array $ajax_set ]; $operations['remove'] = [ '#type' => 'image_button', + '#title' => t('Remove'), '#src' => drupal_get_path('module', 'webform') . '/images/icons/ex.svg', '#limit_validation_errors' => [], '#submit' => [[get_called_class(), 'removeRowSubmit']], @@ -365,7 +547,7 @@ protected static function buildOperations($table_id, $row_index, array $ajax_set /****************************************************************************/ /** - * Webform submission handler for adding another state. + * Form submission handler for adding another state. * * @param array $form * An associative array containing the structure of the form. @@ -375,7 +557,7 @@ protected static function buildOperations($table_id, $row_index, array $ajax_set public static function addStateSubmit(array &$form, FormStateInterface $form_state) { // Get the webform states element by going up one level. $button = $form_state->getTriggeringElement(); - $element =& NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); + $element =& NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2)); $values = $element['states']['#value']; @@ -397,12 +579,12 @@ public static function addStateSubmit(array &$form, FormStateInterface $form_sta // Update the number of rows. $form_state->set(static::getStorageKey($element, 'number_of_rows'), count($values)); - // Rebuild the webform. + // Rebuild the form. $form_state->setRebuild(); } /** - * Webform submission handler for adding another condition. + * Form submission handler for adding another condition. * * @param array $form * An associative array containing the structure of the form. @@ -435,12 +617,12 @@ public static function addConditionSubmit(array &$form, FormStateInterface $form // Update the number of rows. $form_state->set(static::getStorageKey($element, 'number_of_rows'), count($values)); - // Rebuild the webform. + // Rebuild the form. $form_state->setRebuild(); } /** - * Webform submission handler for removing a state or condition. + * Form submission handler for removing a state or condition. * * @param array $form * An associative array containing the structure of the form. @@ -476,7 +658,32 @@ public static function removeRowSubmit(array &$form, FormStateInterface $form_st // Update the number of rows. $form_state->set(static::getStorageKey($element, 'number_of_rows'), count($values)); - // Rebuild the webform. + // Rebuild the form. + $form_state->setRebuild(); + } + + /** + * Form submission handler for editing source. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public static function editSourceSubmit(array &$form, FormStateInterface $form_state) { + // Get the webform states element by going up one level. + $button = $form_state->getTriggeringElement(); + $element =& NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2)); + + // Set edit source. + $form_state->set(static::getStorageKey($element, 'edit_source'), TRUE); + + // Convert states to editable string. + $value = $element['#value'] ? Yaml::encode($element['#value']) : ''; + $form_state->setValueForElement($element['states'], $value); + NestedArray::setValue($form_state->getUserInput(), $element['states']['#parents'], $value); + + // Rebuild the form. $form_state->setRebuild(); } @@ -485,9 +692,9 @@ public static function removeRowSubmit(array &$form, FormStateInterface $form_st */ public static function ajaxCallback(array &$form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); - $parent_length = (isset($button['#row_index'])) ? -4 : -1; + $parent_length = (isset($button['#row_index'])) ? -4 : -2; $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, $parent_length)); - return $element['states']; + return $element; } /** @@ -498,7 +705,11 @@ public static function validateWebformElementStates(&$element, FormStateInterfac $states = Yaml::decode($element['states']['#value']); } else { - $states = static::convertFormValuesToFormApiStates($element['states']['#value']); + $errors = []; + $states = static::convertElementValueToFormApiStates($element, $errors); + if ($errors) { + $form_state->setError($element, reset($errors)); + } } $form_state->setValueForElement($element, NULL); @@ -587,22 +798,30 @@ protected static function getStatesArrayCondition($selector, array $condition) { } /** - * Convert states array to Form API #states. + * Convert an element's submitted value to Form API #states. * - * @param array $states_array - * An associative array containing states. + * @param array $element + * The form element. + * @param array $errors + * An array used to capture errors. * * @return array * An associative array of states. */ - protected static function convertStatesArrayToFormApiStates(array $states_array = []) { + protected static function convertElementValueToFormApiStates(array $element, array &$errors = []) { $states = []; + $states_array = static::convertFormValuesToStatesArray($element['states']['#value']); foreach ($states_array as $state_array) { $state = $state_array['state']; if (!$state) { continue; } + // Check for duplicate states. + if (isset($states[$state])) { + static::setFormApiStateError($element, $errors, $state); + } + // Define values extracted from // WebformElementStates::getFormApiStatesCondition(). $selector = NULL; @@ -614,6 +833,10 @@ protected static function convertStatesArrayToFormApiStates(array $states_array if (count($conditions) === 1) { $condition = reset($conditions); extract(static::getFormApiStatesCondition($condition)); + // Check for duplicate selectors. + if (isset($states[$state][$selector])) { + static::setFormApiStateError($element, $errors, $state, $selector); + } $states[$state][$selector][$trigger] = $value; } else { @@ -631,6 +854,10 @@ protected static function convertStatesArrayToFormApiStates(array $states_array ]; } else { + // Check for duplicate selectors. + if (isset($states[$state][$selector])) { + static::setFormApiStateError($element, $errors, $state, $selector); + } $states[$state][$selector] = [ $trigger => $value, ]; @@ -642,6 +869,37 @@ protected static function convertStatesArrayToFormApiStates(array $states_array return $states; } + /** + * Set Form API state error. + * + * @param array $element + * The form element. + * @param array $errors + * An array used to capture errors. + * @param null|string $state + * An element state. + * @param null|string $selector + * An element selector. + */ + protected static function setFormApiStateError(array $element, array &$errors, $state = NULL, $selector = NULL) { + $state_options = OptGroup::flattenOptions($element['#state_options']); + $selector_options = OptGroup::flattenOptions($element['#selector_options']); + + if ($state && !$selector) { + $t_args = [ + '%state' => $state_options[$state], + ]; + $errors[] = t('The %state state is declared more than once. There can only be one declaration per state.', $t_args); + } + elseif ($state && $selector) { + $t_args = [ + '%selector' => $selector_options[$selector], + '%state' => $state_options[$state], + ]; + $errors[] = t('The %selector element is used more than once within the %state state. To use multiple values within a trigger try using the pattern trigger.', $t_args); + } + } + /** * Get FAPI states array condition. * @@ -685,7 +943,7 @@ protected static function getFormApiStatesCondition(array $condition) { * @return array * An associative array of states. */ - public static function convertFormValuesToStatesArray(array $values = []) { + protected static function convertFormValuesToStatesArray(array $values = []) { $index = 0; $states = []; @@ -705,20 +963,6 @@ public static function convertFormValuesToStatesArray(array $values = []) { return $states; } - /** - * Convert webform values to states array. - * - * @param array $values - * Submitted webform values to converted to states array. - * - * @return array - * An associative array of states. - */ - public static function convertFormValuesToFormApiStates(array $values = []) { - $values = static::convertFormValuesToStatesArray($values); - return static::convertStatesArrayToFormApiStates($values); - } - /** * Determine if an element's #states array is customized. * @@ -728,7 +972,7 @@ public static function convertFormValuesToFormApiStates(array $values = []) { * @return bool|string * FALSE if #states array is not customized or a warning message. */ - public static function isDefaultValueCustomizedFormApiStates(array $element) { + protected static function isDefaultValueCustomizedFormApiStates(array $element) { // Empty default values are not customized. if (empty($element['#default_value'])) { return FALSE; @@ -763,12 +1007,12 @@ public static function isDefaultValueCustomizedFormApiStates(array $element) { } elseif (is_string($condition)) { if (!in_array($condition, ['and', 'or', 'xor'])) { - return t('Conditional logic (Form API #states) is using the %operator operator.', ['%operator' => Unicode::strtoupper($condition)]); + return t('Conditional logic (Form API #states) is using the %operator operator.', ['%operator' => mb_strtoupper($condition)]); } // Make sure the same operator is being used between the conditions. if ($operator && $operator != $condition) { - return t('Conditional logic (Form API #states) has multiple operators.', ['%operator' => Unicode::strtoupper($condition)]); + return t('Conditional logic (Form API #states) has multiple operators.', ['%operator' => mb_strtoupper($condition)]); } // Set the operator. diff --git a/web/modules/webform/src/Element/WebformEmailConfirm.php b/web/modules/webform/src/Element/WebformEmailConfirm.php index 124613ede3..dcf2b0a2a5 100644 --- a/web/modules/webform/src/Element/WebformEmailConfirm.php +++ b/web/modules/webform/src/Element/WebformEmailConfirm.php @@ -2,10 +2,8 @@ namespace Drupal\webform\Element; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Render\Element\CompositeFormElementTrait; use Drupal\webform\Utility\WebformElementHelper; /** @@ -18,7 +16,7 @@ */ class WebformEmailConfirm extends FormElement { - use CompositeFormElementTrait; + use WebformCompositeFormElementTrait; /** * {@inheritdoc} @@ -31,11 +29,10 @@ public function getInfo() { '#process' => [ [$class, 'processWebformEmailConfirm'], ], - '#theme_wrappers' => ['form_element'], + '#pre_render' => [ + [$class, 'preRenderWebformCompositeFormElement'], + ], '#required' => FALSE, - // Add '#markup' property to add an 'id' attribute to the form element. - // @see template_preprocess_form_element() - '#markup' => '', ]; } @@ -98,7 +95,6 @@ public static function processWebformEmailConfirm(&$element, FormStateInterface $element['mail_2']['#value'] = empty($element['#value']) ? NULL : $element['#value']['mail_2']; $element['mail_2']['#error_no_message'] = TRUE; - // Don't require the main element. $element['#required'] = FALSE; @@ -122,13 +118,12 @@ public static function processWebformEmailConfirm(&$element, FormStateInterface * Validates an email confirm element. */ public static function validateWebformEmailConfirm(&$element, FormStateInterface $form_state, &$complete_form) { - $mail_1 = trim($element['mail_1']['#value']); $mail_2 = trim($element['mail_2']['#value']); $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); if ($has_access) { if ((!empty($mail_1) || !empty($mail_2)) && strcmp($mail_1, $mail_2)) { - $form_state->setError($element['mail_2'], t('The specified email addresses do not match.')); + $form_state->setError($element, t('The specified email addresses do not match.')); } else { // NOTE: Only mail_1 needs to be validated since mail_2 is the same value. @@ -138,11 +133,11 @@ public static function validateWebformEmailConfirm(&$element, FormStateInterface WebformElementHelper::setRequiredError($element, $form_state, $required_error_title); } // Verify that the value is not longer than #maxlength. - if (isset($element['mail_1']['#maxlength']) && Unicode::strlen($mail_1) > $element['mail_1']['#maxlength']) { + if (isset($element['mail_1']['#maxlength']) && mb_strlen($mail_1) > $element['mail_1']['#maxlength']) { $t_args = [ '@name' => $element['mail_1']['#title'], '%max' => $element['mail_1']['#maxlength'], - '%length' => Unicode::strlen($mail_1), + '%length' => mb_strlen($mail_1), ]; $form_state->setError($element, t('@name cannot be longer than %max characters but is currently %length characters long.', $t_args)); } diff --git a/web/modules/webform/src/Element/WebformEntityTrait.php b/web/modules/webform/src/Element/WebformEntityTrait.php index 77d2ddd329..6eecd274cd 100644 --- a/web/modules/webform/src/Element/WebformEntityTrait.php +++ b/web/modules/webform/src/Element/WebformEntityTrait.php @@ -26,21 +26,32 @@ public function getInfo() { * * @param array $element * An element. + * @param array $settings + * An array of settings used to limit and randomize options. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Thrown when the current user doesn't have access to the specified entity. * * @see \Drupal\system\Controller\EntityAutocompleteController */ - public static function setOptions(array &$element) { + public static function setOptions(array &$element, array $settings = []) { if (!empty($element['#options'])) { return; } + // Make sure #target_type is not empty. + if (empty($element['#target_type'])) { + $element['#options'] = []; + return; + } + $selection_handler_options = [ 'target_type' => $element['#target_type'], 'handler' => $element['#selection_handler'], 'handler_settings' => (isset($element['#selection_settings'])) ? $element['#selection_settings'] : [], + // Set '_webform_settings' used to limit and randomize options. + // @see webform_query_entity_reference_alter() + '_webform_settings' => $settings, ]; /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager */ @@ -55,7 +66,11 @@ public static function setOptions(array &$element) { $options += $bundle_options; } - $options = self::translateOptions($options, $element); + // If the selection handler is not using views, then translate + // the entity reference's options. + if ($element['#selection_handler'] != 'views') { + $options = self::translateOptions($options, $element); + } // Only select menu can support optgroups. if ($element['#type'] !== 'webform_entity_select') { diff --git a/web/modules/webform/src/Element/WebformExcludedBase.php b/web/modules/webform/src/Element/WebformExcludedBase.php index c3bfb31833..b882fca5d3 100644 --- a/web/modules/webform/src/Element/WebformExcludedBase.php +++ b/web/modules/webform/src/Element/WebformExcludedBase.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; +use Drupal\webform\Plugin\WebformElement\TableSelect; /** * Provides a base webform element for webform excluded elements and columns. @@ -51,6 +52,8 @@ public static function processWebformExcluded(&$element, FormStateInterface $for '#empty' => t('No elements are available.'), '#default_value' => array_combine($default_value, $default_value), ]; + TableSelect::setProcessTableSelectCallback($element['tableselect']); + if (isset($element['#parents'])) { $element['tableselect']['#parents'] = array_merge($element['#parents'], ['tableselect']); } diff --git a/web/modules/webform/src/Element/WebformExcludedColumns.php b/web/modules/webform/src/Element/WebformExcludedColumns.php index afc42bc120..e34b308b5e 100644 --- a/web/modules/webform/src/Element/WebformExcludedColumns.php +++ b/web/modules/webform/src/Element/WebformExcludedColumns.php @@ -43,6 +43,12 @@ public static function getWebformExcludedOptions(array $element) { ]; } $elements = $webform->getElementsInitializedFlattenedAndHasValue('view'); + + // Replace tokens which can be used in an element's #title. + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $elements = $token_manager->replace($elements, $webform); + foreach ($elements as $key => $element) { $options[$key] = [ 'title' => $element['#admin_title'] ?:$element['#title'] ?: $key, diff --git a/web/modules/webform/src/Element/WebformHelp.php b/web/modules/webform/src/Element/WebformHelp.php index 927ecd4a74..cc13e7b081 100644 --- a/web/modules/webform/src/Element/WebformHelp.php +++ b/web/modules/webform/src/Element/WebformHelp.php @@ -17,7 +17,9 @@ class WebformHelp extends RenderElement { public function getInfo() { return [ '#help' => '', + '#help_title' => '', '#theme' => 'webform_element_help', + '#attributes' => [], ]; } diff --git a/web/modules/webform/src/Element/WebformHtmlEditor.php b/web/modules/webform/src/Element/WebformHtmlEditor.php index 1cbd804e0e..8708093087 100644 --- a/web/modules/webform/src/Element/WebformHtmlEditor.php +++ b/web/modules/webform/src/Element/WebformHtmlEditor.php @@ -6,6 +6,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\Utility\WebformXss; /** * Provides a webform element for entering HTML using CodeMirror, TextFormat, or custom CKEditor. @@ -69,25 +70,20 @@ public static function processWebformHtmlEditor(array $element) { // Define value element. $element += ['value' => []]; - // Set value element title and hide it. - $element['value']['#title'] = $element['#title']; - $element['value']['#title_display'] = 'invisible'; + // Copy properties to value element. + $properties = ['#title', '#required', '#attributes', '#default_value']; + $element['value'] += array_intersect_key($element, array_combine($properties, $properties)); - // Set value element required. - if (isset($element['#required'])) { - $element['value']['#required'] = $element['#required']; - } + // Hide title. + $element['value']['#title_display'] = 'invisible'; // Don't display inline form error messages. $element['#error_no_message'] = TRUE; - // Set value element default value. - $element['value']['#default_value'] = $element['#default_value']; - // Add validate callback. $element += ['#element_validate' => []]; array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformHtmlEditor']); - + // If HTML disabled and no #format is specified return simple CodeMirror // HTML editor. $disabled = \Drupal::config('webform.settings')->get('html_editor.disabled') ?: ($element['#format'] === FALSE); @@ -99,9 +95,9 @@ public static function processWebformHtmlEditor(array $element) { return $element; } - // If #format or 'webform.settings.html_editor.format' is defined return + // If #format or 'webform.settings.html_editor.element_format' is defined return // a 'text_format' element. - $format = $element['#format'] ?: \Drupal::config('webform.settings')->get('html_editor.format'); + $format = $element['#format'] ?: \Drupal::config('webform.settings')->get('html_editor.element_format'); if ($format) { $element['value'] += [ '#type' => 'text_format', @@ -115,8 +111,8 @@ public static function processWebformHtmlEditor(array $element) { // Else use textarea with completely custom HTML Editor. $element['value'] += [ '#type' => 'textarea', - '#attributes' => ['class' => ['js-html-editor']], ]; + $element['value']['#attributes']['class'][] = 'js-html-editor'; $element['#attached']['library'][] = 'webform/webform.element.html_editor'; $element['#attached']['drupalSettings']['webform']['html_editor']['allowedContent'] = static::getAllowedContent(); @@ -207,18 +203,10 @@ public static function getAllowedTags() { $allowed_tags = \Drupal::config('webform.settings')->get('element.allowed_tags'); switch ($allowed_tags) { case 'admin': - $allowed_tags = Xss::getAdminTagList(); - // <label>, <fieldset>, <legend>, <font> is missing from allowed tags. - $allowed_tags[] = 'label'; - $allowed_tags[] = 'fieldset'; - $allowed_tags[] = 'legend'; - $allowed_tags[] = 'font'; - return $allowed_tags; + return WebformXss::getAdminTagList(); case 'html': - $allowed_tags = Xss::getHtmlTagList(); - $allowed_tags[] = 'font'; - return $allowed_tags; + return WebformXss::getHtmlTagList(); default: return preg_split('/ +/', $allowed_tags); @@ -230,23 +218,28 @@ public static function getAllowedTags() { * * @param string $text * The text to be filtered. + * @param array $options + * HTML markup options. * * @return array * Render array containing 'processed_text'. * * @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::getMessage */ - public static function checkMarkup($text) { + public static function checkMarkup($text, array $options = []) { + $options += [ + 'tidy' => \Drupal::config('webform.settings')->get('html_editor.tidy'), + ]; // Remove <p> tags around a single line of text, which creates minor // margin issues. - if (\Drupal::config('webform.settings')->get('html_editor.tidy')) { + if ($options['tidy']) { if (substr_count($text, '<p>') === 1 && preg_match('#^\s*<p>.*</p>\s*$#m', $text)) { $text = preg_replace('#^\s*<p>#', '', $text); $text = preg_replace('#</p>\s*$#', '', $text); } } - if ($format = \Drupal::config('webform.settings')->get('html_editor.format')) { + if ($format = \Drupal::config('webform.settings')->get('html_editor.element_format')) { return [ '#type' => 'processed_text', '#text' => $text, @@ -255,6 +248,7 @@ public static function checkMarkup($text) { } else { return [ + '#theme' => 'webform_html_editor_markup', '#markup' => $text, '#allowed_tags' => static::getAllowedTags(), ]; diff --git a/web/modules/webform/src/Element/WebformImageResolution.php b/web/modules/webform/src/Element/WebformImageResolution.php new file mode 100644 index 0000000000..f9c32df581 --- /dev/null +++ b/web/modules/webform/src/Element/WebformImageResolution.php @@ -0,0 +1,119 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Render\Element\FormElement; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\CompositeFormElementTrait; + +/** + * Provides a webform image resolution element . + * + * @FormElement("webform_image_resolution") + */ +class WebformImageResolution extends FormElement { + + use CompositeFormElementTrait; + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#input' => TRUE, + '#size' => 60, + '#process' => [ + [$class, 'processWebformImageResolution'], + ], + '#theme_wrappers' => ['form_element'], + '#required' => FALSE, + // Add '#markup' property to add an 'id' attribute to the form element. + // @see template_preprocess_form_element() + '#markup' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (!isset($element['#default_value'])) { + $element['#default_value'] = ''; + } + $max_resolution = explode('x', $element['#default_value']) + ['', '']; + + return [ + 'x' => $max_resolution[0], + 'y' => $max_resolution[1], + ]; + } + else { + return $input; + } + } + + /** + * Expand a image resolution field into width and height elements. + * + * @see \Drupal\image\Plugin\Field\FieldType\ImageItem::fieldSettingsForm + */ + public static function processWebformImageResolution(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + $element['#type'] = 'item'; + $element += [ + '#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'), + '#height_title' => t('Maximum height'), + '#width_title' => t('Maximum width'), + '#field_prefix' => '<div class="container-inline">', + '#field_suffix' => '</div>', + ]; + $element['x'] = [ + '#type' => 'number', + '#title' => $element['#width_title'], + '#title_display' => 'invisible', + '#value' => empty($element['#value']) ? NULL : $element['#value']['x'], + '#min' => 1, + '#field_suffix' => ' × ', + ]; + $element['y'] = [ + '#type' => 'number', + '#title' => $element['#height_title'], + '#title_display' => 'invisible', + '#value' => empty($element['#value']) ? NULL : $element['#value']['y'], + '#min' => 1, + '#field_suffix' => ' ' . t('pixels'), + ]; + + // Add validate callback. + $element += ['#element_validate' => []]; + array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformImageResolution']); + + return $element; + } + + /** + * Validates an image resolution element. + * + * @see \Drupal\image\Plugin\Field\FieldType\ImageItem::validateResolution + */ + public static function validateWebformImageResolution(&$element, FormStateInterface $form_state, &$complete_form) { + if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) { + foreach (['x', 'y'] as $dimension) { + if (!$element[$dimension]['#value']) { + // We expect the field name placeholder value to be wrapped in t() + // here, so it won't be escaped again as it's already marked safe. + $form_state->setError($element[$dimension], t('Both a height and width value must be specified in the @name field.', ['@name' => $element['#title']])); + return; + } + } + $form_state->setValueForElement($element, $element['x']['#value'] . 'x' . $element['y']['#value']); + } + else { + $form_state->setValueForElement($element, ''); + } + } + +} diff --git a/web/modules/webform/src/Element/WebformLikert.php b/web/modules/webform/src/Element/WebformLikert.php index 062a1622ad..c319a97f9d 100644 --- a/web/modules/webform/src/Element/WebformLikert.php +++ b/web/modules/webform/src/Element/WebformLikert.php @@ -3,9 +3,11 @@ namespace Drupal\webform\Element; use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; -use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformAccessibilityHelper; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformOptionsHelper; /** @@ -28,9 +30,10 @@ public function getInfo() { ], '#theme_wrappers' => ['form_element'], '#required' => FALSE, + '#sticky' => TRUE, '#questions' => [], '#questions_description_display' => 'description', - // Using #answers insteads of #options to prevent triggering + // Using #answers instead of #options to prevent triggering // \Drupal\Core\Form\FormValidator::performRequiredValidation(). '#answers' => [], '#answers_description_display' => 'description', @@ -47,6 +50,9 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ // Get answer with optional N/A. static::processWebformLikertAnswers($element); + // Remove 'for' from element's label. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + // Process answers. $answers = []; foreach ($element['#answers'] as $answer_key => $answer) { @@ -61,7 +67,7 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ list($answer_title, $answer_description) = explode(WebformOptionsHelper::DESCRIPTION_DELIMITER, $answer); } $answers[$answer_key] = [ - 'description_property_name' => $answer_description_property_name , + 'description_property_name' => $answer_description_property_name, 'title' => $answer_title, 'description' => $answer_description, ]; @@ -69,7 +75,11 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ // Build header. $header = [ - 'likert_question' => ['question' => FALSE], + 'likert_question' => [ + 'data' => [ + 'title' => WebformAccessibilityHelper::buildVisuallyHidden(t('Questions')), + ], + ], ]; foreach ($answers as $answer_key => $answer) { $header[$answer_key] = [ @@ -82,6 +92,7 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ $header[$answer_key]['data']['help'] = [ '#type' => 'webform_help', '#help' => $answer['description'], + '#help_title' => $answer['title'], ]; break; @@ -97,7 +108,7 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ // Randomize questions. if (!empty($element['#questions_randomize'])) { - $element['#questions'] = WebformArrayHelper::shuffle($element['#questions']); + $element['#questions'] = WebformElementHelper::randomize($element['#questions']); } // Build rows. @@ -115,16 +126,25 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ } $value = (isset($element['#value'][$question_key])) ? $element['#value'][$question_key] : NULL; + + // Get question id. + // @see \Drupal\Core\Form\FormBuilder::doBuildForm + $question_id = 'edit-' . implode('-', array_merge($element['#parents'], ['table', $question_key, 'likert_question'])); + $question_id = Html::getUniqueId($question_id); + $row = []; // Must format the label as an item so that inline webform errors will be // displayed. $row['likert_question'] = [ '#type' => 'item', '#title' => $question_title, + '#id' => $question_id, // Must include an empty <span> so that the item's value is // not required. '#value' => '<span></span>', + '#webform_element' => TRUE, '#required' => $element['#required'], + '#label_attributes' => ['webform-remove-for-attribute' => TRUE], ]; if ($question_description_property_name) { $row['likert_question'][$question_description_property_name] = $question_description; @@ -141,10 +161,15 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ // value is NULL. // @see \Drupal\Core\Render\Element\Radio::preRenderRadio '#value' => ($value === NULL) ? FALSE : (string) $value, + '#attributes' => ['aria-labelledby' => $question_id], ]; - // Wrap title in span.webform-likert-label so that it can hidden when - // Likert is displayed in grid on desktop. + // Wrap title in span.webform-likert-label.visually-hidden + // so that it can hidden but accessible to screen readers + // when Likert is displayed in grid on desktop. + // Wrap help and description in + // span.webform-likert-(help|description).hidden to block screen + // readers except on mobile. // @see webform.element.likert.css $row[$answer_key]['#title_display'] = 'after'; @@ -155,8 +180,11 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ 'help' => [ '#type' => 'webform_help', '#help' => $answer['description'], + '#help_title' => $answer['title'], + '#prefix' => '<span class="webform-likert-help hidden">', + '#suffix' => '</span>', ], - '#prefix' => '<span class="webform-likert-label">', + '#prefix' => '<span class="webform-likert-label visually-hidden">', '#suffix' => '</span>', ]; $row[$answer_key]['#title'] = \Drupal::service('renderer')->render($build); @@ -164,14 +192,14 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ case 'description': $row[$answer_key] += [ - '#title' => new FormattableMarkup('<span class="webform-likert-label">@title</span>', ['@title' => $answer['title']]), - '#description' => new FormattableMarkup('<span class="webform-likert-description">@description</span>', ['@description' => $answer['description']]), + '#title' => new FormattableMarkup('<span class="webform-likert-label visually-hidden">@title</span>', ['@title' => $answer['title']]), + '#description' => new FormattableMarkup('<span class="webform-likert-description hidden">@description</span>', ['@description' => $answer['description']]), ]; break; default: $row[$answer_key] += [ - '#title' => new FormattableMarkup('<span class="webform-likert-label">@title</span>', ['@title' => $answer['title']]), + '#title' => new FormattableMarkup('<span class="webform-likert-label visually-hidden">@title</span>', ['@title' => $answer['title']]), ]; } } @@ -181,10 +209,13 @@ public static function processWebformLikert(&$element, FormStateInterface $form_ $element['table'] = [ '#type' => 'table', '#header' => $header, + '#sticky' => $element['#sticky'], '#attributes' => [ 'class' => ['webform-likert-table'], 'data-likert-answers-count' => count($element['#answers']), ], + '#prefix' => '<div class="webform-likert-table-wrapper">', + '#suffix' => '</div>', ] + $rows; // Build table element with selected properties. diff --git a/web/modules/webform/src/Element/WebformLocation.php b/web/modules/webform/src/Element/WebformLocationBase.php similarity index 54% rename from web/modules/webform/src/Element/WebformLocation.php rename to web/modules/webform/src/Element/WebformLocationBase.php index 4ffdfb5a11..13843d80f6 100644 --- a/web/modules/webform/src/Element/WebformLocation.php +++ b/web/modules/webform/src/Element/WebformLocationBase.php @@ -5,11 +5,16 @@ use Drupal\Core\Form\FormStateInterface; /** - * Provides a webform element for a location element. - * - * @FormElement("webform_location") + * Provides a webform base element for a location element. */ -class WebformLocation extends WebformCompositeBase { +abstract class WebformLocationBase extends WebformCompositeBase { + + /** + * The location element's class name. + * + * @var string + */ + protected static $name; /** * {@inheritdoc} @@ -17,91 +22,62 @@ class WebformLocation extends WebformCompositeBase { public function getInfo() { return parent::getInfo() + [ '#theme' => 'webform_composite_location', - '#api_key' => '', - '#hidden' => FALSE, - '#geolocation' => FALSE, '#map' => FALSE, + '#geolocation' => FALSE, + '#hidden' => FALSE, ]; } + /** + * Get location attributes. + * + * @return array + * An associative array container location attribute name and titles. + */ + public static function getLocationAttributes() { + return []; + } + /** * {@inheritdoc} */ public static function getCompositeElements(array $element) { - // @see https://developers.google.com/maps/documentation/javascript/geocoding#GeocodingAddressTypes - $attributes = []; - $attributes['lat'] = [ - '#title' => t('Latitude'), - ]; - $attributes['lng'] = [ - '#title' => t('Longitude'), - ]; - $attributes['location'] = [ - '#title' => t('Location'), - ]; - $attributes['formatted_address'] = [ - '#title' => t('Formatted Address'), - ]; - $attributes['street_address'] = [ - '#title' => t('Street Address'), - ]; - $attributes['street_number'] = [ - '#title' => t('Street Number'), - ]; - $attributes['subpremise'] = [ - '#title' => t('Unit'), - ]; - $attributes['postal_code'] = [ - '#title' => t('Postal Code'), - ]; - $attributes['locality'] = [ - '#title' => t('Locality'), - ]; - $attributes['sublocality'] = [ - '#title' => t('City'), - ]; - $attributes['administrative_area_level_1'] = [ - '#title' => t('State/Province'), - ]; - $attributes['country'] = [ - '#title' => t('Country'), - ]; - $attributes['country_short'] = [ - '#title' => t('Country Code'), - ]; - - foreach ($attributes as $name => &$attribute_element) { - $attribute_element['#type'] = 'textfield'; - - $attribute_element['#attributes'] = [ - 'data-webform-location-attribute' => $name, - ]; - } - $elements = []; + $elements['value'] = [ '#type' => 'textfield', '#title' => t('Address'), '#attributes' => [ - 'class' => ['webform-location-geocomplete'], + 'class' => ['webform-location-' . static::$name], ], ]; - $elements += $attributes; + $attributes = static::getLocationAttributes(); + foreach ($attributes as $name => $title) { + $elements[$name] = [ + '#title' => $title, + '#type' => 'textfield', + '#error_no_message' => TRUE, + '#attributes' => [ + 'data-webform-location-' . static::$name . '-attribute' => $name, + ], + ]; + } + return $elements; } /** * {@inheritdoc} */ - public static function preRenderCompositeFormElement($element) { - $element = WebformCompositeBase::preRenderCompositeFormElement($element); - + public static function preRenderWebformCompositeFormElement($element) { // Hide location element webform display only if #geolocation is also set. if (!empty($element['#hidden']) && !empty($element['#geolocation'])) { $element['#wrapper_attributes']['style'] = 'display: none'; } + $element = WebformCompositeBase::preRenderWebformCompositeFormElement($element); + return $element; } @@ -132,32 +108,29 @@ public static function processWebformComposite(&$element, FormStateInterface $fo } } - // Set required. - if (isset($element['#required'])) { - $element['value']['#required'] = $element['#required']; - } + // Get shared properties. + $shared_properties = [ + '#required', + '#placeholder', + ]; + $element['value'] += array_intersect_key($element, array_combine($shared_properties, $shared_properties)); // Set Geolocation detection attribute. if (!empty($element['#geolocation'])) { - $element['value']['#attributes']['data-webform-location-geolocation'] = 'data-webform-location-geolocation'; + $element['value']['#attributes']['data-webform-location-' . static::$name . '-geolocation'] = 'data-webform-location-' . static::$name . '-geolocation'; } // Set Map attribute. if (!empty($element['#map']) && empty($element['#hidden'])) { - $element['value']['#attributes']['data-webform-location-map'] = 'data-webform-location-map'; + $element['value']['#attributes']['data-webform-location-' . static::$name . '-map'] = 'data-webform-location-' . static::$name . '-map'; } - // Add Google Maps API key which is required by - // https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places - // @see webform_js_alter() - $api_key = (!empty($element['#api_key'])) ? $element['#api_key'] : \Drupal::config('webform.settings')->get('element.default_google_maps_api_key'); - $element['#attached']['drupalSettings']['webform']['location']['google_maps_api_key'] = $api_key; - - $element['#attached']['library'][] = 'webform/webform.element.location'; - $element += ['#element_validate' => []]; array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformLocation']); + // Attach library. + $element['#attached']['library'][] = 'webform/webform.element.location.' . static::$name; + return $element; } @@ -168,9 +141,9 @@ public static function validateWebformLocation(&$element, FormStateInterface $fo $value = $element['#value']; $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); - if ($has_access && !empty($element['#required']) && empty($value['location'])) { + if ($has_access && !empty($element['#required']) && empty($value['lat'])) { $t_args = ['@title' => !empty($element['#title']) ? $element['#title'] : t('Location')]; - $form_state->setError($element, t('The @title is not valid.', $t_args)); + $form_state->setError($element['value'], t('The @title is not valid.', $t_args)); } } diff --git a/web/modules/webform/src/Element/WebformLocationGeocomplete.php b/web/modules/webform/src/Element/WebformLocationGeocomplete.php new file mode 100644 index 0000000000..cfef42a9aa --- /dev/null +++ b/web/modules/webform/src/Element/WebformLocationGeocomplete.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a webform element for a location geocomplete element. + * + * @FormElement("webform_location_geocomplete") + */ +class WebformLocationGeocomplete extends WebformLocationBase { + + /** + * {@inheritdoc} + */ + protected static $name = 'geocomplete'; + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#api_key' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getLocationAttributes() { + return [ + 'lat' => t('Latitude'), + 'lng' => t('Longitude'), + 'location' => t('Location'), + 'formatted_address' => t('Formatted Address'), + 'street_address' => t('Street Address'), + 'street_number' => t('Street Number'), + 'subpremise' => t('Unit'), + 'postal_code' => t('Postal Code'), + 'locality' => t('Locality'), + 'sublocality' => t('City'), + 'administrative_area_level_1' => t('State/Province'), + 'country' => t('Country'), + 'country_short' => t('Country Code'), + ]; + } + + /** + * {@inheritdoc} + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processWebformComposite($element, $form_state, $complete_form); + + // Add Google Maps API key which is required by + // https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places + // @see webform_js_alter() + $api_key = (!empty($element['#api_key'])) ? $element['#api_key'] : \Drupal::config('webform.settings')->get('element.default_google_maps_api_key'); + $element['#attached']['drupalSettings']['webform']['location']['geocomplete']['api_key'] = $api_key; + + return $element; + } + +} diff --git a/web/modules/webform/src/Element/WebformLocationPlaces.php b/web/modules/webform/src/Element/WebformLocationPlaces.php new file mode 100644 index 0000000000..ca3e6d1d31 --- /dev/null +++ b/web/modules/webform/src/Element/WebformLocationPlaces.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a webform element for a location places element. + * + * @FormElement("webform_location_places") + */ +class WebformLocationPlaces extends WebformLocationBase { + + /** + * {@inheritdoc} + */ + protected static $name = 'places'; + + /** + * {@inheritdoc} + */ + public function getInfo() { + return parent::getInfo() + [ + '#app_id' => '', + '#api_key' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function getLocationAttributes() { + return [ + 'lat' => t('Latitude'), + 'lng' => t('Longitude'), + 'name' => t('Name'), + 'city' => t('City'), + 'country' => t('Country'), + 'country_code' => t('Country Code'), + 'administrative' => t('State/Province'), + 'county' => t('County'), + 'suburb' => t('Suburb'), + 'postcode' => t('Postal Code'), + ]; + } + + /** + * {@inheritdoc} + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processWebformComposite($element, $form_state, $complete_form); + + // Add Algolia application id and API key. + $app_id = (!empty($element['#app_id'])) ? $element['#app_id'] : \Drupal::config('webform.settings')->get('element.default_algolia_places_app_id'); + $api_key = (!empty($element['#api_key'])) ? $element['#api_key'] : \Drupal::config('webform.settings')->get('element.default_algolia_places_api_key'); + $element['#attached']['drupalSettings']['webform']['location']['places'] = [ + 'app_id' => $app_id, + 'api_key' => $api_key, + ]; + + return $element; + } + +} diff --git a/web/modules/webform/src/Element/WebformManagedFileBase.php b/web/modules/webform/src/Element/WebformManagedFileBase.php index 450c2175e3..2f8864083e 100644 --- a/web/modules/webform/src/Element/WebformManagedFileBase.php +++ b/web/modules/webform/src/Element/WebformManagedFileBase.php @@ -17,7 +17,7 @@ abstract class WebformManagedFileBase extends ManagedFile { /** - * The the types of files that the server accepts. + * The types of files that the server accepts. * * @var string * diff --git a/web/modules/webform/src/Element/WebformMapping.php b/web/modules/webform/src/Element/WebformMapping.php index 302fb18cc8..85c9851764 100644 --- a/web/modules/webform/src/Element/WebformMapping.php +++ b/web/modules/webform/src/Element/WebformMapping.php @@ -60,6 +60,7 @@ public static function processWebformMapping(&$element, FormStateInterface $form $destination_element_base = [ '#title_display' => 'invisible', '#required' => ($element['#required'] === self::REQUIRED_ALL) ? TRUE : FALSE, + '#error_no_message' => ($element['#required'] !== self::REQUIRED_ALL) ? TRUE : FALSE, ]; // Get base #destination__* properties. @@ -71,8 +72,8 @@ public static function processWebformMapping(&$element, FormStateInterface $form // Build header. $header = [ - ['data' => ['#markup' => $element['#source__title'] . ' ' . $arrow], 'width' => '50%'], - ['data' => ['#markup' => $element['#destination__title']], 'width' => '50%'], + ['data' => ['#markup' => $element['#source__title'] . ' ' . $arrow]], + ['data' => ['#markup' => $element['#destination__title']]], ]; // Build rows. @@ -131,6 +132,8 @@ public static function processWebformMapping(&$element, FormStateInterface $form webform_process_states($element, '#wrapper_attributes'); } + $element['#attached']['library'][] = 'webform/webform.element.mapping'; + return $element; } diff --git a/web/modules/webform/src/Element/WebformMarkup.php b/web/modules/webform/src/Element/WebformMarkup.php index 78fd0148f8..ec5b83cf6f 100644 --- a/web/modules/webform/src/Element/WebformMarkup.php +++ b/web/modules/webform/src/Element/WebformMarkup.php @@ -15,7 +15,46 @@ class WebformMarkup extends RenderElement { * {@inheritdoc} */ public function getInfo() { - return []; + $class = get_class($this); + return [ + '#pre_render' => [ + [$class, 'preRenderWebformMarkup'], + ], + ]; + } + + /** + * Create webform markup for rendering. + * + * @param array $element + * An associative array containing the properties and children of the + * element. + * + * @return array + * The modified element with webform html markup. + */ + public static function preRenderWebformMarkup(array $element) { + // Make sure that #markup is defined. + if (!isset($element['#markup'])) { + return $element; + } + + // Replace #markup with renderable webform HTML editor markup. + $element['markup'] = WebformHtmlEditor::checkMarkup($element['#markup'], ['tidy' => FALSE]); + unset($element['#markup']); + + // Must set wrapper id attribute since we are no longer including #markup. + // @see template_preprocess_form_element() + if (isset($element['#theme_wrappers']) && !empty($element['#id'])) { + $element['#wrapper_attributes']['id'] = $element['#id']; + } + + // Sent #name property which is used by form-item-* classes. + if (!isset($element['#name']) && isset($element['#webform_key'])) { + $element['#name'] = $element['#webform_key']; + } + + return $element; } } diff --git a/web/modules/webform/src/Element/WebformMultiple.php b/web/modules/webform/src/Element/WebformMultiple.php index 6dedf4aee8..0ebce0005a 100644 --- a/web/modules/webform/src/Element/WebformMultiple.php +++ b/web/modules/webform/src/Element/WebformMultiple.php @@ -7,6 +7,7 @@ use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Template\Attribute; +use Drupal\webform\Utility\WebformAccessibilityHelper; use Drupal\webform\Utility\WebformElementHelper; /** @@ -29,20 +30,22 @@ public function getInfo() { return [ '#input' => TRUE, '#access' => TRUE, - '#label' => t('item'), - '#labels' => t('items'), '#key' => NULL, '#header' => NULL, '#element' => [ '#type' => 'textfield', '#title' => t('Item value'), '#title_display' => 'invisible', - '#placeholder' => t('Enter value'), + '#placeholder' => t('Enter value…'), ], '#cardinality' => FALSE, - '#min_items' => 1, + '#min_items' => NULL, + '#no_items_message' => $this->t('No items entered. Please add items below.'), '#empty_items' => 1, - '#add_more' => 1, + '#add_more' => TRUE, + '#add_more_items' => 1, + '#add_more_button_label' => $this->t('Add'), + '#add_more_input_label' => $this->t('more items'), '#sorting' => TRUE, '#operations' => TRUE, '#add' => TRUE, @@ -76,7 +79,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form return static::convertValuesToItems($element, $input['items']); } else { - return NULL; + return []; } } @@ -87,9 +90,22 @@ public static function processWebformMultiple(&$element, FormStateInterface $for // Set tree. $element['#tree'] = TRUE; - // Disable operation when #cardinality is set. - if (!empty($element['#cardinality'])) { - $element['#operations'] = FALSE; + // Remove 'for' from the element's label. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + + // Set min items based on when the element is required. + if (!isset($element['#min_items']) || $element['#min_items'] === '') { + $element['#min_items'] = (empty($element['#required'])) ? 0 : 1; + } + + // Make sure min items does not exceed cardinality. + if (!empty($element['#cardinality']) && $element['#min_items'] > $element['#cardinality']) { + $element['#min_items'] = $element['#cardinality']; + } + + // Make sure empty items does not exceed cardinality. + if (!empty($element['#cardinality']) && $element['#empty_items'] > $element['#cardinality']) { + $element['#empty_items'] = $element['#cardinality']; } // Add validate callback that extracts the array of items. @@ -99,40 +115,50 @@ public static function processWebformMultiple(&$element, FormStateInterface $for // Wrap this $element in a <div> that handle #states. WebformElementHelper::fixStatesWrapper($element); - if ($element['#cardinality']) { - // If the cardinality is set limit number of items to this value. - $number_of_items = $element['#cardinality']; - } - else { - // Get unique key used to store the current number of items. - $number_of_items_storage_key = static::getStorageKey($element, 'number_of_items'); - - // Store the number of items which is the number of - // #default_values + number of empty_items. - if ($form_state->get($number_of_items_storage_key) === NULL) { - if (empty($element['#default_value']) || !is_array($element['#default_value'])) { - $number_of_default_values = 0; - } - else { - $number_of_default_values = count($element['#default_value']); - } - $number_of_empty_items = (int) $element['#empty_items']; - $number_of_items = $number_of_default_values + $number_of_empty_items; + // Get unique key used to store the current number of items. + $number_of_items_storage_key = static::getStorageKey($element, 'number_of_items'); - $min_items = (int) $element['#min_items']; - $number_of_items = ($number_of_items < $min_items) ? $min_items : $number_of_items; + // Store the number of items which is the number of + // #default_values + number of empty_items. + if ($form_state->get($number_of_items_storage_key) === NULL) { + if (empty($element['#default_value']) || !is_array($element['#default_value'])) { + $number_of_default_values = 0; + } + else { + $number_of_default_values = count($element['#default_value']); + } + $number_of_empty_items = (int) $element['#empty_items']; + $number_of_items = $number_of_default_values + $number_of_empty_items; - if ($number_of_items < 1) { - $number_of_items = 1; - } - $form_state->set($number_of_items_storage_key, $number_of_items); + // Make sure number of items is greated than min items. + $min_items = (int) $element['#min_items']; + $number_of_items = ($number_of_items < $min_items) ? $min_items : $number_of_items; + + // Make sure number of (default) items does not exceed cardinality. + if (!empty($element['#cardinality']) && $number_of_items > $element['#cardinality']) { + $number_of_items = $element['#cardinality']; } - $number_of_items = $form_state->get($number_of_items_storage_key); + $form_state->set($number_of_items_storage_key, $number_of_items); } + $number_of_items = $form_state->get($number_of_items_storage_key); + $table_id = implode('_', $element['#parents']) . '_table'; + // Disable add operation when #cardinality is met + // and make sure to limit the number of items. + if (!empty($element['#cardinality']) && $number_of_items >= $element['#cardinality']) { + $element['#add'] = FALSE; + $number_of_items = $element['#cardinality']; + $form_state->set($number_of_items_storage_key, $number_of_items); + } + + // Add wrapper to the element. + $element += ['#prefix' => '', '#suffix' => '']; + $element['#prefix'] = '<div id="' . $table_id . '">' . $element['#prefix']; + $element['#suffix'] .= '</div>'; + // DEBUG: // Disable Ajax callback by commenting out the below callback and wrapper. $ajax_settings = [ @@ -152,7 +178,6 @@ public static function processWebformMultiple(&$element, FormStateInterface $for $weight = 0; $rows = []; - if (!$form_state->isProcessingInput() && isset($element['#default_value']) && is_array($element['#default_value'])) { $default_values = $element['#default_value']; } @@ -185,48 +210,65 @@ public static function processWebformMultiple(&$element, FormStateInterface $for } // Build table. - $attributes = ['id' => $table_id, 'class' => ['webform-multiple-table']]; + $attributes = ['class' => ['webform-multiple-table']]; if (count($element['#element']) > 1) { $attributes['class'][] = 'webform-multiple-table-responsive'; } $element['items'] = [ '#prefix' => '<div' . new Attribute($attributes) . '>', '#suffix' => '</div>', - '#type' => 'table', - '#header' => $header, ] + $rows; - // Add sorting to table. - if ($element['#sorting']) { - $element['items']['#tabledrag'] = [ - [ - 'action' => 'order', - 'relationship' => 'sibling', - 'group' => 'webform-multiple-sort-weight', - ], + // Display table if there are any rows. + if ($rows) { + $element['items'] += [ + '#type' => 'table', + '#header' => $header, + ] + $rows; + + // Add sorting to table. + if ($element['#sorting']) { + $element['items']['#tabledrag'] = [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'webform-multiple-sort-weight', + ], + ]; + } + } + elseif (!empty($element['#no_items_message'])) { + $element['items'] += [ + '#type' => 'webform_message', + '#message_message' => $element['#no_items_message'], + '#message_type' => 'info', + '#attributes' => ['class' => ['webform-multiple-table--no-items-message']], ]; } - // Build add items actions. - if (empty($element['#cardinality'])) { + // Build add more actions. + if ($element['#add_more'] && (empty($element['#cardinality']) || ($number_of_items < $element['#cardinality']))) { $element['add'] = [ - '#prefix' => '<div class="container-inline">', + '#prefix' => '<div class="webform-multiple-add js-webform-multiple-add container-inline">', '#suffix' => '</div>', ]; $element['add']['submit'] = [ '#type' => 'submit', - '#value' => t('Add'), + '#value' => $element['#add_more_button_label'], '#limit_validation_errors' => [], '#submit' => [[get_called_class(), 'addItemsSubmit']], '#ajax' => $ajax_settings, '#name' => $table_id . '_add', ]; + $max = ($element['#cardinality']) ? $element['#cardinality'] - $number_of_items : 100; $element['add']['more_items'] = [ '#type' => 'number', + '#title' => $element['#add_more_button_label'] . ' ' . $element['#add_more_input_label'], + '#title_display' => 'invisible', '#min' => 1, - '#max' => 100, - '#default_value' => $element['#add_more'], - '#field_suffix' => t('more @labels', ['@labels' => $element['#labels']]), + '#max' => $max, + '#default_value' => $element['#add_more_items'], + '#field_suffix' => $element['#add_more_input_label'], '#error_no_message' => TRUE, ]; } @@ -244,7 +286,7 @@ public static function processWebformMultiple(&$element, FormStateInterface $for * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form - * An associative array containing the structure of the form. + * An associative array containing the structure of the form. */ protected static function initializeElement(array &$element, FormStateInterface $form_state, array &$complete_form) { // Track element child keys. @@ -277,11 +319,11 @@ protected static function initializeElement(array &$element, FormStateInterface * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form - * An associative array containing the structure of the form. + * An associative array containing the structure of the form. * @param array $sub_elements * The sub element. * @param array $required_states - * An associative array of required states froim the main element's + * An associative array of required states from the main element's * visible/hidden states. */ protected static function initializeElementRecursive(array $element, FormStateInterface $form_state, array &$complete_form, array &$sub_elements, array $required_states) { @@ -360,24 +402,36 @@ protected static function buildElementHeader(array $element) { if (empty($element['#header'])) { return [ - ['data' => '', 'colspan' => ($colspan + 1)], + [ + 'data' => (!empty($element['#title'])) ? WebformAccessibilityHelper::buildVisuallyHidden($element['#title']) : [], + 'colspan' => ($colspan + 1), + ], ]; } elseif (is_array($element['#header'])) { $header = []; if ($element['#sorting']) { - $header[] = ['class' => ["$table_id--handle", "webform-multiple-table--handle"]]; + $header[] = [ + 'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Re-order')), + 'class' => ["$table_id--handle", 'webform-multiple-table--handle'], + ]; } $header = array_merge($header, $element['#header']); if ($element['#sorting']) { - $header[] = ['class' => ["$table_id--weight", "webform-multiple-table--weight"]]; + $header[] = [ + 'data' => ['#markup' => t('Weight')], + 'class' => ["$table_id--weight", 'webform-multiple-table--weight'], + ]; } if ($element['#operations']) { - $header[] = ['class' => ["$table_id--handle", "webform-multiple-table--operations"]]; + $header[] = [ + 'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Operations')), + 'class' => ["$table_id--handle", 'webform-multiple-table--operations'], + ]; } return $header; @@ -391,7 +445,10 @@ protected static function buildElementHeader(array $element) { $header = []; if ($element['#sorting']) { - $header['_handle_'] = ['class' => ["$table_id--handle", "webform-multiple-table--handle"]]; + $header['_handle_'] = [ + 'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Re-order')), + 'class' => ["$table_id--handle", "webform-multiple-table--handle"], + ]; } if ($element['#child_keys']) { @@ -400,9 +457,11 @@ protected static function buildElementHeader(array $element) { continue; } + $child_title = (!empty($element['#element'][$child_key]['#title'])) ? $element['#element'][$child_key]['#title'] : ''; + $title = []; $title['title'] = [ - '#markup' => (!empty($element['#element'][$child_key]['#title'])) ? $element['#element'][$child_key]['#title'] : '', + '#markup' => $child_title, ]; if (!empty($element['#element'][$child_key]['#required']) || !empty($element['#element'][$child_key]['#_required'])) { $title['title'] += [ @@ -414,6 +473,7 @@ protected static function buildElementHeader(array $element) { $title['help'] = [ '#type' => 'webform_help', '#help' => $element['#element'][$child_key]['#help'], + '#help_title' => $child_title, ]; } $header[$child_key] = [ @@ -438,6 +498,7 @@ protected static function buildElementHeader(array $element) { if ($element['#operations']) { $header['_operations_'] = [ + 'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Operations')), 'class' => ["$table_id--operations", "webform-multiple-table--operations"], ]; } @@ -510,11 +571,14 @@ protected static function buildElementRow($table_id, $row_index, array $element, if (!isset($element['#access']) || $element['#access'] !== FALSE) { $hidden_elements[$child_key]['#type'] = 'hidden'; // Unset #access, #element_validate, and #pre_render. - // @see \Drupal\webform\Plugin\WebformElementBase::prepare(). + // @see \Drupal\webform\Plugin\WebformElementBase::prepare() + // Unset #options to prevent An illegal choice has been detected. + // @see \Drupal\Core\Form\FormValidator::performRequiredValidation unset( $hidden_elements[$child_key]['#access'], $hidden_elements[$child_key]['#element_validate'], - $hidden_elements[$child_key]['#pre_render'] + $hidden_elements[$child_key]['#pre_render'], + $hidden_elements[$child_key]['#options'] ); } static::setElementRowParentsRecursive($hidden_elements[$child_key], $child_key, $hidden_parents); @@ -624,7 +688,7 @@ protected static function isHidden(array $element) { } /** - * Set element row default value recusively. + * Set element row default value recursively. * * @param array $element * The element. @@ -641,7 +705,7 @@ protected static function setElementRowDefaultValueRecursive(array &$element, ar } /** - * Set element row default value recusively. + * Set element row default value recursively. * * @param array $element * The element. @@ -707,14 +771,15 @@ public static function addItemsSubmit(array &$form, FormStateInterface $form_sta $form_state->set($number_of_items_storage_key, $number_of_items + $more_items); // Reset values. - $element['items']['#value'] = array_values($element['items']['#value']); - $form_state->setValueForElement($element['items'], $element['items']['#value']); - NestedArray::setValue($form_state->getUserInput(), $element['items']['#parents'], $element['items']['#value']); + $items = (!empty($element['items']['#value'])) ? array_values($element['items']['#value']) : []; + $element['items']['#value'] = $items; + $form_state->setValueForElement($element['items'], $items); + NestedArray::setValue($form_state->getUserInput(), $element['items']['#parents'], $items); $action_key = static::getStorageKey($element, 'action'); $form_state->set($action_key, TRUE); - // Rebuild the webform. + // Rebuild the form. $form_state->setRebuild(); } @@ -751,7 +816,7 @@ public static function addItemSubmit(array &$form, FormStateInterface $form_stat $action_key = static::getStorageKey($element, 'action'); $form_state->set($action_key, TRUE); - // Rebuild the webform. + // Rebuild the form. $form_state->setRebuild(); } @@ -775,8 +840,8 @@ public static function removeItemSubmit(array &$form, FormStateInterface $form_s // Remove one item from the 'number of items'. $number_of_items_storage_key = static::getStorageKey($element, 'number_of_items'); $number_of_items = $form_state->get($number_of_items_storage_key); - // Never allow the number of items to be less than 1. - if ($number_of_items != 1) { + // Never allow the number of items to be less than #min_items. + if ($number_of_items > $element['#min_items']) { $form_state->set($number_of_items_storage_key, $number_of_items - 1); } @@ -787,7 +852,7 @@ public static function removeItemSubmit(array &$form, FormStateInterface $form_s $action_key = static::getStorageKey($element, 'action'); $form_state->set($action_key, TRUE); - // Rebuild the webform. + // Rebuild the form. $form_state->setRebuild(); } @@ -798,11 +863,11 @@ public static function ajaxCallback(array &$form, FormStateInterface $form_state $button = $form_state->getTriggeringElement(); $parent_length = (isset($button['#row_index'])) ? -4 : -2; $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, $parent_length)); - return $element['items']; + return $element; } /** - * Validates webform list element. + * Validates webform multiple element. */ public static function validateWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) { // IMPORTANT: Must get values from the $form_states since sub-elements @@ -811,18 +876,27 @@ public static function validateWebformMultiple(&$element, FormStateInterface $fo // @see \Drupal\webform\Element\WebformOtherBase::validateWebformOther $values = NestedArray::getValue($form_state->getValues(), $element['#parents']); - // Validate unique keys. - if ($error_message = static::validateUniqueKeys($element, $values['items'])) { - $form_state->setError($element, $error_message); - return; - } + $number_of_items_storage_key = static::getStorageKey($element, 'number_of_items'); + $number_of_items = $form_state->get($number_of_items_storage_key); + if (!empty($values['items']) && ($number_of_items || $element['#cardinality'])) { + $items = $values['items']; - // Convert values to items and validate duplicate keys. - $items = static::convertValuesToItems($element, $values['items']); + // Validate unique keys. + if ($error_message = static::validateUniqueKeys($element, $items)) { + $form_state->setError($element, $error_message); + return; + } - // Validate required items. - if (!empty($element['#required']) && empty($items)) { - WebformElementHelper::setRequiredError($element, $form_state); + // Convert values to items and validate duplicate keys. + $items = static::convertValuesToItems($element, $items); + + // Validate required items. + if (!empty($element['#required']) && empty($items)) { + WebformElementHelper::setRequiredError($element, $form_state); + } + } + else { + $items = []; } $element['#value'] = $items; @@ -871,20 +945,7 @@ public static function convertValuesToItems(array $element, array $values = []) // Now build the associative array of items. $items = []; foreach ($values as $value) { - $item = NULL; - if (isset($value['_item_'])) { - $item = $value['_item_']; - } - else { - // Get hidden (#access: FALSE) elements in the '_handle_' column and - // add them to the $value. - // @see \Drupal\webform\Element\WebformMultiple::buildElementRow - if (isset($value['_hidden_']) && is_array($value['_hidden_'])) { - $value += $value['_hidden_']; - } - unset($value['weight'], $value['_operations_'], $value['_hidden_']); - $item = $value; - } + $item = static::convertValueToItem($value); // Never add an empty item. if (static::isEmpty($item)) { @@ -906,6 +967,31 @@ public static function convertValuesToItems(array $element, array $values = []) return $items; } + /** + * Convert value array containing (elements or _item_ and weight) to an item. + * + * @param array $value + * The multiple value array. + * + * @return array + * An item array. + */ + public static function convertValueToItem(array $value) { + if (isset($value['_item_'])) { + return $value['_item_']; + } + else { + // Get hidden (#access: FALSE) elements in the '_handle_' column and + // add them to the $value. + // @see \Drupal\webform\Element\WebformMultiple::buildElementRow + if (isset($value['_hidden_']) && is_array($value['_hidden_'])) { + $value += $value['_hidden_']; + } + unset($value['weight'], $value['_operations_'], $value['_hidden_']); + return $value; + } + } + /** * Validate composite element has unique keys. * @@ -926,15 +1012,20 @@ protected static function validateUniqueKeys(array $element, array $values) { $unique_keys = []; foreach ($values as $value) { - $item = (isset($value['_item_'])) ? $value['_item_'] : $value; + $item = static::convertValueToItem($value); + $key_name = $element['#key']; $key_value = $item[$key_name]; - if (empty($key_value)) { + + // Skip empty key and item. + unset($item[$key_name]); + if (empty($key_value) && static::isEmpty($item)) { continue; } if (isset($unique_keys[$key_value])) { - $key_title = isset($element['#element'][$key_name]['#title']) ? $element['#element'][$key_name]['#title'] : $key_name; + $elements = WebformElementHelper::getFlattened($element['#element']); + $key_title = isset($elements[$key_name]['#title']) ? $elements[$key_name]['#title'] : $key_name; $t_args = ['@key' => $key_value, '%title' => $key_title]; return t("The %title '@key' is already in use. It must be unique.", $t_args); } diff --git a/web/modules/webform/src/Element/WebformOptions.php b/web/modules/webform/src/Element/WebformOptions.php index f6d1c57545..1d003a0cfe 100644 --- a/web/modules/webform/src/Element/WebformOptions.php +++ b/web/modules/webform/src/Element/WebformOptions.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformOptionsHelper; +use Drupal\webform\Utility\WebformYaml; /** * Provides a webform element to assist in creation of options. @@ -30,8 +31,9 @@ public function getInfo() { '#yaml' => FALSE, '#label' => t('option'), '#labels' => t('options'), - '#empty_items' => 5, - '#add_more' => 1, + '#min_items' => 3, + '#empty_items' => 1, + '#add_more_items' => 1, '#options_value_maxlength' => 255, '#options_text_maxlength' => 255, '#options_description' => FALSE, @@ -85,8 +87,8 @@ public static function processWebformOptions(&$element, FormStateInterface $form $element['options'] = [ '#type' => 'webform_codemirror', '#mode' => 'yaml', - '#default_value' => trim(Yaml::encode($element['#default_value'])), - '#placeholder' => t('Enter custom options'), + '#default_value' => WebformYaml::encode($element['#default_value']), + '#placeholder' => t('Enter custom options…'), '#description' => t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.') . '<br /><br />' . t('Option groups can be created by using just the group name followed by indented group options.'), ]; @@ -94,62 +96,84 @@ public static function processWebformOptions(&$element, FormStateInterface $form } else { $t_args = ['@label' => isset($element['#label']) ? Unicode::ucfirst($element['#label']) : t('Options')]; - $properties = ['#label', '#labels', '#empty_items', '#add_more']; + $properties = ['#label', '#labels', '#min_items', '#empty_items', '#add_more_items']; $element['options'] = array_intersect_key($element, array_combine($properties, $properties)) + [ '#type' => 'webform_multiple', '#header' => TRUE, + '#key' => 'value', '#default_value' => (isset($element['#default_value'])) ? static::convertOptionsToValues($element['#default_value'], $element['#options_description']) : [], + '#add_more_input_label' => t('more options'), ]; if ($element['#options_description']) { $element['options']['#element'] = [ - 'value' => [ - '#type' => 'textfield', + 'option_value' => [ + '#type' => 'container', '#title' => t('@label value', $t_args), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter value'), - '#attributes' => ['class' => ['js-webform-options-value']], - '#maxlength' => $element['#options_value_maxlength'], + '#help' => t('A unique value stored in the database.'), + 'value' => [ + '#type' => 'textfield', + '#title' => t('@label value', $t_args), + '#title_display' => 'invisible', + '#placeholder' => t('Enter value…'), + '#attributes' => ['class' => ['js-webform-options-sync']], + '#maxlength' => $element['#options_value_maxlength'], + '#error_no_message' => TRUE, + ], ], - 'option' => [ + 'option_text' => [ '#type' => 'container', - '#title' => t('@label text/description', $t_args), - '#title_display' => t('invisible'), + '#title' => t('@label text / description', $t_args), + '#help' => t('Enter text and description to be displayed on the form.'), 'text' => [ '#type' => 'textfield', '#title' => t('@label text', $t_args), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter text'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter text…'), '#maxlength' => $element['#options_text_maxlength'], + '#error_no_message' => TRUE, ], 'description' => [ '#type' => 'textarea', '#title' => t('@label description', $t_args), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter description'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter description…'), '#rows' => 2, '#maxlength' => $element['#options_description_maxlength'], + '#error_no_message' => TRUE, ], ], ]; } else { $element['options']['#element'] = [ - 'value' => [ - '#type' => 'textfield', + 'option_value' => [ + '#type' => 'container', '#title' => t('@label value', $t_args), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter value'), - '#attributes' => ['class' => ['js-webform-options-value']], - '#maxlength' => $element['#options_value_maxlength'], + '#help' => t('A unique value stored in the database.'), + 'value' => [ + '#type' => 'textfield', + '#title' => t('@label value', $t_args), + '#title_display' => 'invisible', + '#placeholder' => t('Enter value…'), + '#attributes' => ['class' => ['js-webform-options-sync']], + '#maxlength' => $element['#options_value_maxlength'], + '#error_no_message' => TRUE, + ], ], - 'text' => [ - '#type' => 'textfield', + 'option_text' => [ + '#type' => 'container', '#title' => t('@label text', $t_args), - '#title_display' => t('invisible'), - '#placeholder' => t('Enter text'), - '#maxlength' => $element['#options_text_maxlength'], + '#help' => t('Text to be displayed on the form.'), + 'text' => [ + '#type' => 'textfield', + '#title' => t('@label text', $t_args), + '#title_display' => 'invisible', + '#placeholder' => t('Enter text…'), + '#maxlength' => $element['#options_text_maxlength'], + '#error_no_message' => TRUE, + ], ], ]; } @@ -163,6 +187,10 @@ public static function processWebformOptions(&$element, FormStateInterface $form * Validates webform options element. */ public static function validateWebformOptions(&$element, FormStateInterface $form_state, &$complete_form) { + if ($form_state->hasAnyErrors()) { + return; + } + $options_value = NestedArray::getValue($form_state->getValues(), $element['options']['#parents']); if (is_string($options_value)) { @@ -200,11 +228,10 @@ public static function validateWebformOptions(&$element, FormStateInterface $for public static function convertValuesToOptions(array $values = NULL, $options_description = FALSE) { $options = []; if ($values && is_array($values)) { - foreach ($values as $value) { - $option_value = $value['value']; - $option_text = $value['text']; - if ($options_description && !empty($value['description'])) { - $option_text .= WebformOptionsHelper::DESCRIPTION_DELIMITER . $value['description']; + foreach ($values as $option_value => $option) { + $option_text = $option['text']; + if ($options_description && !empty($option['description'])) { + $option_text .= WebformOptionsHelper::DESCRIPTION_DELIMITER . $option['description']; } // Populate empty option value or option text. @@ -222,7 +249,7 @@ public static function convertValuesToOptions(array $values = NULL, $options_des } /** - * Convert options to values for yamform_multiple element. + * Convert options to values for webform_multiple element. * * @param array $options * An array of options. @@ -237,10 +264,10 @@ public static function convertOptionsToValues(array $options = [], $options_desc foreach ($options as $value => $text) { if ($options_description && strpos($text, WebformOptionsHelper::DESCRIPTION_DELIMITER) !== FALSE) { list($text, $description) = explode(WebformOptionsHelper::DESCRIPTION_DELIMITER, $text); - $values[] = ['value' => $value, 'text' => $text, 'description' => $description]; + $values[$value] = ['text' => $text, 'description' => $description]; } else { - $values[] = ['value' => $value, 'text' => $text]; + $values[$value] = ['text' => $text]; } } return $values; diff --git a/web/modules/webform/src/Element/WebformOtherBase.php b/web/modules/webform/src/Element/WebformOtherBase.php index 4f4d5dfcf6..625c0cf9e1 100644 --- a/web/modules/webform/src/Element/WebformOtherBase.php +++ b/web/modules/webform/src/Element/WebformOtherBase.php @@ -5,7 +5,6 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\OptGroup; -use Drupal\Core\Render\Element\CompositeFormElementTrait; use Drupal\Core\Render\Element\FormElement; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformOptionsHelper; @@ -15,7 +14,7 @@ */ abstract class WebformOtherBase extends FormElement { - use CompositeFormElementTrait; + use WebformCompositeFormElementTrait; /** * Other option value. @@ -37,8 +36,11 @@ abstract class WebformOtherBase extends FormElement { protected static $properties = [ '#title', '#required', + '#required_error', '#options', '#options_display', + '#options_randomize', + '#options_description_display', '#default_value', '#attributes', ]; @@ -55,7 +57,7 @@ public function getInfo() { [$class, 'processAjaxForm'], ], '#pre_render' => [ - [$class, 'preRenderCompositeFormElement'], + [$class, 'preRenderWebformCompositeFormElement'], ], '#options' => [], '#other__option_delimiter' => ', ', @@ -101,21 +103,21 @@ public static function processWebformOther(&$element, FormStateInterface $form_s $element['#tree'] = TRUE; $element[$type]['#type'] = static::$type; + $element[$type]['#webform_element'] = TRUE; $element[$type] += array_intersect_key($element, array_combine($properties, $properties)); $element[$type]['#title_display'] = 'invisible'; if (!isset($element[$type]['#options'][static::OTHER_OPTION])) { - $element[$type]['#options'][static::OTHER_OPTION] = (!empty($element['#other__option_label'])) ? $element['#other__option_label'] : t('Other...'); + $element[$type]['#options'][static::OTHER_OPTION] = (!empty($element['#other__option_label'])) ? $element['#other__option_label'] : t('Other…'); } $element[$type]['#error_no_message'] = TRUE; - // Disable label[for] which does not point to any specific element. - // @see webform_preprocess_form_element_label() - if (in_array($type, ['radios', 'checkboxes', 'buttons'])) { - $element['#label_attributes']['for'] = FALSE; - } + // Prevent nested fieldset by removing fieldset theme wrapper around + // radios and checkboxes. + // @see \Drupal\Core\Render\Element\CompositeFormElementTrait + $element[$type]['#pre_render'] = []; // Build other textfield. - $element['other']['#error_no_message'] = TRUE; + $element += ['other' => []]; foreach ($element as $key => $value) { if (strpos($key, '#other__') === 0) { $other_key = str_replace('#other__', '#', $key); @@ -126,7 +128,8 @@ public static function processWebformOther(&$element, FormStateInterface $form_s } $element['other'] += [ '#type' => 'textfield', - '#placeholder' => t('Enter other...'), + '#webform_element' => TRUE, + '#placeholder' => t('Enter other…'), ]; if (!isset($element['other']['#title'])) { $element['other'] += [ @@ -150,9 +153,21 @@ public static function processWebformOther(&$element, FormStateInterface $form_s $element['other']['#parents'] = array_merge($element['#parents'], ['other']); } - // Add js trigger to fieldset. - $element['#attributes']['class'][] = "js-webform-$type-other"; - $element['#attributes']['class'][] = "webform-$type-other"; + // Initialize the other element to allow for webform enhancements. + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + $element_manager->buildElement($element['other'], $complete_form, $form_state); + + // Add js trigger attributes to the composite wrapper. + // @see \Drupal\webform\Element\WebformCompositeFormElementTrait + $is_form_element_wrapper = (isset($element['#wrapper_type']) && $element['#wrapper_type'] === 'form_element'); + $wrapper_attributes = ($is_form_element_wrapper) ? '#wrapper_attributes' : '#attributes'; + $element[$wrapper_attributes]['class'][] = "js-webform-$type-other"; + $element[$wrapper_attributes]['class'][] = "webform-$type-other"; + + // Apply the element id to the wrapper so that inline form errors point + // to the correct element. + $element['#attributes']['id'] = $element['#id']; // Remove options. unset($element['#options']); @@ -174,58 +189,47 @@ public static function processWebformOther(&$element, FormStateInterface $form_s * Validates an other element. */ public static function validateWebformOther(&$element, FormStateInterface $form_state, &$complete_form) { + $type = static::getElementType(); + // Determine if the element is visible. (#access !== FALSE) $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); - // Remove 'webform_' prefix from type. - $type = str_replace('webform_', '', static::$type); + // Determine if the element has mulitple values. + $is_multiple = static::isMultiple($element); // Get value. $value = NestedArray::getValue($form_state->getValues(), $element['#parents']); // Get return value. - $return_value = []; - $element_value = $value[$type]; - $other_value = $value['other']; - $required_error_title = (isset($element['#title'])) ? $element['#title'] : NULL; - if (static::isMultiple($element)) { - $element_value = array_filter($element_value); - $element_value = array_combine($element_value, $element_value); - $return_value += $element_value; - if (isset($return_value[static::OTHER_OPTION])) { - unset($return_value[static::OTHER_OPTION]); - if ($has_access && $other_value === '') { - WebformElementHelper::setRequiredError($element, $form_state, $required_error_title); - } - else { - $return_value += [$other_value => $other_value]; - } - } - } - else { - $return_value = $element_value; - if ($element_value == static::OTHER_OPTION) { - if ($has_access && $other_value === '') { - WebformElementHelper::setRequiredError($element, $form_state, $required_error_title); - $return_value = ''; - } - else { - $return_value = $other_value; - } - } - } + $return_value = static::processValue($element, $value); // Determine if the return value is empty. - if (static::isMultiple($element)) { + if ($is_multiple) { $is_empty = (empty($return_value)) ? TRUE : FALSE; } else { $is_empty = ($return_value === '' || $return_value === NULL) ? TRUE : FALSE; } - // Handler required validation. - if ($element['#required'] && $is_empty && $has_access) { - WebformElementHelper::setRequiredError($element, $form_state, $required_error_title); + // Determine if there is an other value and is the other value empty. + $element_value = (array) $value[$type]; + $other_value = $value['other']; + if ($element_value) { + $element_value = array_filter($element_value); + $element_value = array_combine($element_value, $element_value); + } + $other_is_empty = (isset($element_value[static::OTHER_OPTION]) && $other_value === ''); + + // Display missing other or missing value error. + if ($has_access) { + $required_error_title = (isset($element['#title'])) ? $element['#title'] : NULL; + if ($other_is_empty) { + WebformElementHelper::setRequiredError($element['other'], $form_state, $required_error_title); + } + elseif ($element['#required'] && $is_empty) { + WebformElementHelper::setRequiredError($element, $form_state, $required_error_title); + $element['other']['#error_no_message'] = TRUE; + } } $form_state->setValueForElement($element[$type], NULL); @@ -235,10 +239,54 @@ public static function validateWebformOther(&$element, FormStateInterface $form_ $form_state->setValueForElement($element, $return_value); } + /** + * Processed other element's submitted value. + * + * @param array $element + * The element. + * @param array $value + * The submitted value. + * + * @return array|string + * An array of values or a string. + */ + public static function processValue(array $element, array $value) { + $type = static::getElementType(); + + $element_value = $value[$type]; + $other_value = $value['other']; + + if (static::isMultiple($element)) { + $return_value = array_filter($element_value); + $return_value = array_combine($return_value, $return_value); + if (isset($return_value[static::OTHER_OPTION])) { + unset($return_value[static::OTHER_OPTION]); + if ($other_value !== '') { + $return_value += [$other_value => $other_value]; + } + } + return $return_value; + } + else { + return ($element_value === static::OTHER_OPTION) ? $other_value : $element_value; + } + } + /****************************************************************************/ // Helper functions. /****************************************************************************/ + /** + * Get the element type. + * + * @return string + * The element type. + */ + public static function getElementType() { + // Remove 'webform_' prefix from type. + return str_replace('webform_', '', static::$type); + } + /** * Determine if the webform element contains multiple values. * diff --git a/web/modules/webform/src/Element/WebformPermissions.php b/web/modules/webform/src/Element/WebformPermissions.php index b7b420befb..43a0519c7c 100644 --- a/web/modules/webform/src/Element/WebformPermissions.php +++ b/web/modules/webform/src/Element/WebformPermissions.php @@ -43,14 +43,15 @@ public static function processSelect(&$element, FormStateInterface $form_state, $options[$display_name][$perm] = strip_tags($perm_item['title']); } $element['#options'] = $options; - - WebformElementHelper::enhanceSelect($element, TRUE); + $element['#select2'] = TRUE; // Must convert this element['#type'] to a 'select' to prevent // "Illegal choice %choice in %name element" validation error. // @see \Drupal\Core\Form\FormValidator::performRequiredValidation $element['#type'] = 'select'; + WebformElementHelper::process($element); + return parent::processSelect($element, $form_state, $complete_form); } diff --git a/web/modules/webform/src/Element/WebformRating.php b/web/modules/webform/src/Element/WebformRating.php index 5b18b7f00d..67a736adf3 100644 --- a/web/modules/webform/src/Element/WebformRating.php +++ b/web/modules/webform/src/Element/WebformRating.php @@ -2,8 +2,10 @@ namespace Drupal\webform\Element; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\Range; use Drupal\Core\Render\Element; +use Drupal\webform\Utility\WebformElementHelper; /** * Provides a webform element for entering a rating. @@ -23,6 +25,9 @@ public function getInfo() { '#step' => 1, '#star_size' => 'medium', '#reset' => FALSE, + '#process' => [ + [$class, 'processWebformRating'], + ], '#pre_render' => [ [$class, 'preRenderWebformRating'], ], @@ -30,6 +35,15 @@ public function getInfo() { ] + parent::getInfo(); } + /** + * Expand rating elements. + */ + public static function processWebformRating(&$element, FormStateInterface $form_state, &$complete_form) { + // Add validate callback. + $element['#element_validate'] = [[get_called_class(), 'validateWebformRating']]; + return $element; + } + /** * Prepares a #type 'webform_rating' render element for input.html.twig. * @@ -62,8 +76,8 @@ public static function preRenderWebformRating(array $element) { * @param array $element * A rating element. * - * @return string - * The RateIt div tag. + * @return array + * A renderable array containing the RateIt div tag. * * @see https://github.com/gjunge/rateit.js/wiki */ @@ -89,9 +103,9 @@ public static function buildRateIt(array $element) { 'data-rateit-readonly' => $is_readonly ? 'true' : 'false', ]; - // Set range element's #id. - if (isset($element['#id'])) { - $attributes['data-rateit-backingfld'] = '#' . $element['#id']; + // Set range element's selector based on its parents. + if (isset($element['#attributes']['data-drupal-selector'])) { + $attributes['data-rateit-backingfld'] = '[data-drupal-selector="' . $element['#attributes']['data-drupal-selector'] . '"]'; } // Set value for HTML preview. @@ -120,7 +134,17 @@ public static function buildRateIt(array $element) { 'library' => ['webform/webform.element.rating'], ], ]; + } + /** + * Validates a rating element. + */ + public static function validateWebformRating(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); + if ($has_access && !empty($element['#required']) && ($value === '0' || $value === '')) { + WebformElementHelper::setRequiredError($element, $form_state); + } } } diff --git a/web/modules/webform/src/Element/WebformSelectOther.php b/web/modules/webform/src/Element/WebformSelectOther.php index 8f2f1091bb..ea49ec35a7 100644 --- a/web/modules/webform/src/Element/WebformSelectOther.php +++ b/web/modules/webform/src/Element/WebformSelectOther.php @@ -21,6 +21,7 @@ class WebformSelectOther extends WebformOtherBase { * {@inheritdoc} */ protected static $properties = [ + '#title', '#required', '#options', '#default_value', diff --git a/web/modules/webform/src/Element/WebformSignature.php b/web/modules/webform/src/Element/WebformSignature.php index 1dad714e6b..3b26d6a9da 100644 --- a/web/modules/webform/src/Element/WebformSignature.php +++ b/web/modules/webform/src/Element/WebformSignature.php @@ -2,6 +2,7 @@ namespace Drupal\webform\Element; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Render\Element; @@ -20,6 +21,7 @@ public function getInfo() { return [ '#input' => TRUE, '#process' => [ + [$class, 'processWebformSignature'], [$class, 'processAjaxForm'], [$class, 'processGroup'], ], @@ -34,6 +36,15 @@ public function getInfo() { ]; } + /** + * Processes a signature webform element. + */ + public static function processWebformSignature(&$element, FormStateInterface $form_state, &$complete_form) { + // Remove 'for' from the element's label. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + return $element; + } + /** * Prepares a #type 'webform_signature' render element for input.html.twig. * diff --git a/web/modules/webform/src/Element/WebformSubmissionInformation.php b/web/modules/webform/src/Element/WebformSubmissionInformation.php new file mode 100644 index 0000000000..a7f2b6db0a --- /dev/null +++ b/web/modules/webform/src/Element/WebformSubmissionInformation.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Render\Element\RenderElement; + +/** + * Provides a render element to display webform submission information. + * + * @RenderElement("webform_submission_information") + */ +class WebformSubmissionInformation extends RenderElement { + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + + return [ + '#theme' => 'webform_submission_information', + '#webform_submission' => NULL, + '#source_entity' => NULL, + '#pre_render' => [ + [$class, 'preRenderWebformSubmissionInformation'], + ], + '#theme_wrappers' => ['details'], + '#summary_attributes' => [], + ]; + } + + /** + * Create webform submission information for rendering. + * + * @param array $element + * An associative array containing the properties and children of the + * element. + * + * @return array + * The modified element with webform submission information. + */ + public static function preRenderWebformSubmissionInformation(array $element) { + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $element['#webform_submission']; + $webform = $webform_submission->getWebform(); + + // Add title. + $element += [ + '#title' => t('Submission information'), + ]; + + // Add details attributes. + $element['#attributes']['data-webform-element-id'] = $webform->id() . '-submission-information'; + $element['#attributes']['class'] = ['webform-submission-information']; + + return $element; + } + +} diff --git a/web/modules/webform/src/Element/WebformSubmissionNavigation.php b/web/modules/webform/src/Element/WebformSubmissionNavigation.php new file mode 100644 index 0000000000..cdfe4c936c --- /dev/null +++ b/web/modules/webform/src/Element/WebformSubmissionNavigation.php @@ -0,0 +1,24 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Render\Element\RenderElement; + +/** + * Provides a render element to display webform submission navigation. + * + * @RenderElement("webform_submission_navigation") + */ +class WebformSubmissionNavigation extends RenderElement { + + /** + * {@inheritdoc} + */ + public function getInfo() { + return [ + '#theme' => 'webform_submission_navigation', + '#webform_submission' => NULL, + ]; + } + +} diff --git a/web/modules/webform/src/Element/WebformSubmissionViews.php b/web/modules/webform/src/Element/WebformSubmissionViews.php new file mode 100644 index 0000000000..ad3f423567 --- /dev/null +++ b/web/modules/webform/src/Element/WebformSubmissionViews.php @@ -0,0 +1,190 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Form\FormStateInterface; +use Drupal\views\Entity\View; + +/** + * Provides a form element for selecting webform submission views. + * + * @FormElement("webform_submission_views") + */ +class WebformSubmissionViews extends WebformMultiple { + + /** + * {@inheritdoc} + */ + public static function processWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + if (!\Drupal::moduleHandler()->moduleExists('views')) { + $element['#element_validate'] = [[get_called_class(), 'emptyValue']]; + return $element; + } + + $element['#key'] = 'name'; + $element['#header'] = TRUE; + $element['#empty_items'] = 0; + $element['#min_items'] = 1; + $element['#add_more_input_label'] = t('more submission views'); + + // Build element. + $element['#element'] = []; + + // Name / Title / View. + $view_options = []; + /** @var \Drupal\views\ViewEntityInterface[] $views */ + $views = View::loadMultiple(); + foreach ($views as $view) { + // Only include webform submission views. + if ($view->get('base_table') !== 'webform_submission' || $view->get('base_field') !== 'sid') { + continue; + } + + $optgroup = $view->label(); + $displays = $view->get('display'); + foreach ($displays as $display_id => $display) { + // Only include embed displays. + if ($display['display_plugin'] === 'embed') { + $view_options[$optgroup][$view->id() . ':' . $display_id] = $optgroup . ': ' . $display['display_title']; + } + } + } + $element['#element']['name_title_view'] = [ + '#type' => 'container', + '#title' => t('View / Name / Title'), + '#help' => '<b>' . t('View') . ':</b> ' . t('A webform submission embed display. The selected view should also include contextual filters. {webform_id}/{source_entity_type}/{source_entity_id}/{account_id}/{in_draft}') . + '<hr/>' . '<b>' . t('Name') . ':</b> ' . t('The name to be displayed in the URL when there are multiple submission views available.') . + '<hr/>' . '<b>' . t('Options') . ':</b> ' . t('The title to be display in the dropdown menu when there are multiple submission views available.'), + 'view' => [ + '#type' => 'select', + '#title' => t('View'), + '#title_display' => 'invisible', + '#empty_option' => t('Select view…'), + '#options' => $view_options, + '#error_no_message' => TRUE, + ], + 'name' => [ + '#type' => 'textfield', + '#title' => t('Name'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter name…'), + '#size' => 20, + '#pattern' => '^[-_a-z0-9]+$', + '#error_no_message' => TRUE, + ], + 'title' => [ + '#type' => 'textfield', + '#title' => t('Title'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter title…'), + '#size' => 20, + '#error_no_message' => TRUE, + ], + ]; + + // Global routes. + if (!empty($element['#global'])) { + $global_route_options = [ + 'entity.webform_submission.collection' => t('Submissions'), + 'entity.webform_submission.user' => t('User'), + ]; + $element['#element']['global_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Apply to global'), + '#help' => t('Display the selected view on the below paths') . + '<hr/><b>' . t('Submissions') . ':</b><br/>/admin/structure/webform/submissions/manage' . + '<hr/><b>' . t('User') . ':</b><br/>/user/{user}/submissions', + '#options' => $global_route_options, + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + '#error_no_message' => TRUE, + ]; + } + + // Webform routes. + $webform_route_options = [ + 'entity.webform.results_submissions' => t('Submissions'), + 'entity.webform.user.drafts' => t('User drafts'), + 'entity.webform.user.submissions' => t('User submissions'), + ]; + $element['#element']['webform_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Apply to webform'), + '#help' => t('Display the selected view on the below paths') . + '<hr/><b>' . t('Submissions') . ':</b><br/>/admin/structure/webform/manage/{webform}/results/submissions' . + '<hr/><b>' . t('User drafts') . ':</b><br/>/webform/{webform}/drafts' . + '<hr/><b>' . t('User submissions') . ':</b><br/>/webform/{webform}/submissions', + '#options' => $webform_route_options, + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + '#error_no_message' => TRUE, + ]; + + // Node routes. + if (\Drupal::moduleHandler()->moduleExists('webform_node')) { + $node_route_options = [ + 'entity.node.webform.results_submissions' => t('Submissions'), + 'entity.node.webform.user.drafts' => t('User drafts'), + 'entity.node.webform.user.submissions' => t('User submissions'), + ]; + $element['#element']['node_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Apply to node'), + '#help' => + t('Display the selected view on the below paths') . + '<hr/><b>' . t('Submissions') . ':</b><br/>/node/{node}/webform/results/submissions' . + '<hr/>' . '<b>' . t('User drafts') . ':</b><br/>/node/{node}/webform/drafts' . + '<hr/>' . '<b>' . t('User submissions') . ':</b><br/>/node/{node}/webform/submissions', + '#options' => $node_route_options, + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + '#error_no_message' => TRUE, + ]; + } + + parent::processWebformMultiple($element, $form_state, $complete_form); + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function validateWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + if (!\Drupal::moduleHandler()->moduleExists('views')) { + $element['#value'] = []; + $form_state->setValueForElement($element, []); + return; + } + + parent::validateWebformMultiple($element, $form_state, $complete_form); + $items = NestedArray::getValue($form_state->getValues(), $element['#parents']); + foreach ($items as $name => &$item) { + // Remove empty view references. + if ($name === '' && empty($item['view']) && empty($item['global_routes']) && empty($item['webform_routes']) && empty($item['node_routes'])) { + unset($items[$name]); + continue; + } + + if ($name === '') { + $form_state->setError($element, t('Name is required.')); + } + if (empty($item['title'])) { + $form_state->setError($element, t('Title is required.')); + } + if (empty($item['view'])) { + $form_state->setError($element, t('View name/display id is required.')); + } + } + + $element['#value'] = $items; + $form_state->setValueForElement($element, $items); + } + + /** + * Form validate callback which clears the submitted value. + */ + public static function emptyValue(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#value'] = []; + $form_state->setValueForElement($element, []); + } + +} diff --git a/web/modules/webform/src/Element/WebformSubmissionViewsReplace.php b/web/modules/webform/src/Element/WebformSubmissionViewsReplace.php new file mode 100644 index 0000000000..cd1df1b82c --- /dev/null +++ b/web/modules/webform/src/Element/WebformSubmissionViewsReplace.php @@ -0,0 +1,116 @@ +<?php + +namespace Drupal\webform\Element; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\FormElement; + +/** + * Provides a form element for selecting webform submission views replacement routes. + * + * @FormElement("webform_submission_views_replace") + */ +class WebformSubmissionViewsReplace extends FormElement { + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#input' => TRUE, + '#process' => [ + [$class, 'processWebformSubmissionViewsReplace'], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (!isset($element['#default_value'])) { + $element['#default_value'] = []; + } + return $element['#default_value']; + } + else { + return $input; + } + } + + /** + * Processes a ng webform submission views replacement element. + */ + public static function processWebformSubmissionViewsReplace(&$element, FormStateInterface $form_state, &$complete_form) { + $is_global = (!empty($element['#global'])) ? TRUE : FALSE; + $element['#tree'] = TRUE; + + $element['#value'] = (!is_array($element['#value'])) ? [] : $element['#value']; + $element['#value'] += [ + 'global_routes' => [], + 'webform_routes' => [], + 'node_routes' => [], + ]; + + // Global routes. + if ($is_global) { + $element['global_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Replace the global results with submission views'), + '#options' => [ + 'entity.webform_submission.collection' => t('Submissions'), + 'entity.webform_submission.user' => t('User'), + ], + '#default_value' => $element['#value']['global_routes'], + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + ]; + } + + // Webform routes. + $webform_routes_options = [ + 'entity.webform.results_submissions' => t('Submissions'), + 'entity.webform.user.drafts' => t('User drafts'), + 'entity.webform.user.submissions' => t('User submissions'), + ]; + if (!$is_global) { + $default_webform_routes = \Drupal::configFactory()->get('webform.settings')->get('settings.default_submission_views_replace.webform_routes') ?: []; + if ($webform_routes_options) { + $webform_routes_options = array_diff_key($webform_routes_options, array_flip($default_webform_routes)); + } + } + $element['webform_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Replace the webform results with submission views'), + '#options' => $webform_routes_options, + '#default_value' => ($webform_routes_options) ? $element['#value']['webform_routes'] : [], + '#access' => ($webform_routes_options) ? TRUE : FALSE, + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + ]; + + // Node routes. + $node_routes_options = [ + 'entity.node.webform.results_submissions' => t('Submissions'), + 'entity.node.webform.user.drafts' => t('User drafts'), + 'entity.node.webform.user.submissions' => t('User submissions'), + ]; + if (!$is_global) { + $default_node_routes = \Drupal::configFactory()->get('webform.settings')->get('settings.default_submission_views_replace.node_routes') ?: []; + if ($default_node_routes) { + $node_routes_options = array_diff_key($node_routes_options, array_flip($default_node_routes)); + } + } + $element['node_routes'] = [ + '#type' => 'checkboxes', + '#title' => t('Replace the node results with submission views'), + '#options' => $node_routes_options, + '#default_value' => ($node_routes_options) ? $element['#value']['node_routes'] : [], + '#access' => ($node_routes_options && \Drupal::moduleHandler()->moduleExists('webform_node')) ? TRUE : FALSE, + '#element_validate' => [['\Drupal\webform\Utility\WebformElementHelper', 'filterValues']], + ]; + + return $element; + } + +} diff --git a/web/modules/webform/src/Element/WebformTableSelectSort.php b/web/modules/webform/src/Element/WebformTableSelectSort.php index 7365be67d2..7c6ed9920c 100644 --- a/web/modules/webform/src/Element/WebformTableSelectSort.php +++ b/web/modules/webform/src/Element/WebformTableSelectSort.php @@ -6,6 +6,7 @@ use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\Table; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\webform\Plugin\WebformElement\TableSelect; /** * Provides a webform element for a sortable tableselect element. @@ -140,6 +141,11 @@ public static function preRenderWebformTableSelectSort($element) { $element['#header'] = $header; $element['#rows'] = $rows; + // Attach table select UX improvements. + $element['#attributes']['class'][] = 'webform-tableselect'; + $element['#attributes']['class'][] = 'js-webform-tableselect'; + $element['#attached']['library'][] = 'webform/webform.element.tableselect'; + // Attach table sort. $element['#attributes']['class'][] = 'js-tableselect-sort'; $element['#attributes']['class'][] = 'tableselect-sort'; @@ -197,17 +203,13 @@ public static function processWebformTableSelectSort(&$element, FormStateInterfa foreach ($element['#options'] as $key => $choice) { // Do not overwrite manually created children. if (!isset($element[$key])) { - $checkbox_title = ''; - $weight_title = ''; - if (isset($element['#options'][$key]['title']) && is_array($element['#options'][$key]['title'])) { - if (!empty($element['#options'][$key]['title']['data']['#title'])) { - $checkbox_title = new TranslatableMarkup('Update @title', [ - '@title' => $element['#options'][$key]['title']['data']['#title'], - ]); - $weight_title = new TranslatableMarkup('Weight for @title', [ - '@title' => $element['#options'][$key]['title']['data']['#title'], - ]); - } + if ($title = TableSelect::getTableSelectOptionTitle($choice)) { + $checkbox_title = $title; + $weight_title = new TranslatableMarkup('Weight for @title', ['@title' => $title]); + } + else { + $checkbox_title = ''; + $weight_title = ''; } $element[$key]['checkbox'] = [ diff --git a/web/modules/webform/src/Element/WebformTableSort.php b/web/modules/webform/src/Element/WebformTableSort.php index 88d709033b..869222e255 100644 --- a/web/modules/webform/src/Element/WebformTableSort.php +++ b/web/modules/webform/src/Element/WebformTableSort.php @@ -6,6 +6,7 @@ use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\Table; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\webform\Plugin\WebformElement\TableSelect; /** * Provides a webform element for a sortable table element. @@ -179,12 +180,8 @@ public static function processWebformTableSort(&$element, FormStateInterface $fo // Do not overwrite manually created children. if (!isset($element[$key])) { $weight_title = ''; - if (isset($element['#options'][$key]['title']) && is_array($element['#options'][$key]['title'])) { - if (!empty($element['#options'][$key]['title']['data']['#title'])) { - $weight_title = new TranslatableMarkup('Weight for @title', [ - '@title' => $element['#options'][$key]['title']['data']['#title'], - ]); - } + if ($title = TableSelect::getTableSelectOptionTitle($choice)) { + $weight_title = new TranslatableMarkup('Weight for @title', ['@title' => $title]); } $element[$key]['value'] = [ diff --git a/web/modules/webform/src/Element/WebformTermCheckboxes.php b/web/modules/webform/src/Element/WebformTermCheckboxes.php index db5efbd826..9d48f42e0c 100644 --- a/web/modules/webform/src/Element/WebformTermCheckboxes.php +++ b/web/modules/webform/src/Element/WebformTermCheckboxes.php @@ -41,9 +41,7 @@ public static function processCheckboxes(&$element, FormStateInterface $form_sta /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ $entity_repository = \Drupal::service('entity.repository'); - /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ - $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); - $tree = $taxonomy_storage->loadTree($element['#vocabulary'], 0, NULL, TRUE); + $tree = static::loadTree($element['#vocabulary']); if (empty($element['#breadcrumb'])) { foreach ($tree as $item) { @@ -71,10 +69,8 @@ protected static function getOptionsTree(array $element, $language) { /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ $entity_repository = \Drupal::service('entity.repository'); - /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ - $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); - $tree = $taxonomy_storage->loadTree($element['#vocabulary'], 0, NULL, TRUE); + $tree = static::loadTree($element['#vocabulary']); $options = []; foreach ($tree as $item) { diff --git a/web/modules/webform/src/Element/WebformTermReferenceTrait.php b/web/modules/webform/src/Element/WebformTermReferenceTrait.php index 4508ab8ab1..e966a31c25 100644 --- a/web/modules/webform/src/Element/WebformTermReferenceTrait.php +++ b/web/modules/webform/src/Element/WebformTermReferenceTrait.php @@ -48,10 +48,8 @@ protected static function getOptionsBreadcrumb(array $element, $language) { /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ $entity_repository = \Drupal::service('entity.repository'); - /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ - $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); - $tree = $taxonomy_storage->loadTree($element['#vocabulary'], 0, NULL, TRUE); + $tree = static::loadTree($element['#vocabulary']); $options = []; $breadcrumb = []; @@ -81,10 +79,8 @@ protected static function getOptionsTree(array $element, $language) { /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ $entity_repository = \Drupal::service('entity.repository'); - /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ - $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); - $tree = $taxonomy_storage->loadTree($element['#vocabulary'], 0, NULL, TRUE); + $tree = static::loadTree($element['#vocabulary']); $options = []; foreach ($tree as $item) { @@ -95,4 +91,19 @@ protected static function getOptionsTree(array $element, $language) { return $options; } + /** + * Finds all terms in a given vocabulary ID. + * + * @param string $vid + * Vocabulary ID to retrieve terms for. + * + * @return object[]|\Drupal\taxonomy\TermInterface[] + * An array of term objects that are the children of the vocabulary $vid. + */ + protected static function loadTree($vid) { + /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + return $taxonomy_storage->loadTree($vid, 0, NULL, TRUE); + } + } diff --git a/web/modules/webform/src/Element/WebformTermsOfService.php b/web/modules/webform/src/Element/WebformTermsOfService.php index c82b377ad9..7ca66c34f4 100644 --- a/web/modules/webform/src/Element/WebformTermsOfService.php +++ b/web/modules/webform/src/Element/WebformTermsOfService.php @@ -43,11 +43,13 @@ public function getInfo() { */ public static function preRenderCheckbox($element) { $element = parent::preRenderCheckbox($element); + $id = 'webform-terms-of-service-' . implode('_', $element['#parents']); if (empty($element['#title'])) { $element['#title'] = (string) t('I agree to the {terms of service}.'); } - $element['#title'] = str_replace('{', '<a>', $element['#title']); + + $element['#title'] = str_replace('{', '<a role="button" href="#terms">', $element['#title']); $element['#title'] = str_replace('}', '</a>', $element['#title']); // Change description to render array. @@ -61,23 +63,35 @@ public static function preRenderCheckbox($element) { // Add terms to #description. $element['#description']['terms'] = [ '#type' => 'container', - '#attributes' => ['class' => ['webform-terms-of-service-details', 'js-hide']], + '#attributes' => [ + 'id' => $id . '--description', + 'class' => ['webform-terms-of-service-details', 'js-hide'], + ], ]; if (!empty($element['#terms_title'])) { $element['#description']['terms']['title'] = [ + '#type' => 'container', '#markup' => $element['#terms_title'], - '#prefix' => '<div class="webform-terms-of-service-details--title">', - '#suffix' => '</div>', + '#attributes' => [ + 'class' => ['webform-terms-of-service-details--title'], + ], ]; } if (!empty($element['#terms_content'])) { $element['#description']['terms']['content'] = (is_array($element['#terms_content'])) ? $element['#terms_content'] : ['#markup' => $element['#terms_content']]; $element['#description']['terms']['content'] += [ - '#prefix' => '<div class="webform-terms-of-service-details--content">', - '#suffix' => '</div>', + '#type' => 'container', + '#attributes' => [ + 'class' => ['webform-terms-of-service-details--content'], + ], ]; } + // Add accessibility attributes to title and content. + if ($element['#type'] === static::TERMS_SLIDEOUT) { + + } + // Set type to data attribute. // @see Drupal.behaviors.webformTermsOfService. $element['#wrapper_attributes']['data-webform-terms-of-service-type'] = $element['#terms_type']; diff --git a/web/modules/webform/src/Element/WebformTime.php b/web/modules/webform/src/Element/WebformTime.php index f30f73f29e..2785c5ba9f 100644 --- a/web/modules/webform/src/Element/WebformTime.php +++ b/web/modules/webform/src/Element/WebformTime.php @@ -12,7 +12,7 @@ * * @code * $form['time'] = array( - * '#type' => 'time', + * '#type' => 'webform_time', * '#title' => $this->t('Time'), * '#default_value' => '12:00 AM' * ); @@ -35,8 +35,8 @@ public function getInfo() { '#theme_wrappers' => ['form_element'], '#timepicker' => FALSE, '#time_format' => 'H:i', - '#size' => 10, - '#maxlength' => 10, + '#size' => 12, + '#maxlength' => 12, ]; } @@ -48,9 +48,21 @@ public static function valueCallback(&$element, $input, FormStateInterface $form // Set default value using GNU PHP date format. // @see https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats. if (!empty($element['#default_value'])) { - $element['#default_value'] = date('H:i', strtotime($element['#default_value'])); + try { + // Evaluate if the value can be time formatted, including relative + // values like 'now' or '+2 hours'. + new \DateTime($element['#default_value']); + } + catch (\Exception $exception) { + \Drupal::messenger()->addError($exception->getMessage()); + return NULL; + } + $element['#default_value'] = static::formatTime('H:i', strtotime($element['#default_value'])); return $element['#default_value']; } + else { + return NULL; + } } return $input; @@ -83,18 +95,33 @@ public static function processWebformTime(&$element, FormStateInterface $form_st /** * Webform element validation handler for #type 'webform_time'. * - * Note that #required is validated by _form_validate() already. + * Note that #required is validated by _form_valistatic::formatTime() already. */ public static function validateWebformTime(&$element, FormStateInterface $form_state, &$complete_form) { $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); $value = $element['#value']; - if ($value === '') { + if (trim($value) === '') { return; } $time = strtotime($value); - if ($time === FALSE) { + + // Make the submitted value is parsable and in the specified + // custom format (ex: g:i A) or default format (ex: H:i). + // + // This accounts for time values generated by an HTML5 time input or + // the jQuery Timepicker. + // + // @see \Drupal\webform\Plugin\WebformElement\DateBase::validateDate + // @see https://github.com/jonthornton/jquery-timepicker + // @see js/webform.element.time.js + $is_valid_time = $time && ( + $value == static::formatTime('H:i', $time) || + $value == static::formatTime('H:i:s', $time) || + $value == static::formatTime($element['#time_format'], $time) + ); + if (!$is_valid_time) { if ($has_access) { if (isset($element['#title'])) { $form_state->setError($element, t('%name must be a valid time.', ['%name' => $element['#title']])); @@ -115,7 +142,7 @@ public static function validateWebformTime(&$element, FormStateInterface $form_s if ($time < $min) { $form_state->setError($element, t('%name must be on or after %min.', [ '%name' => $name, - '%min' => date($time_format, $min), + '%min' => static::formatTime($time_format, $min), ])); } } @@ -126,12 +153,14 @@ public static function validateWebformTime(&$element, FormStateInterface $form_s if ($time > $max) { $form_state->setError($element, t('%name must be on or before %max.', [ '%name' => $name, - '%max' => date($time_format, $max), + '%max' => static::formatTime($time_format, $max), ])); } } - $value = date('H:i:s', $time); + // Convert time to 'H:i:s' format. + $value = static::formatTime('H:i:s', $time); + $element['#time_format'] = 'H:i:s'; $element['#value'] = $value; $form_state->setValueForElement($element, $value); } @@ -149,17 +178,34 @@ public static function preRenderWebformTime(array $element) { if (!empty($element['#timepicker'])) { // Render simple text field that is converted to timepicker. $element['#attributes']['type'] = 'text'; - // Apply #time_format to #value. - if (!empty($element['#value'])) { - $element['#value'] = date($element['#attributes']['data-webform-time-format'], strtotime($element['#value'])); + // Apply #time_format to #default_value. + if (!empty($element['#value']) && !empty($element['#default_value']) && $element['#value'] === $element['#default_value']) { + $element['#value'] = static::formatTime($element['#attributes']['data-webform-time-format'], strtotime($element['#value'])); } } else { $element['#attributes']['type'] = 'time'; } - Element::setAttributes($element, ['id', 'name', 'type', 'value', 'size', 'min', 'max', 'step']); + Element::setAttributes($element, ['id', 'name', 'type', 'value', 'size', 'maxlength', 'min', 'max', 'step']); static::setAttributes($element, ['form-time', 'webform-time']); return $element; } + /** + * Format custom time. + * + * @param string $custom_format + * A PHP date format string suitable for input to date(). + * @param int $timestamp + * (optional) A UNIX timestamp to format. + * + * @return string + * Formatted time. + */ + protected static function formatTime($custom_format, $timestamp = NULL) { + /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ + $date_formatter = \Drupal::service('date.formatter'); + return $date_formatter->format($timestamp ?: time(), 'custom', $custom_format); + } + } diff --git a/web/modules/webform/src/Entity/Webform.php b/web/modules/webform/src/Entity/Webform.php index deec1a1dcd..41534609b0 100644 --- a/web/modules/webform/src/Entity/Webform.php +++ b/web/modules/webform/src/Entity/Webform.php @@ -3,26 +3,24 @@ namespace Drupal\webform\Entity; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Access\AccessResult; -use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Config\Entity\ConfigEntityBundleBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Render\Element; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\user\Entity\User; use Drupal\user\UserInterface; use Drupal\webform\Plugin\WebformElement\WebformActions; -use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase; use Drupal\webform\Plugin\WebformElement\WebformWizardPage; +use Drupal\webform\Plugin\WebformElementAttachmentInterface; use Drupal\webform\Plugin\WebformHandlerMessageInterface; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformReflectionHelper; use Drupal\webform\Plugin\WebformHandlerInterface; use Drupal\webform\Plugin\WebformHandlerPluginCollection; +use Drupal\webform\Utility\WebformTextHelper; +use Drupal\webform\Utility\WebformYaml; use Drupal\webform\WebformException; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; @@ -34,6 +32,13 @@ * @ConfigEntityType( * id = "webform", * label = @Translation("Webform"), + * label_collection = @Translation("Webforms"), + * label_singular = @Translation("webform"), + * label_plural = @Translation("webforms"), + * label_count = @PluralTranslation( + * singular = "@count webform", + * plural = "@count webforms", + * ), * handlers = { * "storage" = "\Drupal\webform\WebformEntityStorage", * "view_builder" = "Drupal\webform\WebformEntityViewBuilder", @@ -44,6 +49,7 @@ * "duplicate" = "Drupal\webform\WebformEntityAddForm", * "delete" = "Drupal\webform\WebformEntityDeleteForm", * "edit" = "Drupal\webform\WebformEntityElementsForm", + * "export" = "Drupal\webform\WebformEntityExportForm", * "settings" = "Drupal\webform\EntitySettings\WebformEntitySettingsGeneralForm", * "settings_form" = "Drupal\webform\EntitySettings\WebformEntitySettingsFormForm", * "settings_submissions" = "Drupal\webform\EntitySettings\WebformEntitySettingsSubmissionsForm", @@ -55,12 +61,14 @@ * }, * admin_permission = "administer webform", * bundle_of = "webform_submission", + * static_cache = TRUE, * entity_keys = { * "id" = "id", * "label" = "title", * }, * links = { * "canonical" = "/webform/{webform}", + * "access-denied" = "/webform/{webform}/access-denied", * "submissions" = "/webform/{webform}/submissions", * "add-form" = "/webform/{webform}", * "edit-form" = "/admin/structure/webform/manage/{webform}/elements", @@ -85,8 +93,10 @@ * "status", * "open", * "close", + * "weight", * "uid", * "template", + * "archive", * "id", * "uuid", * "title", @@ -124,6 +134,13 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface { */ protected $uuid; + /** + * The webform's current operation. + * + * @var string + */ + protected $operation; + /** * The webform override state. * @@ -154,6 +171,13 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface { */ protected $close; + /** + * The webform weight. + * + * @var int + */ + protected $weight = 0; + /** * The webform template indicator. * @@ -161,6 +185,13 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface { */ protected $template = FALSE; + /** + * The webform archive indicator. + * + * @var bool + */ + protected $archive = FALSE; + /** * The webform title. * @@ -316,18 +347,25 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface { protected $elementsWizardPages = []; /** - * The webform pages. + * Track managed file elements. * * @var array */ - protected $pages; + protected $elementsManagedFiles = []; /** - * Track if the webform has a managed file (upload) element. + * Track attachment elements. * - * @var bool + * @var array */ - protected $hasManagedFile = FALSE; + protected $elementsAttachments = []; + + /** + * The webform pages. + * + * @var array + */ + protected $pages; /** * Track if the webform is using a flexbox layout. @@ -378,6 +416,13 @@ public function getLangcode() { return $this->langcode; } + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + /** * {@inheritdoc} */ @@ -413,6 +458,29 @@ public function setOwnerId($uid) { return $this; } + /** + * {@inheritdoc} + */ + public function getOperation() { + return $this->operation; + + } + + /** + * {@inheritdoc} + */ + public function setOperation($operation) { + $this->operation = $operation; + return $this; + } + + /** + * {@inheritdoc} + */ + public function isTest() { + return ($this->operation === 'test') ? TRUE : FALSE; + } + /** * {@inheritdoc} */ @@ -464,6 +532,11 @@ public function status() { * {@inheritdoc} */ public function isOpen() { + // Archived webforms are always closed. + if ($this->isArchived()) { + return FALSE; + } + switch ($this->status) { case WebformInterface::STATUS_OPEN: return TRUE; @@ -515,6 +588,13 @@ public function isTemplate() { return $this->template ? TRUE : FALSE; } + /** + * {@inheritdoc} + */ + public function isArchived() { + return $this->archive ? TRUE : FALSE; + } + /** * {@inheritdoc} */ @@ -522,6 +602,13 @@ public function isConfidential() { return $this->getSetting('form_confidential'); } + /** + * {@inheritdoc} + */ + public function hasRemoteAddr() { + return (!$this->isConfidential() && $this->getSetting('form_remote_addr')) ? TRUE : FALSE; + } + /** * {@inheritdoc} */ @@ -588,7 +675,15 @@ public function hasPage() { */ public function hasManagedFile() { $this->initElements(); - return $this->hasManagedFile; + return (!empty($this->elementsManagedFiles)) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function hasAttachments() { + $this->initElements(); + return (!empty($this->elementsAttachments)) ? TRUE : FALSE; } /** @@ -832,6 +927,7 @@ public static function getDefaultSettings() { 'page' => TRUE, 'page_submit_path' => '', 'page_confirm_path' => '', + 'form_title' => 'both', 'form_submit_once' => FALSE, 'form_exception_message' => '', 'form_open_message' => '', @@ -839,6 +935,7 @@ public static function getDefaultSettings() { 'form_previous_submissions' => TRUE, 'form_confidential' => FALSE, 'form_confidential_message' => '', + 'form_remote_addr' => TRUE, 'form_convert_anonymous' => FALSE, 'form_prepopulate' => FALSE, 'form_prepopulate_source_entity' => FALSE, @@ -854,22 +951,37 @@ public static function getDefaultSettings() { 'form_submit_back' => FALSE, 'form_autofocus' => FALSE, 'form_details_toggle' => FALSE, - 'form_login' => FALSE, - 'form_login_message' => '', + 'form_access_denied' => WebformInterface::ACCESS_DENIED_DEFAULT, + 'form_access_denied_title' => '', + 'form_access_denied_message' => '', + 'form_access_denied_attributes' => [], + 'form_file_limit' => '', 'submission_label' => '', 'submission_log' => FALSE, + 'submission_views' => [], + 'submission_views_replace' => [], 'submission_user_columns' => [], - 'submission_login' => FALSE, - 'submission_login_message' => '', + 'submission_user_duplicate' => FALSE, + 'submission_access_denied' => WebformInterface::ACCESS_DENIED_DEFAULT, + 'submission_access_denied_title' => '', + 'submission_access_denied_message' => '', + 'submission_access_denied_attributes' => [], 'submission_exception_message' => '', 'submission_locked_message' => '', + 'submission_excluded_elements' => [], + 'submission_exclude_empty' => FALSE, + 'submission_exclude_empty_checkbox' => FALSE, + 'previous_submission_message' => '', + 'previous_submissions_message' => '', 'autofill' => FALSE, 'autofill_message' => '', 'autofill_excluded_elements' => [], 'wizard_progress_bar' => TRUE, 'wizard_progress_pages' => FALSE, 'wizard_progress_percentage' => FALSE, + 'wizard_progress_link' => FALSE, 'wizard_start_label' => '', + 'wizard_preview_link' => FALSE, 'wizard_confirmation' => TRUE, 'wizard_confirmation_label' => '', 'wizard_track' => '', @@ -880,6 +992,7 @@ public static function getDefaultSettings() { 'preview_attributes' => [], 'preview_excluded_elements' => [], 'preview_exclude_empty' => TRUE, + 'preview_exclude_empty_checkbox' => FALSE, 'draft' => self::DRAFT_NONE, 'draft_multiple' => FALSE, 'draft_auto_save' => FALSE, @@ -898,9 +1011,11 @@ public static function getDefaultSettings() { 'limit_total' => NULL, 'limit_total_interval' => NULL, 'limit_total_message' => '', + 'limit_total_unique' => FALSE, 'limit_user' => NULL, 'limit_user_interval' => NULL, 'limit_user_message' => '', + 'limit_user_unique' => FALSE, 'entity_limit_total' => NULL, 'entity_limit_total_interval' => NULL, 'entity_limit_user' => NULL, @@ -913,176 +1028,6 @@ public static function getDefaultSettings() { ]; } - /** - * {@inheritdoc} - */ - public static function getDefaultAccessRules() { - return [ - 'create' => [ - 'roles' => [ - 'anonymous', - 'authenticated', - ], - 'users' => [], - 'permissions' => [], - ], - 'view_any' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'update_any' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'delete_any' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'purge_any' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'view_own' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'update_own' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'delete_own' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'administer' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - 'test' => [ - 'roles' => [], - 'users' => [], - 'permissions' => [], - ], - ]; - } - - /** - * {@inheritdoc} - */ - public function checkAccessRules($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission = NULL) { - // Always grant access to user that can administer webforms. - if ($account->hasPermission('administer webform')) { - return AccessResult::allowed()->cachePerPermissions(); - } - - // Grant user with administer webform submission access to view all webform submissions. - if ($account->hasPermission('administer webform submission') && $operation != 'administer') { - return AccessResult::allowed()->cachePerPermissions(); - } - - // The "page" operation is the same as "create" but requires that the - // Webform is allowed to be displayed as dedicated page. - // Used by the 'entity.webform.canonical' route. - if ($operation == 'page') { - if (empty($this->settings['page'])) { - return AccessResult::forbidden()->addCacheableDependency($this); - } - else { - $operation = 'create'; - } - } - - $access_rules = $this->getAccessRules() + static::getDefaultAccessRules(); - - $cacheability = new CacheableMetadata(); - $cacheability->addCacheableDependency($this); - $cacheability->addCacheContexts(['user.permissions']); - foreach ($access_rules as $access_rule) { - // If there is some per-user access logic, our response must be cacheable - // accordingly. - if (!empty($access_rule['users'])) { - $cacheability->addCacheContexts(['user']); - } - } - - // Check administer access rule and grant full access to user. - if ($this->checkAccessRule($access_rules['administer'], $account)) { - return AccessResult::allowed()->addCacheableDependency($cacheability); - } - - // Check operation specific access rules. - if (in_array($operation, ['create', 'view_any', 'update_any', 'delete_any', 'purge_any', 'administer', 'test']) - && $this->checkAccessRule($access_rules[$operation], $account)) { - return AccessResult::allowed()->addCacheableDependency($cacheability); - } - if (isset($access_rules[$operation . '_any']) - && $this->checkAccessRule($access_rules[$operation . '_any'], $account)) { - return AccessResult::allowed()->addCacheableDependency($cacheability); - } - - // If webform submission is not set then check 'view own'. - // @see \Drupal\webform\WebformSubmissionForm::displayMessages. - if (empty($webform_submission) - && $operation === 'view_own' - && $this->checkAccessRule($access_rules[$operation], $account)) { - return AccessResult::allowed()->addCacheableDependency($cacheability); - } - - // If webform submission is set then check the webform submission owner. - if (!empty($webform_submission)) { - $is_authenticated_owner = ($account->isAuthenticated() && $account->id() === $webform_submission->getOwnerId()); - $is_anonymous_owner = ($account->isAnonymous() && !empty($_SESSION['webform_submissions']) && isset($_SESSION['webform_submissions'][$webform_submission->id()])); - $is_owner = ($is_authenticated_owner || $is_anonymous_owner); - if ($is_owner) { - if (isset($access_rules[$operation . '_own']) - && $this->checkAccessRule($access_rules[$operation . '_own'], $account)) { - return AccessResult::allowed()->cachePerUser()->addCacheableDependency($cacheability); - } - } - } - - return AccessResult::forbidden()->addCacheableDependency($cacheability); - } - - /** - * Checks an access rule against a user account's roles and id. - * - * @param array $access_rule - * An access rule. - * @param \Drupal\Core\Session\AccountInterface $account - * The user session for which to check access. - * - * @return bool - * The access result. Returns a TRUE if access is allowed. - * - * @see \Drupal\webform\Plugin\WebformElementBase::checkAccessRule - */ - protected function checkAccessRule(array $access_rule, AccountInterface $account) { - if (!empty($access_rule['roles']) && array_intersect($access_rule['roles'], $account->getRoles())) { - return TRUE; - } - elseif (!empty($access_rule['users']) && in_array($account->id(), $access_rule['users'])) { - return TRUE; - } - elseif (!empty($access_rule['permissions'])) { - foreach ($access_rule['permissions'] as $permission) { - if ($account->hasPermission($permission)) { - return TRUE; - } - } - } - - return FALSE; - } - /** * {@inheritdoc} */ @@ -1176,6 +1121,22 @@ public function getElementsInitializedFlattenedAndHasValue($operation = NULL) { return $this->checkElementsFlattenedAccess($operation, $this->elementsInitializedFlattenedAndHasValue); } + /** + * {@inheritdoc} + */ + public function getElementsManagedFiles() { + $this->initElements(); + return $this->elementsManagedFiles; + } + + /** + * {@inheritdoc} + */ + public function getElementsAttachments() { + $this->initElements(); + return $this->elementsAttachments; + } + /** * Check operation access for each element. * @@ -1215,11 +1176,43 @@ public function getElementsSelectorOptions() { $elements = $this->getElementsInitializedAndFlattened(); foreach ($elements as $element) { $element_plugin = $element_manager->getElementInstance($element); + $element_selectors = $element_plugin->getElementSelectorOptions($element); + foreach ($element_selectors as $element_selector_key => $element_selector_value) { + // Suffix duplicate selector optgroups with empty characters. + // + // This prevents elements with the same titles and multiple selectors + // from clobbering each other. + if (isset($selectors[$element_selector_key]) && is_array($element_selector_value)) { + while (isset($selectors[$element_selector_key])) { + $element_selector_key .= ' '; + } + $selectors[$element_selector_key] = $element_selector_value; + } + else { + $selectors[$element_selector_key] = $element_selector_value; + } + } $selectors += $element_plugin->getElementSelectorOptions($element); } return $selectors; } + /** + * {@inheritdoc} + */ + public function getElementsSelectorSourceValues() { + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + + $source_values = []; + $elements = $this->getElementsInitializedAndFlattened(); + foreach ($elements as $element) { + $element_plugin = $element_manager->getElementInstance($element); + $source_values += $element_plugin->getElementSelectorSourceValues($element); + } + return $source_values; + } + /** * {@inheritdoc} */ @@ -1231,7 +1224,7 @@ public function getElementsPrepopulate() { * {@inheritdoc} */ public function setElements(array $elements) { - $this->elements = Yaml::encode($elements); + $this->elements = WebformYaml::encode($elements); $this->resetElements(); return $this; } @@ -1245,7 +1238,6 @@ protected function initElements() { } // @see \Drupal\webform\Entity\Webform::resetElements - $this->hasManagedFile = FALSE; $this->hasFlexboxLayout = FALSE; $this->hasContainer = FALSE; $this->hasConditions = FALSE; @@ -1257,6 +1249,8 @@ protected function initElements() { $this->elementsInitializedAndFlattened = []; $this->elementsInitializedFlattenedAndHasValue = []; $this->elementsTranslations = []; + $this->elementsManagedFiles = []; + $this->elementsAttachments = []; try { $config_translation = \Drupal::moduleHandler()->moduleExists('config_translation'); @@ -1265,11 +1259,16 @@ protected function initElements() { /** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */ $language_manager = \Drupal::service('language_manager'); - // If current webform is translated, load the base (default) webform and apply - // the translation to the elements. - if ($config_translation && $this->langcode != $language_manager->getCurrentLanguage()->getId()) { + // If current webform is translated, load the base (default) webform and + // apply the translation to the elements. + if ($config_translation + && ($this->langcode != $language_manager->getCurrentLanguage()->getId())) { + // Always get the elements in the original language. $elements = $translation_manager->getElements($this); - $this->elementsTranslations = $translation_manager->getElements($this, $language_manager->getCurrentLanguage()->getId()); + // For none admin routes get the element (label) translations. + if (!$translation_manager->isAdminRoute()) { + $this->elementsTranslations = $translation_manager->getElements($this, $language_manager->getCurrentLanguage()->getId()); + } } else { $elements = Yaml::decode($this->elements); @@ -1303,7 +1302,6 @@ protected function initElements() { */ protected function resetElements() { $this->pages = NULL; - $this->hasManagedFile = NULL; $this->hasFlexboxLayout = NULL; $this->hasContainer = NULL; $this->hasConditions = NULL; @@ -1317,6 +1315,8 @@ protected function resetElements() { $this->elementsInitializedAndFlattened = NULL; $this->elementsInitializedFlattenedAndHasValue = NULL; $this->elementsTranslations = NULL; + $this->elementsManagedFiles = []; + $this->elementsAttachments = []; } /** @@ -1337,7 +1337,7 @@ protected function initElementsRecursive(array &$elements, $parent = '', $depth $elements = WebformElementHelper::removeIgnoredProperties($elements); foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -1346,6 +1346,15 @@ protected function initElementsRecursive(array &$elements, $parent = '', $depth WebformElementHelper::applyTranslation($element, $this->elementsTranslations[$key]); } + // Prevent regressions where webform_computed_* element is still using + // #value instead of #template. + if (isset($element['#type']) && strpos($element['#type'], 'webform_computed_') === 0) { + if (isset($element['#value']) && !isset($element['#template'])) { + $element['#template'] = $element['#value']; + unset($element['#value']); + } + } + // Copy only the element properties to decoded and flattened elements. $this->elementsDecodedAndFlattened[$key] = WebformElementHelper::getProperties($element); @@ -1403,11 +1412,6 @@ protected function initElementsRecursive(array &$elements, $parent = '', $depth // and stored in the '#webform_composite_elements' property. $element_plugin->initialize($element); - // Track managed file upload. - if ($element_plugin instanceof WebformManagedFileBase) { - $this->hasManagedFile = TRUE; - } - // Track flexbox. if ($element['#type'] == 'flexbox' || $element['#type'] == 'webform_flexbox') { $this->hasFlexboxLayout = TRUE; @@ -1443,6 +1447,16 @@ protected function initElementsRecursive(array &$elements, $parent = '', $depth $this->elementsWizardPages[$key] = $key; } + // Track managed files. + if ($element_plugin->hasManagedFiles($element)) { + $this->elementsManagedFiles[$key] = $key; + } + + // Track attachments. + if ($element_plugin instanceof WebformElementAttachmentInterface) { + $this->elementsAttachments[$key] = $key; + } + $element['#webform_multiple'] = $element_plugin->hasMultipleValues($element); $element['#webform_composite'] = $element_plugin->isComposite(); } @@ -1537,7 +1551,7 @@ public function setElementProperties($key, array $properties, $parent_key = '') */ protected function setElementPropertiesRecursive(array &$elements, $key, array $properties, $parent_key = '') { foreach ($elements as $element_key => &$element) { - if (Element::property($element_key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $element_key)) { continue; } @@ -1589,7 +1603,7 @@ public function deleteElement($key) { */ protected function deleteElementRecursive(array &$elements, $key) { foreach ($elements as $element_key => &$element) { - if (Element::property($element_key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $element_key)) { continue; } @@ -1618,7 +1632,7 @@ protected function deleteElementRecursive(array &$elements, $key) { */ protected function collectSubElementKeysRecursive(array &$sub_element_keys, array $elements) { foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } $sub_element_keys[$key] = $key; @@ -1717,13 +1731,23 @@ public function createDuplicate() { // Update owner to current user. $duplicate->setOwnerId(\Drupal::currentUser()->id()); - // If template, clear description, remove template flag, and publish. - if ($duplicate->isTemplate()) { + // If template being used to create a new webform, clear description + // and remove the template flag. + // @see \Drupal\webform_templates\Controller\WebformTemplatesController::index + $is_template_duplicate = \Drupal::request()->get('template'); + if ($duplicate->isTemplate() && !$is_template_duplicate) { $duplicate->set('description', ''); $duplicate->set('template', FALSE); - $duplicate->setStatus(TRUE); } + // If archived, remove archive flag. + if ($duplicate->isArchived()) { + $duplicate->set('archive', FALSE); + } + + // Set default status. + $duplicate->setStatus(\Drupal::config('webform.settings')->get('settings.default_status')); + // Remove enforce module dependency when a sub-module's webform is // duplicated. if (isset($duplicate->dependencies['enforced']['module'])) { @@ -1744,11 +1768,14 @@ public function createDuplicate() { * {@inheritdoc} */ public static function preCreate(EntityStorageInterface $storage, array &$values) { + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + $values += [ - 'status' => WebformInterface::STATUS_OPEN, + 'status' => \Drupal::config('webform.settings')->get('settings.default_status'), 'uid' => \Drupal::currentUser()->id(), 'settings' => static::getDefaultSettings(), - 'access' => static::getDefaultAccessRules(), + 'access' => $access_rules_manager->getDefaultAccessRules(), ]; // Convert boolean status to STATUS constant. @@ -1791,6 +1818,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie // Delete all submission associated with this webform. $submission_ids = \Drupal::entityQuery('webform_submission') + ->accessCheck(FALSE) ->condition('webform_id', array_keys($entities), 'IN') ->sort('sid') ->execute(); @@ -1841,6 +1869,23 @@ public function getCacheContexts() { return $cache_contexts; } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + if ($this->isScheduled()) { + $time = time(); + if ($this->open && strtotime($this->open) > $time) { + return (strtotime($this->open) - $time); + } + elseif ($this->close && strtotime($this->close) > $time) { + return (strtotime($this->close) - $time); + } + } + + return parent::getCacheMaxAge(); + } + /** * {@inheritdoc} */ @@ -1935,37 +1980,37 @@ public function updatePaths() { return; } - $submit_base_path = '/' . ($page_submit_path ?: $default_page_base_path . '/' . str_replace('_', '-', $this->id())); - - // Update submit path. - $submit_suffixes = [ - '', - '/submissions', - '/drafts', - ]; - foreach ($submit_suffixes as $submit_suffix) { - $submit_source = '/webform/' . $this->id() . $submit_suffix; - $submit_alias = $submit_base_path . $submit_suffix; - $this->updatePath($submit_source, $submit_alias, $this->langcode); - $this->updatePath($submit_source, $submit_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); + // Update webform base, confirmation, submissions and drafts paths. + $path_base_alias = '/' . ($page_submit_path ?: $default_page_base_path . '/' . str_replace('_', '-', $this->id())); + $path_suffixes = ['', '/confirmation', '/submissions', '/drafts']; + foreach ($path_suffixes as $path_suffix) { + $path_source = '/webform/' . $this->id() . $path_suffix; + $path_alias = $path_base_alias . $path_suffix; + if ($path_suffix === '/confirmation' && $this->settings['page_confirm_path']) { + $path_alias = '/' . trim($this->settings['page_confirm_path'], '/'); + } + $this->updatePath($path_source, $path_alias, $this->langcode); + $this->updatePath($path_source, $path_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); } - - // Update confirm path. - $confirm_source = '/webform/' . $this->id() . '/confirmation'; - $confirm_alias = $this->settings['page_confirm_path'] ?: $submit_base_path . '/confirmation'; - $confirm_alias = '/' . trim($confirm_alias, '/'); - $this->updatePath($confirm_source, $confirm_alias, $this->langcode); - $this->updatePath($confirm_source, $confirm_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); } /** * {@inheritdoc} */ public function deletePaths() { + // Path module must be enabled for URL aliases to be updated. + if (!\Drupal::moduleHandler()->moduleExists('path')) { + return; + } + /** @var \Drupal\Core\Path\AliasStorageInterface $path_alias_storage */ $path_alias_storage = \Drupal::service('path.alias_storage'); - $path_alias_storage->delete(['source' => '/webform/' . $this->id()]); - $path_alias_storage->delete(['source' => '/webform/' . $this->id() . '/confirmation']); + + // Delete webform base, confirmation, submissions and drafts paths. + $path_suffixes = ['', '/confirmation', '/submissions', '/drafts']; + foreach ($path_suffixes as $path_suffix) { + $path_alias_storage->delete(['source' => '/webform/' . $this->id() . $path_suffix]); + } } /** @@ -2172,6 +2217,9 @@ public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = $settings = $this->getSettings(); $handlers = $this->getHandlers(); foreach ($handlers as $handler) { + $handler->setWebformSubmission($webform_submission); + $this->invokeHandlerAlter($handler, $method, $args); + if ($handler->isEnabled() && $handler->checkConditions($webform_submission)) { $handler->overrideSettings($settings, $webform_submission); } @@ -2183,6 +2231,9 @@ public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = else { $handlers = $this->getHandlers(); foreach ($handlers as $handler) { + $handler->setWebformSubmission($webform_submission); + $this->invokeHandlerAlter($handler, $method, $args); + // If the handler is disabled never invoke it. if ($handler->isDisabled()) { continue; @@ -2198,6 +2249,25 @@ public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = } } + /** + * Alter a webform handler when it is invoked. + * + * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler + * A webform handler. + * @param string $method_name + * The handler method to be invoked. + * @param array $args + * Array of arguments being passed to the handler's method. + * + * @see hook_webform_handler_invoke_alter() + * @see hook_webform_handler_invoke_METHOD_NAME_alter() + */ + protected function invokeHandlerAlter(WebformHandlerInterface $handler, $method_name, array $args) { + $method_name = WebformTextHelper::camelToSnake($method_name); + \Drupal::moduleHandler()->alter('webform_handler_invoke', $handler, $method_name, $args); + \Drupal::moduleHandler()->alter('webform_handler_invoke_' . $method_name, $handler, $args); + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Entity/WebformOptions.php b/web/modules/webform/src/Entity/WebformOptions.php index 9af18274ee..c2c4bd6d2c 100644 --- a/web/modules/webform/src/Entity/WebformOptions.php +++ b/web/modules/webform/src/Entity/WebformOptions.php @@ -16,6 +16,13 @@ * @ConfigEntityType( * id = "webform_options", * label = @Translation("Webform options"), + * label_collection = @Translation("Webform options"), + * label_singular = @Translation("webform options"), + * label_plural = @Translation("webform options"), + * label_count = @PluralTranslation( + * singular = "@count webform options", + * plural = "@count webform options", + * ), * handlers = { * "storage" = "\Drupal\webform\WebformOptionsStorage", * "access" = "Drupal\webform\WebformOptionsAccessControlHandler", @@ -135,6 +142,7 @@ public function getOptions() { public function setOptions(array $options) { $this->options = Yaml::encode($options); $this->optionsDecoded = NULL; + return $this; } /** @@ -186,11 +194,12 @@ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) * {@inheritdoc} */ public static function getElementOptions(array &$element, $property_name = '#options') { - // If element already has #options return them. - // NOTE: Only WebformOptions can be altered. If you need to alter an - // element's options, @see hook_webform_element_alter(). + // If element already has #options array just call alter hook with + // a NULL id. if (is_array($element[$property_name])) { - return $element[$property_name]; + $options = $element[$property_name]; + \Drupal::moduleHandler()->alter('webform_options', $options, $element); + return $options; } // Return empty options if element does not define an options id. diff --git a/web/modules/webform/src/Entity/WebformSubmission.php b/web/modules/webform/src/Entity/WebformSubmission.php index 342afda2af..7dffefc5af 100644 --- a/web/modules/webform/src/Entity/WebformSubmission.php +++ b/web/modules/webform/src/Entity/WebformSubmission.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Entity; use Drupal\Component\Render\PlainTextOutput; +use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Serialization\Yaml; use Drupal\Component\Utility\Crypt; use Drupal\Core\Entity\EntityStorageInterface; @@ -10,6 +11,7 @@ use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityChangedTrait; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\user\Entity\User; use Drupal\user\UserInterface; @@ -24,6 +26,13 @@ * @ContentEntityType( * id = "webform_submission", * label = @Translation("Webform submission"), + * label_collection = @Translation("Submissions"), + * label_singular = @Translation("submission"), + * label_plural = @Translation("submissions"), + * label_count = @PluralTranslation( + * singular = "@count submission", + * plural = "@count submissions", + * ), * bundle_label = @Translation("Webform"), * handlers = { * "storage" = "Drupal\webform\WebformSubmissionStorage", @@ -45,6 +54,7 @@ * }, * bundle_entity_type = "webform", * list_cache_contexts = { "user" }, + * list_cache_tags = { "config:webform_list", "webform_submission_list" }, * base_table = "webform_submission", * admin_permission = "administer webform", * entity_keys = { @@ -55,10 +65,10 @@ * }, * links = { * "canonical" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}", + * "access-denied" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/access-denied", * "table" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/table", * "text" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/text", * "yaml" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/yaml", - * "yaml" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/yaml", * "edit-form" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/edit", * "notes-form" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/notes", * "resend-form" = "/admin/structure/webform/manage/{webform}/submission/{webform_submission}/resend", @@ -185,7 +195,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { // Can't use entity reference without a target type because it defaults to // an integer which limits reference to only content entities (and not - // config entities like Views, Panels, etc...). + // config entities like Views, Panels, etc…). // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::propertyDefinitions() $fields['entity_id'] = BaseFieldDefinition::create('string') ->setLabel(t('Submitted to: Entity ID')) @@ -223,7 +233,10 @@ public function serial() { public function label() { $submission_label = $this->getWebform()->getSetting('submission_label') ?: \Drupal::config('webform.settings')->get('settings.default_submission_label'); - return PlainTextOutput::renderFromHtml(\Drupal::service('webform.token_manager')->replace($submission_label, $this)); + + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + return PlainTextOutput::renderFromHtml($token_manager->replaceNoRenderContext($submission_label, $this)); } /** @@ -421,11 +434,20 @@ public function getWebform() { /** * {@inheritdoc} */ - public function getSourceEntity() { + public function getSourceEntity($translate = FALSE) { if ($this->entity_type->value && $this->entity_id->value) { $entity_type = $this->entity_type->value; $entity_id = $this->entity_id->value; - return $this->entityTypeManager()->getStorage($entity_type)->load($entity_id); + $source_entity = $this->entityTypeManager()->getStorage($entity_type)->load($entity_id); + + // If translated is set, get the translated source entity. + if ($translate && $source_entity instanceof ContentEntityInterface) { + $langcode = $this->language()->getId(); + if ($source_entity->hasTranslation($langcode)) { + $source_entity = $source_entity->getTranslation($langcode); + } + } + return $source_entity; } else { return NULL; @@ -452,8 +474,10 @@ public function getSourceUrl() { * {@inheritdoc} */ public function getTokenUrl() { - return $this->getSourceUrl() - ->setOption('query', ['token' => $this->token->value]); + $uri = $this->getSourceUrl(); + $options = $uri->getOptions(); + $options['query']['token'] = $this->getToken(); + return $uri->setOptions($options); } /** @@ -543,6 +567,18 @@ public function isSticky() { return (bool) $this->get('sticky')->value; } + /** + * {@inheritdoc} + */ + public function isOwner(AccountInterface $account) { + if ($account->isAnonymous()) { + return !empty($_SESSION['webform_submissions']) && isset($_SESSION['webform_submissions'][$this->id()]); + } + else { + return $account->id() === $this->getOwnerId(); + } + } + /** * {@inheritdoc} */ @@ -625,7 +661,7 @@ public static function preCreate(EntityStorageInterface $storage, array &$values // Get temporary webform entity and store it in the static // WebformSubmission::$webform property. - // This could be reworked to use \Drupal\user\PrivateTempStoreFactory + // This could be reworked to use \Drupal\Core\TempStore\PrivateTempStoreFactory // but it might be overkill since we are just using this to validate // that a webform's elements can be rendered. // @see \Drupal\webform\WebformEntityElementsValidator::validateRendering() @@ -682,7 +718,7 @@ public static function preCreate(EntityStorageInterface $storage, array &$values 'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(), 'token' => Crypt::randomBytesBase64(), 'uri' => preg_replace('#^' . base_path() . '#', '/', $current_request->getRequestUri()), - 'remote_addr' => ($webform && $webform->isConfidential()) ? '' : $current_request->getClientIp(), + 'remote_addr' => ($webform && $webform->hasRemoteAddr()) ? '' : $current_request->getClientIp(), ]; $webform->invokeHandlers(__FUNCTION__, $values); @@ -693,20 +729,31 @@ public static function preCreate(EntityStorageInterface $storage, array &$values * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { + // Because the original data is not stored in the persistent entity storage + // cache we have to reset it for the original webform submission entity. + // @see \Drupal\Core\Entity\EntityStorageBase::doPreSave + // @see \Drupal\Core\Entity\ContentEntityStorageBase::getFromPersistentCache + if (isset($this->original)) { + $this->original->setData($this->originalData); + $this->original->setOriginalData($this->originalData); + } + + $request_time = \Drupal::time()->getRequestTime(); + // Set created. if (!$this->created->value) { - $this->created->value = REQUEST_TIME; + $this->created->value = $request_time; } // Set changed. - $this->changed->value = REQUEST_TIME; + $this->changed->value = $request_time; // Set completed. if ($this->isDraft()) { $this->completed->value = NULL; } elseif (!$this->isCompleted()) { - $this->completed->value = REQUEST_TIME; + $this->completed->value = $request_time; } parent::preSave($storage); @@ -724,7 +771,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { */ public function save() { // Clear the remote_addr for confidential submissions. - if ($this->getWebform()->isConfidential()) { + if (!$this->getWebform()->hasRemoteAddr()) { $this->get('remote_addr')->value = ''; } diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsAccessForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsAccessForm.php index 133929c6f0..8317a9a661 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsAccessForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsAccessForm.php @@ -3,12 +3,40 @@ namespace Drupal\webform\EntitySettings; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\WebformAccessRulesManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Webform access settings. */ class WebformEntitySettingsAccessForm extends WebformEntitySettingsBaseForm { + /** + * Webform access rules manager. + * + * @var \Drupal\webform\WebformAccessRulesManagerInterface + */ + protected $accessRulesManager; + + /** + * WebformEntitySettingsAccessForm constructor. + * + * @param \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager + * Webform access rules manager. + */ + public function __construct(WebformAccessRulesManagerInterface $access_rules_manager) { + $this->accessRulesManager = $access_rules_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.access_rules_manager') + ); + } + /** * {@inheritdoc} */ @@ -16,54 +44,40 @@ public function form(array $form, FormStateInterface $form_state) { /** @var \Drupal\webform\WebformInterface $webform */ $webform = $this->entity; - $access = $webform->getAccessRules(); - $permissions = [ - 'create' => $this->t('Create webform submissions'), - 'view_any' => $this->t('View all webform submissions'), - 'update_any' => $this->t('Update all webform submissions'), - 'delete_any' => $this->t('Delete all webform submissions'), - 'purge_any' => $this->t('Purge all webform submissions'), - 'view_own' => $this->t('View own webform submissions'), - 'update_own' => $this->t('Update own webform submissions'), - 'delete_own' => $this->t('Delete own webform submissions'), - 'administer' => $this->t('Administer webform & submissions'), - 'test' => $this->t('Test webform'), - ]; - $form['access']['#tree'] = TRUE; - foreach ($permissions as $name => $title) { - $form['access'][$name] = [ - '#type' => ($name === 'create') ? 'fieldset' : 'details', - '#title' => $title, - '#open' => ($access[$name]['roles'] || $access[$name]['users']) ? TRUE : FALSE, + + $access = $webform->getAccessRules() + $this->accessRulesManager->getDefaultAccessRules(); + $access_rules = $this->accessRulesManager->getAccessRulesInfo(); + foreach ($access_rules as $access_rule => $info) { + $form['access'][$access_rule] = [ + '#type' => ($access_rule === 'create') ? 'fieldset' : 'details', + '#title' => $info['title'], + '#open' => ($access[$access_rule]['roles'] || $access[$access_rule]['users']) ? TRUE : FALSE, + '#description' => $info['description'], + // Never convert description to help. + // @see _webform_preprocess_description_help() + '#help' => FALSE, ]; - $form['access'][$name]['roles'] = [ + $form['access'][$access_rule]['roles'] = [ '#type' => 'webform_roles', '#title' => $this->t('Roles'), - '#include_anonymous' => (!in_array($name, ['update_any', 'delete_any', 'purge_any'])) ? TRUE : FALSE, - '#default_value' => $access[$name]['roles'], + '#include_anonymous' => (!in_array($access_rule, ['update_any', 'delete_any', 'purge_any'])) ? TRUE : FALSE, + '#default_value' => $access[$access_rule]['roles'], ]; - $form['access'][$name]['users'] = [ + $form['access'][$access_rule]['users'] = [ '#type' => 'webform_users', '#title' => $this->t('Users'), - '#default_value' => $access[$name]['users'] ? $this->entityTypeManager->getStorage('user')->loadMultiple($access[$name]['users']) : [], + '#default_value' => $access[$access_rule]['users'] ? $this->entityTypeManager->getStorage('user')->loadMultiple($access[$access_rule]['users']) : [], ]; - $form['access'][$name]['permissions'] = [ + $form['access'][$access_rule]['permissions'] = [ '#type' => 'webform_permissions', '#title' => $this->t('Permissions'), '#multiple' => TRUE, '#select2' => TRUE, - '#default_value' => $access[$name]['permissions'], + '#default_value' => $access[$access_rule]['permissions'], ]; } - $form['access']['administer']['message'] = [ - '#weight' => -10, - '#type' => 'webform_message', - '#message_type' => 'warning', - '#message_message' => $this->t('<strong>Warning</strong>: The below settings give the below users, permissions, and roles full access to this webform and its submissions.'), - ]; - return parent::form($form, $form_state); } diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsAssetsForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsAssetsForm.php index 40c81de918..83864a4491 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsAssetsForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsAssetsForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Element\WebformMessage; /** * Webform CSS and JS assets. @@ -21,6 +22,8 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_message', '#message_message' => $this->t('The below CSS and JavasScript will be loaded on all pages that references and loads this webform.'), '#message_type' => 'info', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, ]; $form['css'] = [ '#type' => 'fieldset', diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsBaseForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsBaseForm.php index 7b7b27e11f..f83a740377 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsBaseForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsBaseForm.php @@ -4,7 +4,8 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Render\Element; +use Drupal\webform\Utility\WebformDialogHelper; +use Drupal\webform\Utility\WebformElementHelper; /** * Base webform entity settings form. @@ -31,6 +32,13 @@ protected function actions(array $form, FormStateInterface $form_state) { if ($this->operation != 'settings') { unset($actions['delete']); } + + // Open delete button in a modal dialog. + if (isset($actions['delete'])) { + $actions['delete']['#attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, $actions['delete']['#attributes']['class']); + WebformDialogHelper::attachLibraries($actions['delete']); + } + return $actions; } @@ -49,7 +57,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $this->logger('webform')->notice('Webform settings @label has been saved.', $context); - drupal_set_message($this->t('Webform settings %label has been saved.', ['%label' => $webform->label()])); + $this->messenger()->addStatus($this->t('Webform settings %label has been saved.', ['%label' => $webform->label()])); } /** @@ -62,8 +70,7 @@ public function save(array $form, FormStateInterface $form_state) { */ protected function appendDefaultValueToElementDescriptions(array &$form, array $default_settings) { foreach ($form as $key => &$element) { - // Skip if not a FAPI element. - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -71,10 +78,11 @@ protected function appendDefaultValueToElementDescriptions(array &$form, array $ if (!isset($element['#description'])) { $element['#description'] = ''; } - $element['#description'] .= ($element['#description'] ? '<br /><br />' : ''); - // @todo: Stop quotes from being encoded. (i.e. "Submit" => "Submit"e;) $value = $default_settings["default_$key"]; - $element['#description'] .= $this->t('Defaults to: %value', ['%value' => $value]); + if (!is_array($value)) { + $element['#description'] .= ($element['#description'] ? '<br /><br />' : ''); + $element['#description'] .= $this->t('Defaults to: %value', ['%value' => $value]); + } } $this->appendDefaultValueToElementDescriptions($element, $default_settings); diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsConfirmationForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsConfirmationForm.php index 23af324d93..ffd10da671 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsConfirmationForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsConfirmationForm.php @@ -47,7 +47,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $elements = $webform->getElementsDecoded(); if (!empty($elements['#method'])) { - drupal_set_message($this->t('Form is being posted using a custom method. Confirmation page must be handled by the <a href=":href">custom form action</a>.', [':href' => $webform->toUrl('settings-form')->toString()]), 'warning'); + $this->messenger()->addWarning($this->t('Form is being posted using a custom method. Confirmation page must be handled by the <a href=":href">custom form action</a>.', [':href' => $webform->toUrl('settings-form')->toString()])); return $form; } @@ -71,7 +71,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['confirmation_type']['ajax_confirmation'] = [ '#type' => 'webform_message', '#message_type' => 'warning', - '#message_message' => $this->t("Only 'Inline', 'Message', and 'Modal' confirmation types are fully supported by Ajax."), + '#message_message' => $this->t("Only 'Inline', 'Message', 'Modal', and 'None' confirmation types are fully supported by Ajax."), '#access' => $settings['ajax'], '#states' => [ 'invisible' => [ @@ -80,6 +80,8 @@ public function form(array $form, FormStateInterface $form_state) { [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_MESSAGE]], 'or', [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_MODAL]], + 'or', + [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_NONE]], ], ], ]; @@ -93,6 +95,7 @@ public function form(array $form, FormStateInterface $form_state) { WebformInterface::CONFIRMATION_MODAL => $this->t('Modal (reloads the current page/form and displays the confirmation message in a modal dialog)'), WebformInterface::CONFIRMATION_URL => $this->t('URL (redirects to a custom path or URL)'), WebformInterface::CONFIRMATION_URL_MESSAGE => $this->t('URL with message (redirects to a custom path or URL and displays the confirmation message at the top of the page)'), + WebformInterface::CONFIRMATION_NONE => $this->t('None (reloads the current page and does not display a confirmation message)'), ], '#default_value' => $settings['confirmation_type'], ]; @@ -144,6 +147,10 @@ public function form(array $form, FormStateInterface $form_state) { '#default_value' => $settings['confirmation_exclude_token'], '#access' => !$webform->isResultsDisabled(), ]; + $form['confirmation_url']['token_tree_link'] = $this->tokenManager->buildTreeElement( + ['webform', 'webform_submission', 'webform_handler'], + $this->t('You may use tokens to pass query string parameters. Make sure all tokens include the urlencode suffix. (i.e. [webform-submission:values:email:urlencode])') + ); // Confirmation settings. $form['confirmation_settings'] = [ @@ -152,7 +159,9 @@ public function form(array $form, FormStateInterface $form_state) { '#open' => TRUE, '#states' => [ 'invisible' => [ - ':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_URL], + [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_URL]], + 'or', + [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_NONE]], ], ], ]; @@ -176,11 +185,13 @@ public function form(array $form, FormStateInterface $form_state) { '#default_value' => $settings['confirmation_message'], '#states' => [ 'invisible' => [ - ':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_URL], + [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_URL]], + 'or', + [':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_NONE]], ], ], ]; - $form['confirmation_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['confirmation_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Attributes. $form['confirmation_attributes_container'] = [ @@ -244,7 +255,19 @@ public function form(array $form, FormStateInterface $form_state) { '#classes' => $this->config('webform.settings')->get('settings.confirmation_back_classes'), '#default_value' => $settings['confirmation_back_attributes'], ]; - $form['back']['back_container']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['back']['back_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + + // None. + $form['confirmation_type']['none'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('This setting assumes that a webform handler will manage the displaying of a confirmation message.'), + '#states' => [ + 'visible' => [ + ':input[name="confirmation_type"]' => ['value' => WebformInterface::CONFIRMATION_NONE], + ], + ], + ]; $this->tokenManager->elementValidate($form); diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsFormForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsFormForm.php index 1063e3a16f..9eafdcb786 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsFormForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsFormForm.php @@ -4,6 +4,8 @@ use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\Utility\WebformElementHelper; @@ -55,7 +57,7 @@ public function form(array $form, FormStateInterface $form_state) { // Form settings. $form['form_settings'] = [ '#type' => 'details', - '#title' => $this->t('Form settings'), + '#title' => $this->t('Form general settings'), '#open' => TRUE, ]; $form['form_settings']['status'] = [ @@ -80,6 +82,21 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; + $t_args = [ + ':page_cache_href' => 'https://www.drupal.org/docs/8/administering-a-drupal-8-site/internal-page-cache', + ':issue_href' => 'https://www.drupal.org/node/2352009', + ':cache_control_override_href' => 'https://www.drupal.org/project/cache_control_override', + ]; + if ($this->moduleHandler->moduleExists('page_cache') && !$this->moduleHandler->moduleExists('cache_control_override')) { + $form['form_settings']['scheduled']['page_cache'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#message_message' => $this->t('Scheduled forms do not work as expected for anonymous users when Drupal\'s <a href=":page_cache_href">Internal Page Cache</a> module is enabled. This is a <a href=":issue_href">known issue</a>.', $t_args) . '<br/><br/>' . + '<strong>' . $this->t('It is strongly recommended that you install the <a href=":cache_control_override_href">Cache Control Override</a> module.', $t_args) . '</strong>', + ]; + } $form['form_settings']['scheduled']['open'] = [ '#type' => 'datetime', '#title' => $this->t('Open'), @@ -90,6 +107,7 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => [ '#type' => 'webform_help', '#help' => $this->t('If the open date/time is left blank, this form will immediately be opened.'), + '#help_title' => $this->t('Open'), ], ]; $form['form_settings']['scheduled']['close'] = [ @@ -102,6 +120,7 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => [ '#type' => 'webform_help', '#help' => $this->t('If the close date/time is left blank, this webform will never be closed.'), + '#help_title' => $this->t('Close'), ], '#default_value' => $webform->get('close') ? DrupalDateTime::createFromTimestamp(strtotime($webform->get('close'))) : NULL, ]; @@ -110,6 +129,19 @@ public function form(array $form, FormStateInterface $form_state) { $form['form_settings']['status']['#access'] = FALSE; $form['form_settings']['scheduled']['#access'] = FALSE; } + $form['form_settings']['form_title'] = [ + '#type' => 'select', + '#title' => $this->t('Form title display'), + '#description' => $this->t("Select how the form's title is displayed when this webform is attached to a source entity. This title is only displayed when a webform is linked to from a source entity or opened in dialog."), + '#options' => [ + WebformInterface::TITLE_SOURCE_ENTITY_WEBFORM => $this->t('Source entity: Webform'), + WebformInterface::TITLE_WEBFORM_SOURCE_ENTITY => $this->t('Webform: Source entity'), + WebformInterface::TITLE_WEBFORM => $this->t('Webform'), + WebformInterface::TITLE_SOURCE_ENTITY => $this->t('Source entity'), + ], + '#required' => TRUE, + '#default_value' => $settings['form_title'], + ]; $form['form_settings']['form_open_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Form open message'), @@ -128,47 +160,20 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('A message to be displayed if the webform breaks.'), '#default_value' => $settings['form_exception_message'], ]; - $form['form_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); - - // Form attributes. - $form['form_attributes'] = [ + $form['form_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + $form['form_settings']['form_attributes'] = [ '#type' => 'details', '#title' => $this->t('Form attributes'), '#open' => TRUE, ]; $elements = $webform->getElementsDecoded(); - $form['form_attributes']['attributes'] = [ + $form['form_settings']['form_attributes']['attributes'] = [ '#type' => 'webform_element_attributes', '#title' => $this->t('Form'), '#classes' => $this->config('webform.settings')->get('settings.form_classes'), '#default_value' => (isset($elements['#attributes'])) ? $elements['#attributes'] : [], ]; - // Form access denied. - $form['form_access_denied'] = [ - '#type' => 'details', - '#title' => $this->t('Access denied'), - '#open' => TRUE, - ]; - $form['form_access_denied']['form_login'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Redirect to login when access denied to webform'), - '#return_value' => TRUE, - '#default_value' => $settings['form_login'], - ]; - $form['form_access_denied']['form_login_message'] = [ - '#type' => 'webform_html_editor', - '#title' => $this->t('Login message when access denied to webform'), - '#description' => $this->t('A message to be displayed on the login page.'), - '#default_value' => $settings['form_login_message'], - '#states' => [ - 'visible' => [ - ':input[name="form_login"]' => ['checked' => TRUE], - ], - ], - ]; - $form['form_access_denied']['token_tree_link'] = $this->tokenManager->buildTreeLink(); - // Form behaviors. $form['form_behaviors'] = [ '#type' => 'details', @@ -198,10 +203,91 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; + if ($settings['draft'] !== WebformInterface::DRAFT_NONE) { + $form['form_behaviors']['form_reset_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Currently loaded drafts will be deleted when the form is reset.'), + '#weight' => $form['form_behaviors']['form_reset']['#weight'] + 1, + '#states' => [ + 'visible' => [ + ':input[name="form_reset"]' => ['checked' => TRUE], + ], + ], + + ]; + } + + // Access denied. + $form['access_denied'] = [ + '#type' => 'details', + '#title' => $this->t('Form access denied settings'), + '#open' => TRUE, + ]; + $form['access_denied']['form_access_denied'] = [ + '#type' => 'radios', + '#title' => $this->t('When a user is denied access to this webform'), + '#description' => $this->t('Select what happens when a user is denied access to this webform.') . + '<br/><br/>' . + $this->t('Go to <a href=":href">submission settings</a> to select what happens when a user is denied access to submissions.', [':href' => Url::fromRoute('entity.webform.settings_submissions', ['webform' => $webform->id()])->toString()]), + + '#options' => [ + WebformInterface::ACCESS_DENIED_DEFAULT => $this->t('Default (Displays the default access denied page)'), + WebformInterface::ACCESS_DENIED_MESSAGE => $this->t('Inline (Displays message when access is denied to field, nodes, and blocks)'), + WebformInterface::ACCESS_DENIED_PAGE => $this->t('Page (Displays message when access is denied to forms, fields, nodes, and blocks)'), + WebformInterface::ACCESS_DENIED_LOGIN => $this->t('Login (Redirects to user login form and displays message. Field, nodes, and block only display the message.)'), + ], + '#required' => TRUE, + '#default_value' => $settings['form_access_denied'], + ]; + $form['access_denied']['access_denied_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="form_access_denied"]' => ['!value' => WebformInterface::ACCESS_DENIED_DEFAULT], + ], + ], + ]; + $form['access_denied']['access_denied_container']['form_access_denied_title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Access denied title'), + '#description' => $this->t('Page title to be shown on access denied page'), + '#default_value' => $settings['form_access_denied_title'], + '#states' => [ + 'visible' => [ + ':input[name="form_access_denied"]' => ['value' => WebformInterface::ACCESS_DENIED_PAGE], + ], + ], + ]; + $form['access_denied']['access_denied_container']['form_access_denied_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Access denied message'), + '#description' => $this->t('Will be displayed either in-line or as a status message depending on the setting above.'), + '#default_value' => $settings['form_access_denied_message'], + ]; + $form['access_denied']['access_denied_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + $form['access_denied']['access_denied_container']['access_denied_attributes'] = [ + '#type' => 'details', + '#title' => $this->t('Access denied message attributes'), + '#open' => TRUE, + '#states' => [ + 'visible' => [ + [':input[name="form_access_denied"]' => ['value' => WebformInterface::ACCESS_DENIED_MESSAGE]], + 'or', + [':input[name="form_access_denied"]' => ['value' => WebformInterface::ACCESS_DENIED_PAGE]], + ], + ], + ]; + $form['access_denied']['access_denied_container']['access_denied_attributes']['form_access_denied_attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Access denied message'), + '#default_value' => $settings['form_access_denied_attributes'], + ]; + // Wizard settings. $form['wizard_settings'] = [ '#type' => 'details', - '#title' => $this->t('Wizard settings'), + '#title' => $this->t('Form wizard settings'), '#open' => TRUE, '#states' => [ 'visible' => [ @@ -212,36 +298,76 @@ public function form(array $form, FormStateInterface $form_state) { $form['wizard_settings']['wizard_progress_bar'] = [ '#type' => 'checkbox', '#title' => $this->t('Show wizard progress bar'), + '#description' => $this->t('If checked, a progress bar will displayed about the form.'), '#return_value' => TRUE, '#default_value' => $settings['wizard_progress_bar'], ]; + $form['wizard_settings']['wizard_progress_link'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Link to previous pages in progress bar'), + '#description' => $this->t('If checked, previous pages will be link in the progress bar.'), + '#return_value' => TRUE, + '#default_value' => $settings['wizard_progress_link'], + '#states' => [ + 'visible' => [ + ':input[name="wizard_progress_bar"]' => ['checked' => TRUE], + ], + ], + ]; $form['wizard_settings']['wizard_progress_pages'] = [ '#type' => 'checkbox', '#title' => $this->t('Show wizard progress pages'), + '#description' => $this->t('If checked, the current page and total remaining pages will be displayed. (i.e. Page 1 of 10)'), '#return_value' => TRUE, '#default_value' => $settings['wizard_progress_pages'], ]; $form['wizard_settings']['wizard_progress_percentage'] = [ '#type' => 'checkbox', '#title' => $this->t('Show wizard progress percentage'), + '#description' => $this->t('If checked, the percentage of completed pages will be displayed. (i.e. 10%)'), '#return_value' => TRUE, '#default_value' => $settings['wizard_progress_percentage'], ]; + $form['wizard_settings']['wizard_preview_link'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Link to previous pages in preview'), + '#description' => $this->t("If checked, the preview page will included 'Edit' buttons for each previous page.") . '<br/><br/>' . + '<em>' . $this->t("This settings is only available when 'Enable preview page' is enabled.") . '</em>', + '#return_value' => TRUE, + '#default_value' => $settings['wizard_preview_link'], + '#states' => [ + 'enabled' => [ + ':input[name="preview"]' => ['!value' => DRUPAL_DISABLED], + ], + ], + ]; $form['wizard_settings']['wizard_confirmation'] = [ '#type' => 'checkbox', '#title' => $this->t('Include confirmation page in progress'), + '#description' => $this->t("If checked, the confirmation page will be included in the progress bar."), '#return_value' => TRUE, '#default_value' => $settings['wizard_confirmation'], + '#states' => [ + 'visible' => [ + [':input[name="wizard_progress_bar"]' => ['checked' => TRUE]], + 'or', + [':input[name="wizard_progress_pages"]' => ['checked' => TRUE]], + 'or', + [':input[name="wizard_progress_percentage"]' => ['checked' => TRUE]], + ], + ], ]; $form['wizard_settings']['wizard_start_label'] = [ '#type' => 'textfield', '#title' => $this->t('Wizard start label'), + '#description' => $this->t('The first page label in the progress bar. Subsequent pages are titled by their wizard page title.'), '#size' => 20, '#default_value' => $settings['wizard_start_label'], ]; $form['wizard_settings']['wizard_confirmation_label'] = [ '#type' => 'textfield', '#title' => $this->t('Wizard end label'), + '#description' => $this->t("The confirmation page label's in the progress bar."), '#size' => 20, '#default_value' => $settings['wizard_confirmation_label'], '#states' => [ @@ -253,6 +379,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['wizard_settings']['wizard_track'] = [ '#type' => 'select', '#title' => $this->t('Track wizard progress in the URL by'), + '#description' => $this->t("Progress tracking allows analytic software to capture a multi-step form's progress."), '#options' => [ 'name' => $this->t("Page name (?page=contact)"), 'index' => $this->t("Page index (?page=2)"), @@ -264,7 +391,7 @@ public function form(array $form, FormStateInterface $form_state) { // Preview settings. $form['preview_settings'] = [ '#type' => 'details', - '#title' => $this->t('Preview settings'), + '#title' => $this->t('Form preview settings'), '#open' => TRUE, '#states' => [ 'visible' => [ @@ -295,7 +422,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['preview_settings']['preview_container']['preview_label'] = [ '#type' => 'textfield', '#title' => $this->t('Preview label'), - '#description' => $this->t("The text displayed within a multistep wizard's progress bar"), + '#description' => $this->t("The text displayed within a multi-step wizard's progress bar"), '#default_value' => $settings['preview_label'], ]; $form['preview_settings']['preview_container']['preview_title'] = [ @@ -328,6 +455,12 @@ public function form(array $form, FormStateInterface $form_state) { '#return_value' => TRUE, '#default_value' => $settings['preview_exclude_empty'], ]; + $form['preview_settings']['preview_container']['elements']['preview_exclude_empty_checkbox'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Exclude unselected checkboxes'), + '#return_value' => TRUE, + '#default_value' => $settings['preview_exclude_empty_checkbox'], + ]; $form['preview_settings']['preview_container']['preview_attributes'] = [ '#type' => 'details', '#title' => $this->t('Preview attributes'), @@ -339,7 +472,23 @@ public function form(array $form, FormStateInterface $form_state) { '#classes' => $this->config('webform.settings')->get('settings.preview_classes'), '#default_value' => $settings['preview_attributes'], ]; - $form['preview_settings']['preview_container']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['preview_settings']['preview_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + + // File settings. + $form['file_settings'] = [ + '#type' => 'details', + '#title' => $this->t('File settings'), + '#open' => TRUE, + '#access' => $webform->hasManagedFile(), + ]; + $form['file_settings']['form_file_limit'] = [ + '#type' => 'textfield', + '#title' => $this->t('File upload limit'), + '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to set the file upload limit for this form.'), + '#element_validate' => [['\Drupal\webform\Form\AdminConfig\WebformAdminConfigElementsForm', 'validateMaxFilesize']], + '#size' => 10, + '#default_value' => $settings['form_file_limit'], + ]; // Custom settings. $properties = WebformElementHelper::getProperties($webform->getElementsDecoded()); @@ -350,13 +499,13 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['custom_settings'] = [ '#type' => 'details', - '#title' => $this->t('Custom settings'), + '#title' => $this->t('Form custom settings'), '#open' => array_filter($properties) ? TRUE : FALSE, '#access' => !$this->moduleHandler->moduleExists('webform_ui') || $this->currentUser()->hasPermission('edit webform source'), ]; $form['custom_settings']['method'] = [ '#type' => 'select', - '#title' => $this->t('Method'), + '#title' => $this->t('Form method'), '#description' => $this->t('The HTTP method with which the form will be submitted.') . '<br /><br />' . '<em>' . $this->t('Selecting a custom POST or GET method will automatically disable wizards, previews, drafts, submissions, limits, purging, confirmations, emails, and handlers.') . '</em>', '#options' => [ @@ -379,7 +528,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['custom_settings']['action'] = [ '#type' => 'textfield', - '#title' => $this->t('Action'), + '#title' => $this->t('Form action'), '#description' => $this->t('The URL or path to which the webform will be submitted.'), '#states' => [ 'invisible' => [ @@ -401,7 +550,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['custom_settings']['custom'] = [ '#type' => 'webform_codemirror', '#mode' => 'yaml', - '#title' => $this->t('Custom properties'), + '#title' => $this->t('Form custom properties'), '#description' => $this->t('Properties do not have to prepended with a hash (#) character, the hash character will be automatically added to the custom properties.') . '<br /><br />' . @@ -515,7 +664,7 @@ protected function getFormBehaviors() { 'form_reset' => [ 'group' => $this->t('Form'), 'title' => $this->t('Display reset button'), - 'form_description' => $this->t("If checked, users will be able to reset a form and restart multistep wizards."), + 'form_description' => $this->t("If checked, users will be able to reset a form and restart multi-step wizards. Current drafts will be deleted when the form is reset."), ], 'form_submit_once' => [ 'group' => $this->t('Form'), @@ -556,7 +705,6 @@ protected function getFormBehaviors() { 'title' => $this->t('Disable inline form errors'), 'all_description' => $this->t('Inline form errors is disabled for all forms.'), 'form_description' => $this->t('If checked, <a href=":href">inline form errors</a> will be disabled for this form.', [':href' => 'https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-form-errors-module-overview']), - 'access' => (\Drupal::moduleHandler()->moduleExists('inline_form_errors') && floatval(\Drupal::VERSION) >= 8.5), ], 'form_required' => [ 'group' => $this->t('Validation'), diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsGeneralForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsGeneralForm.php index 4890d37f00..9da0bb18f0 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsGeneralForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsGeneralForm.php @@ -2,6 +2,7 @@ namespace Drupal\webform\EntitySettings; +use Drupal\Component\Serialization\Json; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Url; @@ -107,6 +108,13 @@ public function form(array $form, FormStateInterface $form_state) { '#access' => $this->moduleHandler->moduleExists('webform_templates'), '#default_value' => $webform->isTemplate(), ]; + $form['general_settings']['archive'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Archive this webform'), + '#description' => $this->t('If checked, this webform will be closed and unavailable to webform blocks and fields.'), + '#return_value' => TRUE, + '#default_value' => $webform->isArchived(), + ]; $form['general_settings']['results_disabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Disable saving of submissions'), @@ -242,11 +250,6 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Ajax settings'), '#open' => TRUE, '#access' => empty($elements['#method']), - '#states' => [ - 'visible' => [ - ':input[name="method"]' => ['value' => ''], - ], - ], ]; $form['ajax_settings']['ajax'] = [ '#type' => 'checkbox', @@ -257,7 +260,7 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['ajax_settings']['ajax_scroll_top'] = [ '#type' => 'radios', - '#title' => $this->t('On Ajax load, scroll to the top of the...'), + '#title' => $this->t('On Ajax load, scroll to the top of the…'), '#description' => $this->t("Select where the page should be scrolled to when paging, saving of drafts, previews, submissions, and confirmations. Select 'None' to disable scrolling."), '#options' => [ '' => $this->t('None'), @@ -272,6 +275,79 @@ public function form(array $form, FormStateInterface $form_state) { '#default_value' => $settings['ajax_scroll_top'], ]; + // Dialog settings. + if ($default_settings['dialog']) { + $rows = []; + // Preset examples. + foreach ($default_settings['dialog_options'] as $dialog_name => $dialog_options) { + $dialog_options += [ + 'width' => $this->t('auto'), + 'height' => $this->t('auto'), + ]; + $dialog_link = [ + '#type' => 'link', + '#url' => $webform->toUrl(), + '#title' => $this->t('Test @title', ['@title' => $dialog_options['title']]), + '#attributes' => [ + 'class' => ['webform-dialog', 'webform-dialog-' . $dialog_name, 'button'], + ], + ]; + $row = []; + $row['title'] = $dialog_options['title']; + $row['dimensions'] = $dialog_options['width'] . ' x ' . $dialog_options['height']; + $row['link'] = ['data' => $dialog_link, 'nowrap' => 'nowrap']; + $row['source'] = $this->buildDialogSource($dialog_link); + $rows[$dialog_name] = $row; + } + + // Custom example. + $dialog_link = [ + '#type' => 'link', + '#title' => $this->t('Test Custom'), + '#url' => $webform->toUrl(), + '#attributes' => [ + 'class' => ['webform-dialog', 'button'], + 'data-dialog-options' => Json::encode([ + 'width' => 400, + 'height' => 400, + ]), + ], + ]; + $row = []; + $row['title'] = $this->t('Custom'); + $row['dimensions'] = '400 x 400'; + $row['link'] = ['data' => $dialog_link]; + $row['source'] = $this->buildDialogSource($dialog_link); + $rows['custom'] = $row; + + $form['dialog_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Dialog settings'), + '#description' => $this->t('Below are links and code snippets that can be inserted into your website to open this form in a modal dialog.'), + '#open' => TRUE, + 'table' => [ + '#type' => 'table', + '#header' => [ + ['data' => $this->t('Title'), 'width' => '10%', 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => $this->t('Dimensions'), 'width' => '10%', 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => $this->t('Example'), 'width' => '10%', 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => $this->t('Source'), 'width' => '70%'], + ], + '#rows' => $rows, + ], + ]; + + $form['dialog_settings']['form_prepopulate_source_entity'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow (dialog) source entity to be populated using query string parameters'), + '#description' => $this->t("If checked, source entity can be populated using query string parameters.") . + '<br/><br/>' . $this->t("For example, appending <code>?source_entity_type=node&source_entity_id=1</code> to a webform's URL would set a submission's 'Submitted to' value to 'node:1'.") . + '<br/><br/>' . $this->t("You can also append <code>?source_entity_type=ENTITY_TYPE&source_entity_id=ENTITY_ID</code> and the <code>ENTITY_TYPE</code> and <code>ENTITY_ID</code> parameters will automatically be replaced based on the current page's source entity."), + '#return_value' => TRUE, + '#default_value' => $settings['form_prepopulate_source_entity'], + ]; + } + if ($this->currentUser()->hasPermission('administer webform')) { // Author information. $form['author_information'] = [ @@ -293,6 +369,21 @@ public function form(array $form, FormStateInterface $form_state) { ]; } + // Advanced settings. + $form['advanced_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Advanced settings'), + '#open' => TRUE, + '#access' => $this->moduleHandler->moduleExists('webform_node'), + ]; + $form['advanced_settings']['weight'] = [ + '#type' => 'weight', + '#title' => $this->t('Weight'), + '#description' => $this->t('Weight is used when multiple webforms are associated to the same webform node.'), + '#default_value' => $webform->get('weight'), + '#access' => $this->moduleHandler->moduleExists('webform_node'), + ]; + // Third party settings. $form['third_party_settings'] = [ '#type' => 'details', @@ -308,6 +399,8 @@ public function form(array $form, FormStateInterface $form_state) { ksort($form['third_party_settings']); } + $form['#attached']['library'][] = 'webform/webform.admin.settings'; + return parent::form($form, $form_state); } @@ -338,6 +431,7 @@ public function save(array $form, FormStateInterface $form_state) { $values['title'], $values['description'], $values['category'], + $values['weight'], $values['template'], $values['uid'] ); @@ -348,4 +442,46 @@ public function save(array $form, FormStateInterface $form_state) { parent::save($form, $form_state); } + /** + * Build dialog source. + * + * @param array $link + * Webform link. + * + * @return array + * A renderable array containing dialog source + */ + protected function buildDialogSource(array $link) { + $source_entity_link = $link; + $source_entity_link['#url'] = clone $source_entity_link['#url']; + $source_entity_link['#url']->setOption('query', ['source_entity_type' => 'ENTITY_TYPE', 'source_entity_id' => 'ENTITY_ID']); + + return [ + 'data' => [ + 'webform' => [ + '#theme' => 'webform_codemirror', + '#type' => 'html', + '#code' => (string) \Drupal::service('renderer')->renderPlain($link), + '#suffix' => '<br/>', + ], + 'source_entity' => [ + 'container' => [ + '#type' => 'container', + '#attributes' => ['class' => ['js-form-item']], + '#states' => [ + 'visible' => [ + ':input[name="form_prepopulate_source_entity"]' => ['checked' => TRUE], + ], + ], + 'link' => [ + '#theme' => 'webform_codemirror', + '#type' => 'html', + '#code' => (string) \Drupal::service('renderer')->renderPlain($source_entity_link), + ], + ], + ], + ], + ]; + } + } diff --git a/web/modules/webform/src/EntitySettings/WebformEntitySettingsSubmissionsForm.php b/web/modules/webform/src/EntitySettings/WebformEntitySettingsSubmissionsForm.php index bb5f3797e6..34abd39c9f 100644 --- a/web/modules/webform/src/EntitySettings/WebformEntitySettingsSubmissionsForm.php +++ b/web/modules/webform/src/EntitySettings/WebformEntitySettingsSubmissionsForm.php @@ -4,6 +4,8 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\views\Entity\View; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionStorageInterface; @@ -50,7 +52,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Display warning and disable the submission form. if ($webform->isResultsDisabled()) { - drupal_set_message($this->t('Saving of submissions is disabled, submission settings, submission limits, purging and the saving of drafts is disabled. Submissions must be sent via an email or handled using a <a href=":href">custom webform handler</a>.', [':href' => $webform->toUrl('handlers')->toString()]), 'warning'); + $this->messenger()->addWarning($this->t('Saving of submissions is disabled, submission settings, submission limits, purging and the saving of drafts is disabled. Submissions must be sent via an email or handled using a <a href=":href">custom webform handler</a>.', [':href' => $webform->toUrl('handlers')->toString()])); return $form; } @@ -76,7 +78,7 @@ public function form(array $form, FormStateInterface $form_state) { // Submission settings. $form['submission_settings'] = [ '#type' => 'details', - '#title' => $this->t('Submission settings'), + '#title' => $this->t('Submission general settings'), '#open' => TRUE, ]; $form['submission_settings']['submission_label'] = [ @@ -93,9 +95,21 @@ public function form(array $form, FormStateInterface $form_state) { $form['submission_settings']['submission_locked_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Submission locked message'), - '#description' => $this->t('A message to be displayed if submission is lockec.'), + '#description' => $this->t('A message to be displayed if submission is locked.'), '#default_value' => $settings['submission_locked_message'], ]; + $form['submission_settings']['previous_submission_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Previous submission message'), + '#description' => $this->t('A message to be displayed when there is previous submission.'), + '#default_value' => $settings['previous_submission_message'], + ]; + $form['submission_settings']['previous_submissions_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Previous submissions message'), + '#description' => $this->t('A message to be displayed when there are previous submissions.'), + '#default_value' => $settings['previous_submissions_message'], + ]; $form['submission_settings']['next_serial'] = [ '#type' => 'number', '#title' => $this->t('Next submission number'), @@ -103,68 +117,30 @@ public function form(array $form, FormStateInterface $form_state) { '#min' => 1, '#default_value' => $webform_storage->getNextSerial($webform), ]; - $form['submission_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); - $form['submission_settings']['submission_columns'] = [ + $form['submission_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + $form['submission_settings']['submission_container']['elements'] = [ '#type' => 'details', - '#title' => $this->t('Submission columns'), - '#description' => $this->t('Below columns are displayed to users who can view previous submissions and/or pending drafts.'), - ]; - // Submission user columns. - // @see \Drupal\webform\Form\WebformResultsCustomForm::buildForm - $available_columns = $webform_submission_storage->getColumns($webform); - // Remove columns that should never be displayed to users. - $available_columns = array_diff_key($available_columns, array_flip(['uuid', 'in_draft', 'entity', 'sticky', 'locked', 'notes', 'uid', 'operations'])); - $custom_columns = $webform_submission_storage->getUserColumns($webform); - // Change sid's # to an actual label. - $available_columns['sid']['title'] = $this->t('Submission ID'); - if (isset($custom_columns['sid'])) { - $custom_columns['sid']['title'] = $this->t('Submission ID'); - } - // Get available columns as option. - $columns_options = []; - foreach ($available_columns as $column_name => $column) { - $title = (strpos($column_name, 'element__') === 0) ? ['data' => ['#markup' => '<b>' . $column['title'] . '</b>']] : $column['title']; - $key = (isset($column['key'])) ? str_replace('webform_', '', $column['key']) : $column['name']; - $columns_options[$column_name] = ['title' => $title, 'key' => $key]; - } - // Get custom columns as the default value. - $columns_keys = array_keys($custom_columns); - $columns_default_value = array_combine($columns_keys, $columns_keys); - // Display columns in sortable table select element. - $form['submission_settings']['submission_columns']['submission_user_columns'] = [ - '#type' => 'webform_tableselect_sort', - '#header' => [ - 'title' => $this->t('Title'), - 'key' => $this->t('Key'), - ], - '#options' => $columns_options, - '#default_value' => $columns_default_value, + '#title' => $this->t('Included submission values'), + '#description' => $this->t('If you wish to include only parts of the submission when viewing as HTML, table, or plain text, select the elements that should be included. Please note, element specific access controls are still applied to displayed elements.'), + '#open' => $settings['submission_excluded_elements'] ? TRUE : FALSE, ]; - - // Submission access denied. - $form['submission_access_denied'] = [ - '#type' => 'details', - '#title' => $this->t('Access denied'), - '#open' => TRUE, + $form['submission_settings']['submission_container']['elements']['submission_excluded_elements'] = [ + '#type' => 'webform_excluded_elements', + '#webform_id' => $this->getEntity()->id(), + '#default_value' => $settings['submission_excluded_elements'], ]; - $form['submission_access_denied']['submission_login'] = [ + $form['submission_settings']['submission_container']['elements']['submission_exclude_empty'] = [ '#type' => 'checkbox', - '#title' => $this->t('Redirect to login when access denied to submission'), + '#title' => $this->t('Exclude empty elements'), '#return_value' => TRUE, - '#default_value' => $settings['submission_login'], + '#default_value' => $settings['submission_exclude_empty'], ]; - $form['submission_access_denied']['submission_login_message'] = [ - '#type' => 'webform_html_editor', - '#title' => $this->t('Login message when access denied to submission'), - '#description' => $this->t('A message to be displayed on the login page.'), - '#default_value' => $settings['submission_login_message'], - '#states' => [ - 'visible' => [ - ':input[name="submission_login"]' => ['checked' => TRUE], - ], - ], + $form['submission_settings']['submission_container']['elements']['submission_exclude_empty_checkbox'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Exclude unselected checkboxes'), + '#return_value' => TRUE, + '#default_value' => $settings['submission_exclude_empty_checkbox'], ]; - $form['submission_access_denied']['token_tree_link'] = $this->tokenManager->buildTreeLink(); // Submission behaviors. $form['submission_behaviors'] = [ @@ -192,6 +168,19 @@ public function form(array $form, FormStateInterface $form_state) { ], '#weight' => -99, ]; + $form['submission_behaviors']['form_remote_addr'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Track user IP address'), + '#description' => $this->t("If checked, a user's IP address will be recorded."), + '#return_value' => TRUE, + '#default_value' => $settings['form_remote_addr'], + '#states' => [ + 'visible' => [ + ':input[name="form_confidential"]' => ['checked' => FALSE], + ], + ], + '#weight' => -98, + ]; $form['submission_behaviors']['form_convert_anonymous'] = [ '#type' => 'checkbox', '#title' => $this->t('Convert anonymous user drafts and submissions to authenticated user'), @@ -203,7 +192,7 @@ public function form(array $form, FormStateInterface $form_state) { ':input[name="form_confidential"]' => ['checked' => FALSE], ], ], - '#weight' => -98, + '#weight' => -97, ]; $behavior_elements = [ // Form specific behaviors. @@ -212,7 +201,7 @@ public function form(array $form, FormStateInterface $form_state) { 'form_description' => $this->t('Show the previous submissions notification that appears when users have previously submitted this form.'), ], 'token_update' => [ - 'title' => $this->t('Allow users to update a submission using a secure token.'), + 'title' => $this->t('Allow users to update a submission using a secure token'), 'form_description' => $this->t("If checked users will be able to update a submission using the webform's URL appended with the submission's (secure) token.") . ' ' . $this->t("The 'tokenized' URL to update a submission will be available when viewing a submission's information and can be inserted into an email using the [webform_submission:update-url] token.") . ' ' . $this->t('Only webforms that are open to new submissions can be updated using the secure token.'), @@ -238,23 +227,144 @@ public function form(array $form, FormStateInterface $form_state) { '#weight' => $form['submission_behaviors']['token_update']['#weight'] + 1, ]; + // User settings. + $form['submission_user_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Submission user settings'), + '#open' => TRUE, + ]; + $form['submission_user_settings']['submission_user_duplicate'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow users to duplicate previous submissions'), + '#description' => $this->t('If checked, users will be able to duplicate their previous submissions.'), + '#return_value' => TRUE, + '#default_value' => $settings['submission_user_duplicate'], + ]; + $form['submission_user_settings']['submission_columns'] = [ + '#type' => 'details', + '#title' => $this->t('Submission user columns'), + '#description' => $this->t('Below columns are displayed to users who can view previous submissions and/or pending drafts.'), + ]; + // Submission user columns. + // @see \Drupal\webform\Form\WebformResultsCustomForm::buildForm + $available_columns = $webform_submission_storage->getColumns($webform); + // Remove columns that should never be displayed to users. + $available_columns = array_diff_key($available_columns, array_flip(['uuid', 'in_draft', 'entity', 'sticky', 'locked', 'notes', 'uid'])); + $custom_columns = $webform_submission_storage->getUserColumns($webform); + // Change sid's # to an actual label. + $available_columns['sid']['title'] = $this->t('Submission ID'); + if (isset($custom_columns['sid'])) { + $custom_columns['sid']['title'] = $this->t('Submission ID'); + } + // Get available columns as option. + $columns_options = []; + foreach ($available_columns as $column_name => $column) { + $title = (strpos($column_name, 'element__') === 0) ? ['data' => ['#markup' => '<b>' . $column['title'] . '</b>']] : $column['title']; + $key = (isset($column['key'])) ? str_replace('webform_', '', $column['key']) : $column['name']; + $columns_options[$column_name] = ['title' => $title, 'key' => $key]; + } + // Get custom columns as the default value. + $columns_keys = array_keys($custom_columns); + $columns_default_value = array_combine($columns_keys, $columns_keys); + // Display columns in sortable table select element. + $form['submission_user_settings']['submission_columns']['submission_user_columns'] = [ + '#type' => 'webform_tableselect_sort', + '#header' => [ + 'title' => $this->t('Title'), + 'key' => $this->t('Key'), + ], + '#options' => $columns_options, + '#default_value' => $columns_default_value, + ]; + + // Access denied. + $form['access_denied'] = [ + '#type' => 'details', + '#title' => $this->t('Submission access denied settings'), + '#open' => TRUE, + ]; + $form['access_denied']['submission_access_denied'] = [ + '#type' => 'radios', + '#title' => $this->t('When a user is denied access to a submission'), + '#description' => $this->t('Select what happens when a user is denied access to a submission.') . + '<br/><br/>' . + $this->t('Go to <a href=":href">form settings</a> to select what happens when a user is denied access to a webform.', [':href' => Url::fromRoute('entity.webform.settings_form', ['webform' => $webform->id()])->toString()]), + '#options' => [ + WebformInterface::ACCESS_DENIED_DEFAULT => $this->t('Default (Displays the default access denied page)'), + WebformInterface::ACCESS_DENIED_PAGE => $this->t('Page (Displays message when access is denied to a submission)'), + WebformInterface::ACCESS_DENIED_LOGIN => $this->t('Login (Redirects to user login form and displays message)'), + ], + '#required' => TRUE, + '#default_value' => $settings['submission_access_denied'], + ]; + $form['access_denied']['access_denied_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="submission_access_denied"]' => ['!value' => WebformInterface::ACCESS_DENIED_DEFAULT], + ], + ], + ]; + $form['access_denied']['access_denied_container']['submission_access_denied_title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Access denied title'), + '#description' => $this->t('Page title to be shown on access denied page'), + '#default_value' => $settings['submission_access_denied_title'], + '#states' => [ + 'visible' => [ + ':input[name="submission_access_denied"]' => ['value' => WebformInterface::ACCESS_DENIED_PAGE], + ], + ], + ]; + $form['access_denied']['access_denied_container']['submission_access_denied_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Access denied message'), + '#description' => $this->t('Will be displayed either in-line or as a status message depending on the setting above.'), + '#default_value' => $settings['submission_access_denied_message'], + ]; + $form['access_denied']['access_denied_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + $form['access_denied']['access_denied_container']['access_denied_attributes'] = [ + '#type' => 'details', + '#title' => $this->t('Access denied message attributes'), + '#open' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="submission_access_denied"]' => ['value' => WebformInterface::ACCESS_DENIED_PAGE], + ], + ], + ]; + $form['access_denied']['access_denied_container']['access_denied_attributes']['submission_access_denied_attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Access denied message'), + '#default_value' => $settings['submission_access_denied_attributes'], + ]; + // Submission limits. $form['submission_limits'] = [ '#type' => 'details', - '#title' => $this->t('Submission limits'), + '#title' => $this->t('Submission limit settings'), '#open' => TRUE, ]; + // Submission limits: Total. $form['submission_limits']['total'] = [ '#type' => 'details', '#title' => $this->t('Total submissions'), ]; - $form['submission_limits']['total']['limit_total'] = [ + $form['submission_limits']['total']['total_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="limit_total_unique"]' => ['checked' => FALSE], + ], + ], + ]; + $form['submission_limits']['total']['total_container']['limit_total'] = [ '#type' => 'number', '#title' => $this->t('Total submissions limit'), '#min' => 1, '#default_value' => $settings['limit_total'], ]; - $form['submission_limits']['total']['limit_total_interval'] = [ + $form['submission_limits']['total']['total_container']['limit_total_interval'] = [ '#type' => 'select', '#options' => WebformDateHelper::getIntervalOptions(), '#title' => $this->t('Total submissions limit interval'), @@ -263,13 +373,13 @@ public function form(array $form, FormStateInterface $form_state) { 'visible' => [':input[name="limit_total"]' => ['!value' => '']], ], ]; - $form['submission_limits']['total']['entity_limit_total'] = [ + $form['submission_limits']['total']['total_container']['entity_limit_total'] = [ '#type' => 'number', '#title' => $this->t('Total submissions limit per source entity'), '#min' => 1, '#default_value' => $settings['entity_limit_total'], ]; - $form['submission_limits']['total']['entity_limit_total_interval'] = [ + $form['submission_limits']['total']['total_container']['entity_limit_total_interval'] = [ '#type' => 'select', '#options' => WebformDateHelper::getIntervalOptions(), '#title' => $this->t('Total submissions limit interval per source entity'), @@ -278,7 +388,7 @@ public function form(array $form, FormStateInterface $form_state) { 'visible' => [':input[name="entity_limit_total"]' => ['!value' => '']], ], ]; - $form['submission_limits']['total']['limit_total_message'] = [ + $form['submission_limits']['total']['total_container']['limit_total_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Total submissions limit message'), '#min' => 1, @@ -291,18 +401,74 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; + $form['submission_limits']['total']['total_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + if ($form['submission_limits']['total']['total_container']['token_tree_link']) { + $form['submission_limits']['total']['total_container']['token_tree_link'] += [ + '#states' => [ + 'visible' => [ + [':input[name="limit_total"]' => ['!value' => '']], + 'or', + [':input[name="entity_limit_total"]' => ['!value' => '']], + ], + ], + ]; + } + $form['submission_limits']['total']['limit_total_unique'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Limit total to one submission per webform/source entity'), + '#default_value' => $settings['limit_total_unique'], + ]; + $form['submission_limits']['total']['limit_total_unique_info'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('Only submission administrators will only be able to create and update the unique submission.') . '<br/><br/>' . + $this->t('Webform blocks can be used to place this webform on the desired source entity types.'), + '#message_type' => 'info', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + ':input[name="limit_total_unique"]' => ['checked' => TRUE], + ], + ], + ]; + $form['submission_limits']['total']['limit_total_unique_warning'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t("Please make sure users are allowed to 'edit any submission'."), + '#message_type' => 'warning', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + ':input[name="limit_total_unique"]' => ['checked' => TRUE], + ], + ], + ]; + // Submission limits: User. $form['submission_limits']['user'] = [ '#type' => 'details', '#title' => $this->t('Per user'), '#description' => $this->t('Limit the number of submissions per user. A user is identified by their user id if logged-in, or by their Cookie if anonymous.'), + '#states' => [ + 'visible' => [ + ':input[name="limit_total_unique"]' => ['checked' => FALSE], + ], + ], + ]; + $form['submission_limits']['user']['user_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="limit_user_unique"]' => ['checked' => FALSE], + ], + ], ]; - $form['submission_limits']['user']['limit_user'] = [ + $form['submission_limits']['user']['user_container']['limit_user'] = [ '#type' => 'number', '#title' => $this->t('Per user submission limit'), '#min' => 1, '#default_value' => $settings['limit_user'], ]; - $form['submission_limits']['user']['limit_user_interval'] = [ + $form['submission_limits']['user']['user_container']['limit_user_interval'] = [ '#type' => 'select', '#options' => WebformDateHelper::getIntervalOptions(), '#title' => $this->t('Per user submission limit interval'), @@ -311,13 +477,13 @@ public function form(array $form, FormStateInterface $form_state) { 'visible' => [':input[name="limit_user"]' => ['!value' => '']], ], ]; - $form['submission_limits']['user']['entity_limit_user'] = [ + $form['submission_limits']['user']['user_container']['entity_limit_user'] = [ '#type' => 'number', '#min' => 1, '#title' => $this->t('Per user submission limit per source entity'), '#default_value' => $settings['entity_limit_user'], ]; - $form['submission_limits']['user']['entity_limit_user_interval'] = [ + $form['submission_limits']['user']['user_container']['entity_limit_user_interval'] = [ '#type' => 'select', '#options' => WebformDateHelper::getIntervalOptions(), '#title' => $this->t('Per user submission limit interval per source entity'), @@ -326,7 +492,7 @@ public function form(array $form, FormStateInterface $form_state) { 'visible' => [':input[name="entity_limit_user"]' => ['!value' => '']], ], ]; - $form['submission_limits']['user']['limit_user_message'] = [ + $form['submission_limits']['user']['user_container']['limit_user_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Per user submission limit message'), '#default_value' => $settings['limit_user_message'], @@ -338,11 +504,52 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; + $form['submission_limits']['user']['user_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + if ($form['submission_limits']['user']['user_container']['token_tree_link']) { + $form['submission_limits']['user']['user_container']['token_tree_link'] += [ + '#states' => [ + 'visible' => [ + [':input[name="limit_user"]' => ['!value' => '']], + 'or', + [':input[name="entity_limit_user"]' => ['!value' => '']], + ], + ], + ]; + } + $form['submission_limits']['user']['limit_user_unique'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Limit users to one submission per webform/source entity'), + '#default_value' => $settings['limit_user_unique'], + ]; + $form['submission_limits']['user']['limit_user_unique_info'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('Only authenticated users will be able to create and update their unique submission.'), + '#message_type' => 'info', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + ':input[name="limit_user_unique"]' => ['checked' => TRUE], + ], + ], + ]; + $form['submission_limits']['user']['limit_user_unique_warning'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t("Please make sure users are allowed to 'edit own submission'."), + '#message_type' => 'warning', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + ':input[name="limit_user_unique"]' => ['checked' => TRUE], + ], + ], + ]; // Purge settings. $form['purge_settings'] = [ '#type' => 'details', - '#title' => $this->t('Submission purging'), + '#title' => $this->t('Submission purge settings'), '#open' => TRUE, ]; $form['purge_settings']['purge'] = [ @@ -371,7 +578,7 @@ public function form(array $form, FormStateInterface $form_state) { // Draft settings. $form['draft_settings'] = [ '#type' => 'details', - '#title' => $this->t('Draft settings'), + '#title' => $this->t('Submission draft settings'), '#open' => TRUE, ]; $form['draft_settings']['draft'] = [ @@ -432,12 +639,12 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('Message to be displayed when a draft is loaded.'), '#default_value' => $settings['draft_loaded_message'], ]; - $form['draft_settings']['draft_container']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['draft_settings']['draft_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Autofill settings. $form['autofill_settings'] = [ '#type' => 'details', - '#title' => $this->t('Autofill settings'), + '#title' => $this->t('Submission autofill settings'), '#open' => TRUE, ]; $form['autofill_settings']['autofill'] = [ @@ -470,6 +677,53 @@ public function form(array $form, FormStateInterface $form_state) { '#webform_id' => $this->getEntity()->id(), '#default_value' => $settings['autofill_excluded_elements'], ]; + $form['autofill_settings']['autofill_container']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + + // Submission views. + $form['views_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Submission views settings'), + '#open' => TRUE, + ]; + if (!$this->moduleHandler->moduleExists('webform_views')) { + $form['views_settings']['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_message' => $this->t('To expose your webform elements to your webform submission views. Please install the <a href=":href">Webform Views Integration</a> module.', [':href' => 'https://www.drupal.org/project/webform_views']), + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } + if ($this->moduleHandler->moduleExists('views_ui') + && $this->currentUser()->hasPermission('administer views') + && ($view = View::load('webform_submissions')) + && ($view->access('duplicate'))) { + + $form['views_settings']['submission_views_create'] = [ + '#type' => 'link', + '#title' => $this->t('Create new submission view'), + '#url' => Url::fromRoute( + 'entity.view.duplicate_form', + ['view' => 'webform_submissions'] + ), + '#attributes' => [ + 'target' => '_blank', + 'class' => ['button', 'button-action', 'button--small'], + ], + '#prefix' => '<p>', + '#suffix' => '</p>', + ]; + } + $form['views_settings']['submission_views'] = [ + '#type' => 'webform_submission_views', + '#title' => $this->t('Submission views'), + '#title_display' => 'invisible', + '#default_value' => $settings['submission_views'], + ]; + $form['views_settings']['submission_views_replace'] = [ + '#type' => 'webform_submission_views_replace', + '#default_value' => $settings['submission_views_replace'], + ]; $this->tokenManager->elementValidate($form); @@ -501,11 +755,34 @@ public function save(array $form, FormStateInterface $form_state) { $next_serial = (int) $values['next_serial']; $max_serial = $webform_storage->getMaxSerial($webform); if ($next_serial < $max_serial) { - drupal_set_message($this->t('The next submission number was increased to @min to make it higher than existing submissions.', ['@min' => $max_serial])); + $this->messenger()->addStatus($this->t('The next submission number was increased to @min to make it higher than existing submissions.', ['@min' => $max_serial])); $next_serial = $max_serial; } $webform_storage->setNextSerial($webform, $next_serial); + // Limit total unique. + if (!empty($values['limit_total_unique'])) { + $values['limit_total'] = NULL; + $values['limit_total_interval'] = NULL; + $values['limit_total_message'] = ''; + $values['entity_limit_total'] = NULL; + $values['entity_limit_total_interval'] = NULL; + $values['limit_user'] = NULL; + $values['limit_user_interval'] = NULL; + $values['limit_user_message'] = ''; + $values['entity_limit_user'] = NULL; + $values['entity_limit_user_interval'] = NULL; + } + + // Limit user unique. + if (!empty($values['limit_user_unique'])) { + $values['limit_user'] = NULL; + $values['limit_user_interval'] = NULL; + $values['limit_user_message'] = ''; + $values['entity_limit_user'] = NULL; + $values['entity_limit_user_interval'] = NULL; + } + // Remove main properties. unset( $values['next_serial'] diff --git a/web/modules/webform/src/EventSubscriber/WebformExceptionHtmlSubscriber.php b/web/modules/webform/src/EventSubscriber/WebformExceptionHtmlSubscriber.php new file mode 100644 index 0000000000..a65cdea5cc --- /dev/null +++ b/web/modules/webform/src/EventSubscriber/WebformExceptionHtmlSubscriber.php @@ -0,0 +1,325 @@ +<?php + +namespace Drupal\webform\EventSubscriber; + +use Drupal\Core\Cache\CacheableResponseInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Routing\RedirectDestinationInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Url; +use Drupal\webform\Element\WebformHtmlEditor; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformTokenManagerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Event subscriber to redirect to login form when webform settings instruct to. + */ +class WebformExceptionHtmlSubscriber extends DefaultExceptionHtmlSubscriber { + + use StringTranslationTrait; + + /** + * The current account. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $account; + + /** + * The configuration object factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The renderer. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * The webform token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * Constructs a new WebformSubscriber. + * + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel + * The HTTP kernel. + * @param \Psr\Log\LoggerInterface $logger + * The logger service. + * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination + * The redirect destination service. + * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router + * A router implementation which does not check access. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration object factory. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. + */ + public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router, AccountInterface $account, ConfigFactoryInterface $config_factory, RendererInterface $renderer, MessengerInterface $messenger, WebformTokenManagerInterface $token_manager) { + parent::__construct($http_kernel, $logger, $redirect_destination, $access_unaware_router); + + $this->account = $account; + $this->configFactory = $config_factory; + $this->renderer = $renderer; + $this->messenger = $messenger; + $this->tokenManager = $token_manager; + } + + /** + * {@inheritdoc} + */ + protected static function getPriority() { + // Execute before CustomPageExceptionHtmlSubscriber which is -50. + // @see \Drupal\Core\EventSubscriber\CustomPageExceptionHtmlSubscriber::getPriority + return -49; + } + + /** + * Handles a 403 error for HTML. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on403(GetResponseForExceptionEvent $event) { + if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) { + return; + } + + $this->on403RedirectEntityAccess($event); + $this->on403RedirectPrivateFileAccess($event); + } + + /** + * Redirect to user login when access is denied to private webform file. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + * + * @see webform_file_download() + * @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::accessFileDownload + */ + public function on403RedirectPrivateFileAccess(GetResponseForExceptionEvent $event) { + $path = $event->getRequest()->getPathInfo(); + // Make sure the user is trying to access a private webform file upload. + if (strpos($path, '/system/files/webform/') !== 0) { + return; + } + + // Make private webform file upload is not a temporary file. + // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::postSave + if (strpos($path, '/_sid_/') !== FALSE) { + return; + } + + // Check that private file redirection is enabled. + if (!$this->configFactory->get('webform.settings')->get('file.file_private_redirect')) { + return; + } + + $message = $this->configFactory->get('webform.settings')->get('file.file_private_redirect_message'); + $this->redirectToLogin($event, $message); + } + + /** + * Redirect to user login when access is denied for webform or submission. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on403RedirectEntityAccess(GetResponseForExceptionEvent $event) { + $url = Url::fromUserInput($event->getRequest()->getPathInfo()); + if (!$url) { + return; + } + + $route_parameters = $url->isRouted() ? $url->getRouteParameters() : []; + if (empty($route_parameters['webform']) && empty($route_parameters['webform_submission'])) { + return; + } + + $config = $this->configFactory->get('webform.settings'); + + // If webform submission, handle login redirect. + if (!empty($route_parameters['webform_submission'])) { + $webform_submission = WebformSubmission::load($route_parameters['webform_submission']); + $webform = $webform_submission->getWebform(); + + $submission_access_denied_message = $webform->getSetting('submission_access_denied_message') + ?: $config->get('settings.default_submission_access_denied_message'); + + switch ($webform->getSetting('submission_access_denied')) { + case WebformInterface::ACCESS_DENIED_LOGIN: + $this->redirectToLogin($event, $submission_access_denied_message, $webform_submission); + break; + + case WebformInterface::ACCESS_DENIED_PAGE: + // Must manually build access denied path so that base path is not + // included. + $this->makeSubrequest($event, '/admin/structure/webform/manage/' . $webform->id() . '/submission/' . $webform_submission->id() . '/access-denied', Response::HTTP_FORBIDDEN); + break; + + case WebformInterface::ACCESS_DENIED_DEFAULT: + default: + // Make the default 403 request so that we can add cacheable dependencies. + $this->makeSubrequest($event, $this->getSystemSite403Path(), Response::HTTP_FORBIDDEN); + break; + } + + // Add cacheable dependencies. + $response = $event->getResponse(); + if ($response instanceof CacheableResponseInterface) { + $response->addCacheableDependency($webform); + $response->addCacheableDependency($webform_submission); + $response->addCacheableDependency($config); + } + return; + } + + // If webform, handle access denied redirect or page. + if (!empty($route_parameters['webform'])) { + $webform = Webform::load($route_parameters['webform']); + + $webform_access_denied_message = $webform->getSetting('form_access_denied_message') + ?: $config->get('settings.default_form_access_denied_message'); + + switch ($webform->getSetting('form_access_denied')) { + case WebformInterface::ACCESS_DENIED_LOGIN: + $this->redirectToLogin($event, $webform_access_denied_message, $webform); + break; + + case WebformInterface::ACCESS_DENIED_PAGE: + // Must manually build access denied path so that base path is not + // included. + $this->makeSubrequest($event, '/webform/' . $webform->id() . '/access-denied', Response::HTTP_FORBIDDEN); + break; + + case WebformInterface::ACCESS_DENIED_MESSAGE: + // Display message. + $this->setMessage($webform_access_denied_message, $webform); + // Make the default 403 request so that we can add cacheable dependencies. + $this->makeSubrequest($event, $this->getSystemSite403Path(), Response::HTTP_FORBIDDEN); + break; + + case WebformInterface::ACCESS_DENIED_DEFAULT: + default: + // Make the default 403 request so that we can add cacheable dependencies. + $this->makeSubrequest($event, $this->getSystemSite403Path(), Response::HTTP_FORBIDDEN); + break; + } + // Add cacheable dependencies. + $response = $event->getResponse(); + if ($response instanceof CacheableResponseInterface) { + $response->addCacheableDependency($webform); + $response->addCacheableDependency($config); + } + return; + } + } + + /** + * {@inheritdoc} + */ + protected function getHandledFormats() { + return ['html']; + } + + /** + * {@inheritdoc} + */ + public function onException(GetResponseForExceptionEvent $event) { + // Only handle 403 exception. + // @see \Drupal\webform\EventSubscriber\WebformExceptionHtmlSubscriber::on403 + $exception = $event->getException(); + if ($exception instanceof HttpExceptionInterface && $exception->getStatusCode() === 403) { + parent::onException($event); + } + } + + /** + * Get 403 path from system.site config. + * + * @return string + * The custom 403 path or Drupal's default 403 path. + */ + protected function getSystemSite403Path() { + return $this->configFactory->get('system.site')->get('page.403') ?: '/system/403'; + } + + /** + * Redirect to user login with destination and display custom message. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + * @param null|string $message + * (Optional) Message to be display on user login. + * @param null|\Drupal\Core\Entity\EntityInterface $entity + * (Optional) Entity to be used when replacing tokens. + */ + protected function redirectToLogin(GetResponseForExceptionEvent $event, $message = NULL, EntityInterface $entity = NULL) { + // Display message. + if ($message) { + $this->setMessage($message, $entity); + } + + // Only redirect anonymous users. + if ($this->account->isAuthenticated()) { + return; + } + + $redirect_url = Url::fromRoute( + 'user.login', + [], + ['absolute' => TRUE, 'query' => $this->redirectDestination->getAsArray()] + ); + $event->setResponse(new RedirectResponse($redirect_url->toString())); + } + + /** + * Display custom message. + * + * @param null|string $message + * (Optional) Message to be display on user login. + * @param null|\Drupal\Core\Entity\EntityInterface $entity + * (Optional) Entity to be used when replacing tokens. + */ + protected function setMessage($message, EntityInterface $entity = NULL) { + $message = $this->tokenManager->replace($message, $entity); + $build = WebformHtmlEditor::checkMarkup($message); + $this->messenger->addStatus($this->renderer->renderPlain($build)); + } + +} diff --git a/web/modules/webform/src/EventSubscriber/WebformSubscriber.php b/web/modules/webform/src/EventSubscriber/WebformSubscriber.php deleted file mode 100644 index 979a068f02..0000000000 --- a/web/modules/webform/src/EventSubscriber/WebformSubscriber.php +++ /dev/null @@ -1,220 +0,0 @@ -<?php - -namespace Drupal\webform\EventSubscriber; - -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Routing\RedirectDestinationInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Render\RendererInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\Core\Url; -use Drupal\webform\Element\WebformHtmlEditor; -use Drupal\webform\Entity\Webform; -use Drupal\webform\Entity\WebformSubmission; -use Drupal\webform\WebformTokenManagerInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpFoundation\Response; - -/** - * Event subscriber to redirect to login form when webform settings instruct to. - */ -class WebformSubscriber implements EventSubscriberInterface { - - use StringTranslationTrait; - - /** - * The current account. - * - * @var \Drupal\Core\Session\AccountInterface - */ - protected $account; - - /** - * The configuration object factory. - * - * @var \Drupal\Core\Config\ConfigFactoryInterface - */ - protected $configFactory; - - /** - * The renderer. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; - - /** - * The redirect.destination service. - * - * @var \Drupal\Core\Routing\RedirectDestinationInterface - */ - protected $redirectDestination; - - /** - * The webform token manager. - * - * @var \Drupal\webform\WebformTokenManagerInterface - */ - protected $tokenManager; - - /** - * Constructs a new WebformSubscriber. - * - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The configuration object factory. - * @param \Drupal\Core\Render\RendererInterface $renderer - * The renderer. - * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination - * The redirect.destination service. - * @param \Drupal\webform\WebformTokenManagerInterface $token_manager - * The webform token manager. - */ - public function __construct(AccountInterface $account, ConfigFactoryInterface $config_factory, RendererInterface $renderer, RedirectDestinationInterface $redirect_destination, WebformTokenManagerInterface $token_manager) { - $this->account = $account; - $this->configFactory = $config_factory; - $this->renderer = $renderer; - $this->redirectDestination = $redirect_destination; - - $this->tokenManager = $token_manager; - } - - /** - * Redirect to user login when access is denied to private webform file. - * - * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event - * The event to process. - * - * @see webform_file_download() - * @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::accessFileDownload - */ - public function onRespondRedirectPrivateFileAccess(FilterResponseEvent $event) { - if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) { - return; - } - - // Check for 403 access denied status code in (master) response. - $response = $event->getResponse(); - if ($response->getStatusCode() != Response::HTTP_FORBIDDEN) { - return; - } - - $path = $event->getRequest()->getPathInfo(); - // Make sure the user is trying to access a private webform file upload. - if (strpos($path, '/system/files/webform/') !== 0) { - return; - } - - // Make private webform file upload is not a temporary file. - // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::postSave - if (strpos($path, '/_sid_/') !== FALSE) { - return; - } - - // Only redirect anonymous users. - if ($this->account->isAuthenticated()) { - return; - } - - // Check that private file redirection is enabled. - if (!$this->configFactory->get('webform.settings')->get('file.file_private_redirect')) { - return; - } - - $message = $this->configFactory->get('webform.settings')->get('file.file_private_redirect_message'); - $this->redirectToLogin($event, $message); - } - - /** - * Redirect to user login when access is denied for webform or submission. - * - * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event - * The event to process. - */ - public function onRespondRedirectEntityAccess(FilterResponseEvent $event) { - if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) { - return; - } - - // Check for 403 access denied status code in (master) response. - $response = $event->getResponse(); - if ($response->getStatusCode() != Response::HTTP_FORBIDDEN) { - return; - } - - $url = Url::fromUserInput($event->getRequest()->getPathInfo()); - if (!$url) { - return; - } - - $route_parameters = $url->isRouted() ? $url->getRouteParameters() : []; - if (empty($route_parameters['webform']) && empty($route_parameters['webform_submission'])) { - return; - } - - // If webform submission, handle login redirect. - if (!empty($route_parameters['webform_submission'])) { - $webform_submission = WebformSubmission::load($route_parameters['webform_submission']); - if ($webform_submission->getWebform()->getSetting('submission_login')) { - $message = $webform_submission->getWebform()->getSetting('submission_login_message') - ?: $this->configFactory->get('webform.settings')->get('settings.default_submission_login_message'); - $this->redirectToLogin($event, $message, $webform_submission); - }; - return; - } - - // If webform, handle login redirect. - if (!empty($route_parameters['webform'])) { - $webform = Webform::load($route_parameters['webform']); - if ($webform->getSetting('form_login')) { - $message = $webform->getSetting('form_login_message') - ?: $this->configFactory->get('webform.settings')->get('settings.default_form_login_message'); - $this->redirectToLogin($event, $message, $webform); - }; - return; - } - } - - /** - * Redirect to user login with destination and display custom message. - * - * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event - * The event to process. - * @param null|string $message - * (Optional) Message to be display on user login. - * @param null|\Drupal\Core\Entity\EntityInterface $entity - * (Optional) Entity to be used when replacing tokens. - */ - protected function redirectToLogin(FilterResponseEvent $event, $message = NULL, EntityInterface $entity = NULL) { - // Display message. - if ($message) { - $message = $this->tokenManager->replace($message, $entity); - $build = WebformHtmlEditor::checkMarkup($message); - drupal_set_message($this->renderer->renderPlain($build)); - } - - $redirect_url = Url::fromRoute( - 'user.login', - [], - ['absolute' => TRUE, 'query' => $this->redirectDestination->getAsArray()] - ); - $event->setResponse(new RedirectResponse($redirect_url->toString())); - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events = []; - $events[KernelEvents::RESPONSE][] = ['onRespondRedirectPrivateFileAccess']; - $events[KernelEvents::RESPONSE][] = ['onRespondRedirectEntityAccess']; - return $events; - } - -} diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigAdvancedForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigAdvancedForm.php index ad04f0e6e7..03b22da034 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigAdvancedForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigAdvancedForm.php @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteBuilderInterface; use Drupal\Core\Url; +use Drupal\webform\Commands\WebformCliService; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -36,6 +37,13 @@ class WebformAdminConfigAdvancedForm extends WebformAdminConfigBaseForm { */ protected $routerBuilder; + /** + * The (drush) command-line service. + * + * @var \Drupal\webform\Commands\WebformCliService + */ + protected $cliService; + /** * {@inheritdoc} */ @@ -54,12 +62,15 @@ public function getFormId() { * The render cache service. * @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder * The router builder service. + * @param \Drupal\webform\Commands\WebformCliService $cli_service + * The (drush) command-line service. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $render_cache, RouteBuilderInterface $router_builder) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $render_cache, RouteBuilderInterface $router_builder, WebformCliService $cli_service) { parent::__construct($config_factory); $this->renderCache = $render_cache; $this->moduleHandler = $module_handler; $this->routerBuilder = $router_builder; + $this->cliService = $cli_service; } /** @@ -70,7 +81,8 @@ public static function create(ContainerInterface $container) { $container->get('config.factory'), $container->get('module_handler'), $container->get('cache.render'), - $container->get('router.builder') + $container->get('router.builder'), + $container->get('webform.cli_service') ); } @@ -115,6 +127,13 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#return_value' => TRUE, '#default_value' => $config->get('ui.details_save'), ]; + $form['ui']['help_disabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable help'), + '#description' => $this->t('If checked, help text will be removed from every webform page and form.'), + '#return_value' => TRUE, + '#default_value' => $config->get('ui.help_disabled'), + ]; $form['ui']['dialog_disabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Disable dialogs'), @@ -128,7 +147,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('If checked, all off-canvas system trays will be disabled.'), '#return_value' => TRUE, '#default_value' => $config->get('ui.offcanvas_disabled'), - '#access' => (floatval(\Drupal::VERSION) >= 8.5), '#states' => [ 'visible' => [ ':input[name="ui[dialog_disabled]"]' => [ @@ -157,7 +175,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Requirements. $form['requirements'] = [ '#type' => 'details', - '#title' => $this->t('Requirements'), + '#title' => $this->t('Requirement settings'), '#description' => $this->t('The below requirements are checked by the <a href=":href">Status report</a>.', [':href' => Url::fromRoute('system.status')->toString()]), '#open' => TRUE, '#tree' => TRUE, @@ -219,6 +237,15 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#min' => 1, '#required' => TRUE, '#default_value' => $config->get('batch.default_batch_export_size'), + '#description' => $this->t('Batch export size is used when submissions are being exported/downloaded.'), + ]; + $form['batch']['default_batch_import_size'] = [ + '#type' => 'number', + '#title' => $this->t('Batch import size'), + '#min' => 1, + '#required' => TRUE, + '#default_value' => $config->get('batch.default_batch_import_size'), + '#description' => $this->t('Batch import size is used when submissions are being imported/uploaded.'), ]; $form['batch']['default_batch_update_size'] = [ '#type' => 'number', @@ -226,10 +253,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#min' => 1, '#required' => TRUE, '#default_value' => $config->get('batch.default_batch_update_size'), + '#description' => $this->t('Batch update size is used when submissions are being bulk updated.'), ]; $form['batch']['default_batch_delete_size'] = [ '#type' => 'number', '#title' => $this->t('Batch delete size'), + '#description' => $this->t('Batch delete size is used when submissions are being cleared.'), '#min' => 1, '#required' => TRUE, '#default_value' => $config->get('batch.default_batch_delete_size'), @@ -243,6 +272,25 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $config->get('batch.default_batch_email_size'), ]; + // Repair. + $form['repair'] = [ + '#type' => 'details', + '#title' => $this->t('Repair webform configuration'), + '#open' => TRUE, + '#description' => '<p>' . $this->t('If older Webform configuration files are imported after the Webform module has been updated this may cause the older configuration to be out-of-sync and result in unexpected behaviors and errors.') . '</p>' . + '<p>' . $this->t("Running the below 'Repair' command will apply all missing settings to older Webform configuration files.") . '</p>', + '#help' => FALSE, + '#weight' => 100, + ]; + $form['repair']['action'] = ['#type' => 'actions']; + $form['repair']['action']['repair_configuration'] = [ + '#type' => 'submit', + '#value' => $this->t('Repair configuration'), + '#attributes' => [ + 'onclick' => 'return confirm("' . $this->t('Are you sure you want to repair webform configuration?') . '\n' . $this->t('This cannot be undone!!!') . '");', + ], + ]; + return parent::buildForm($form, $form_state); } @@ -250,19 +298,62 @@ public function buildForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $config = $this->config('webform.settings'); - $config->set('ui', $form_state->getValue('ui')); - $config->set('requirements', $form_state->getValue('requirements')); - $config->set('test', $form_state->getValue('test')); - $config->set('batch', $form_state->getValue('batch')); - $config->save(); + $op = (string) $form_state->getValue('op'); + if ($op === (string) $this->t('Repair configuration')) { + // Copied from: + // @see \Drupal\webform\Commands\WebformCliService::drush_webform_repair + module_load_include('install', 'webform'); + + $this->messenger()->addMessage($this->t('Repairing webform submission storage schema…')); + _webform_update_webform_submission_storage_schema(); + + $this->messenger()->addMessage($this->t('Repairing admin settings…')); + _webform_update_admin_settings(TRUE); + + $this->messenger()->addMessage($this->t('Repairing webform settings…')); + _webform_update_webform_settings(); + + $this->messenger()->addMessage($this->t('Repairing webform handlers…')); + _webform_update_webform_handler_settings(); + + $this->messenger()->addMessage($this->t('Repairing webform field storage definitions…')); + _webform_update_field_storage_definitions(); + + $this->messenger()->addMessage($this->t('Repairing webform submission storage schema…')); + _webform_update_webform_submission_storage_schema(); + + drupal_flush_all_caches(); + + $this->messenger()->addStatus($this->t('Webform configuration has been repaired.')); + } + else { + // Update config and submit form. + $config = $this->config('webform.settings'); + $config->set('ui', $form_state->getValue('ui')); + $config->set('requirements', $form_state->getValue('requirements')); + $config->set('test', $form_state->getValue('test')); + $config->set('batch', $form_state->getValue('batch')); + + // Track if help is disabled. + // @todo Figure out how to clear cached help block. + $is_help_disabled = ($config->getOriginal('ui.help_disabled') != $config->get('ui.help_disabled')); - // Clear render cache so that local tasks can be updated. - // @see webform_local_tasks_alter() - $this->renderCache->deleteAll(); - $this->routerBuilder->rebuild(); + parent::submitForm($form, $form_state); - parent::submitForm($form, $form_state); + // Clear cached data. + if ($is_help_disabled) { + // Flush cache when help is being enabled. + // @see webform_help() + drupal_flush_all_caches(); + } + else { + // Clear render cache so that local tasks can be updated to hide/show + // the 'Contribute' tab. + // @see webform_local_tasks_alter() + $this->renderCache->deleteAll(); + $this->routerBuilder->rebuild(); + } + } } } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigBaseForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigBaseForm.php index 71ce6fc8c4..d4a72f2718 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigBaseForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigBaseForm.php @@ -3,7 +3,12 @@ namespace Drupal\webform\Form\AdminConfig; use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Core\Config\Config; use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Plugin\WebformElement\TableSelect; +use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\Plugin\WebformHandlerManager; /** * Base webform admin settings form. @@ -17,8 +22,19 @@ protected function getEditableConfigNames() { return ['webform.settings']; } + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('webform.settings'); + _webform_config_update($config); + $config->save(); + + parent::submitForm($form, $form_state); + } + /****************************************************************************/ - // Exclude plugins + // Exclude plugins. /****************************************************************************/ /** @@ -33,9 +49,6 @@ protected function getEditableConfigNames() { * A table select element used to excluded plugins by id. */ protected function buildExcludedPlugins(PluginManagerInterface $plugin_manager, array $excluded_ids) { - $plugins = $plugin_manager->getDefinitions(); - $plugins = $plugin_manager->getSortedDefinitions($plugins); - $header = [ 'title' => ['data' => $this->t('Title')], 'id' => ['data' => $this->t('Name'), 'class' => [RESPONSIVE_PRIORITY_LOW]], @@ -44,22 +57,39 @@ protected function buildExcludedPlugins(PluginManagerInterface $plugin_manager, $ids = []; $options = []; + $plugins = $this->getPluginDefinitions($plugin_manager); foreach ($plugins as $id => $plugin_definition) { $ids[$id] = $id; + + $description = [ + 'data' => [ + 'content' => ['#markup' => $plugin_definition['description']], + ], + ]; + if (!empty($plugin_definition['deprecated'])) { + $description['data']['deprecated'] = [ + '#type' => 'webform_message', + '#message_message' => $plugin_definition['deprecated_message'], + '#message_type' => 'warning', + ]; + } $options[$id] = [ 'title' => $plugin_definition['label'], 'id' => $plugin_definition['id'], - 'description' => $plugin_definition['description'], + 'description' => $description, ]; } - return [ + $element = [ '#type' => 'tableselect', '#header' => $header, '#options' => $options, '#required' => TRUE, + '#sticky' => TRUE, '#default_value' => array_diff($ids, $excluded_ids), ]; + TableSelect::setProcessTableSelectCallback($element); + return $element; } /** @@ -76,10 +106,8 @@ protected function buildExcludedPlugins(PluginManagerInterface $plugin_manager, * @see \Drupal\webform\Form\WebformAdminSettingsForm::buildExcludedPlugins */ protected function convertIncludedToExcludedPluginIds(PluginManagerInterface $plugin_manager, array $included_ids) { - $plugins = $plugin_manager->getDefinitions(); - $plugins = $plugin_manager->getSortedDefinitions($plugins); - $ids = []; + $plugins = $this->getPluginDefinitions($plugin_manager); foreach ($plugins as $id => $plugin) { $ids[$id] = $id; } @@ -89,4 +117,25 @@ protected function convertIncludedToExcludedPluginIds(PluginManagerInterface $pl return $excluded_ids; } + /** + * Get plugin definitions. + * + * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager + * A webform element, handler, or exporter plugin manager. + * + * @return array + * Plugin definitions. + */ + protected function getPluginDefinitions(PluginManagerInterface $plugin_manager) { + $plugins = $plugin_manager->getDefinitions(); + $plugins = $plugin_manager->getSortedDefinitions($plugins); + if ($plugin_manager instanceof WebformElementManagerInterface) { + unset($plugins['webform_element']); + } + elseif ($plugin_manager instanceof WebformHandlerManager) { + unset($plugins['broken']); + } + return $plugins; + } + } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigElementsForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigElementsForm.php index 2aed78a389..aa7e3735ea 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigElementsForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigElementsForm.php @@ -2,16 +2,18 @@ namespace Drupal\webform\Form\AdminConfig; +use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; use Drupal\file\Plugin\Field\FieldType\FileItem; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\Utility\WebformArrayHelper; -use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Utility\WebformOptionsHelper; +use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\WebformLibrariesManagerInterface; +use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -26,6 +28,13 @@ class WebformAdminConfigElementsForm extends WebformAdminConfigBaseForm { */ protected $moduleHandler; + /** + * The webform token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + /** * The webform element manager. * @@ -54,14 +63,17 @@ public function getFormId() { * The factory for configuration objects. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager * The webform element manager. * @param \Drupal\webform\WebformLibrariesManagerInterface $libraries_manager * The webform libraries manager. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, WebformElementManagerInterface $element_manager, WebformLibrariesManagerInterface $libraries_manager) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, WebformTokenManagerInterface $token_manager, WebformElementManagerInterface $element_manager, WebformLibrariesManagerInterface $libraries_manager) { parent::__construct($config_factory); $this->moduleHandler = $module_handler; + $this->tokenManager = $token_manager; $this->elementManager = $element_manager; $this->librariesManager = $libraries_manager; } @@ -73,6 +85,7 @@ public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), $container->get('module_handler'), + $container->get('webform.token_manager'), $container->get('plugin.manager.webform.element'), $container->get('webform.libraries_manager') ); @@ -87,7 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Element: Settings. $form['element'] = [ '#type' => 'details', - '#title' => $this->t('Element settings'), + '#title' => $this->t('Element general settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -101,7 +114,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#type' => 'webform_radios_other', '#title' => $this->t('Allowed tags'), '#options' => [ - 'admin' => $this->t('Admin tags Excludes: script, iframe, etc...'), + 'admin' => $this->t('Admin tags Excludes: script, iframe, etc…'), 'html' => $this->t('HTML tags: Includes only @html_tags.', ['@html_tags' => WebformArrayHelper::toString(Xss::getHtmlTagList())]), ], '#other__option_label' => $this->t('Custom tags'), @@ -150,7 +163,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['element']['default_more_title'] = [ '#type' => 'textfield', '#title' => $this->t('Default more label'), - '#description' => $this->t('The (read) more label used hide/show more information about an element.'), + '#description' => $this->t('The (read) more label used to hide/show more information about an element.'), '#required' => 'required', '#default_value' => $config->get('element.default_more_title'), ]; @@ -252,36 +265,83 @@ public function buildForm(array $form, FormStateInterface $form_state) { $format_options[$filter->id()] = $filter->label(); } } - $form['html_editor']['format'] = [ + $form['html_editor']['format_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="html_editor[disabled]"]' => ['checked' => FALSE], + ], + ], + ]; + $form['html_editor']['format_container']['element_format'] = [ '#type' => 'select', - '#title' => $this->t('Text format'), + '#title' => $this->t('Element text format'), '#description' => $this->t('Leave blank to use the custom and recommended Webform specific HTML editor.'), '#empty_option' => $this->t('- None -'), '#options' => $format_options, - '#default_value' => $config->get('html_editor.format'), + '#default_value' => $config->get('html_editor.element_format'), + '#parents' => ['html_editor', 'element_format'], + ]; + $form['html_editor']['format_container']['mail_format'] = [ + '#type' => 'select', + '#title' => $this->t('Mail text format'), + '#description' => $this->t('Leave blank to use the custom and recommended Webform specific HTML editor.'), + '#empty_option' => $this->t('- None -'), + '#options' => $format_options, + '#default_value' => $config->get('html_editor.mail_format'), + '#parents' => ['html_editor', 'mail_format'], '#states' => [ 'visible' => [ ':input[name="html_editor[disabled]"]' => ['checked' => FALSE], ], ], ]; - $t_args = [ - ':dialog_href' => Url::fromRoute('<current>', [], ['fragment' => 'edit-ui'])->toString(), - ':modules_href' => Url::fromRoute('system.modules_list', [], ['fragment' => 'edit-modules-core-experimental'])->toString(), + $form['html_editor']['format_container']['make_unused_managed_files_temporary'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Unused html editor files should be marked temporary'), + '#description' => $this->t('Drupal core does not automatically delete unused files because unused files could reused.'), + '#return_value' => TRUE, + '#default_value' => $config->get('html_editor.make_unused_managed_files_temporary'), + '#parents' => ['html_editor', 'make_unused_managed_files_temporary'], + '#states' => [ + 'visible' => [ + [':input[name="html_editor[element_format]"]' => ['!value' => '']], + 'or', + [':input[name="html_editor[mail_format]"]' => ['!value' => '']], + ], + ], ]; - $form['html_editor']['message'] = [ + $form['html_editor']['format_container']['warning_message'] = [ '#type' => 'webform_message', - '#message_message' => $this->t('Text formats that open CKEditor image and/or link dialogs will not work properly.') . '<br />' . - $this->t('You may need to <a href=":dialog_href">disable dialogs</a> or enable the experimental <a href=":modules_href">Settings Tray</a> module.', $t_args) . '<br />' . - $this->t('For more information see: <a href="https://www.drupal.org/node/2741877">Issue #2741877: Nested modals don\'t work</a>'), + '#message_message' => $this->t('Files uploaded via the CKEditor file dialog to webform elements, settings, and configuration will not be exportable.') . '<br/>' . + '<strong>' . $this->t('All files must be uploaded to your production environment and then copied to development and local environment.') . '</strong>', '#message_type' => 'warning', '#states' => [ 'visible' => [ - ':input[name="html_editor[disabled]"]' => ['checked' => FALSE], - ':input[name="html_editor[format]"]' => ['!value' => ''], + [':input[name="html_editor[element_format]"]' => ['!value' => '']], + 'or', + [':input[name="html_editor[mail_format]"]' => ['!value' => '']], ], ], - ]; + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + if (!$this->moduleHandler->moduleExists('imce')) { + $form['html_editor']['format_container']['help_message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('It is recommended to use the <a href=":href">IMCE module</a> to manage webform elements, settings, and configuration files.', [':href' => 'https://www.drupal.org/project/imce']), + '#message_type' => 'info', + '#states' => [ + 'visible' => [ + [':input[name="html_editor[element_format]"]' => ['!value' => '']], + 'or', + [':input[name="html_editor[mail_format]"]' => ['!value' => '']], + ], + ], + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } // Element: Location. $form['location'] = [ @@ -289,6 +349,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Location settings'), '#open' => TRUE, '#tree' => TRUE, + '#access' => $this->librariesManager->isIncluded('jquery.geocomplete') || $this->librariesManager->isIncluded('algolia.places'), ]; $form['location']['default_google_maps_api_key'] = [ '#type' => 'textfield', @@ -297,6 +358,20 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $config->get('element.default_google_maps_api_key'), '#access' => $this->librariesManager->isIncluded('jquery.geocomplete'), ]; + $form['location']['default_algolia_places_app_id'] = [ + '#type' => 'textfield', + '#title' => $this->t('Algolia application id'), + '#description' => $this->t('Algolia requires users to use a valid application id and API key for more than 1,000 requests per day. By <a href="https://www.algolia.com/users/sign_up/places">signing up</a>, you can create a free Places app and access your API keys.'), + '#default_value' => $config->get('element.default_algolia_places_app_id'), + '#access' => $this->librariesManager->isIncluded('algolia.places'), + ]; + $form['location']['default_algolia_places_api_key'] = [ + '#type' => 'textfield', + '#title' => $this->t('Algolia API key'), + '#default_value' => $config->get('element.default_algolia_places_api_key'), + '#access' => $this->librariesManager->isIncluded('algolia.places'), + ]; + // Element: Select. $form['select'] = [ '#type' => 'details', @@ -342,7 +417,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['file']['delete_temporary_managed_files'] = [ '#type' => 'checkbox', - '#title' => $this->t('Immediately deleted temporary managed files'), + '#title' => $this->t('Immediately delete temporary managed files'), '#description' => $this->t('Drupal core does not immediately delete temporary file. For webform submissions it is recommended that temporary files are immediately deleted.'), '#return_value' => TRUE, '#default_value' => $config->get('file.delete_temporary_managed_files'), @@ -373,15 +448,24 @@ public function buildForm(array $form, FormStateInterface $form_state) { ], ], ]; - $form['file']['default_max_filesize'] = [ '#type' => 'textfield', - '#title' => $this->t('Default maximum upload size'), - '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', ['%limit' => function_exists('file_upload_max_size') ? format_size(file_upload_max_size()) : $this->t('N/A')]), + '#title' => $this->t('Default maximum file upload size'), + '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes.') + . '<br /><br />' + . $this->t('Current limit: %limit', ['%limit' => function_exists('file_upload_max_size') ? format_size(file_upload_max_size()) : $this->t('N/A')]), '#element_validate' => [[get_class($this), 'validateMaxFilesize']], '#size' => 10, '#default_value' => $config->get('file.default_max_filesize'), ]; + $form['file']['default_form_file_limit'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default file upload limit per form'), + '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to set file upload limit.'), + '#element_validate' => [[get_class($this), 'validateMaxFilesize']], + '#size' => 10, + '#default_value' => $config->get('settings.default_form_file_limit'), + ]; $file_types = [ 'managed_file' => 'file', 'audio_file' => 'audio file', @@ -400,6 +484,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $config->get("file.default_{$file_type_name}_extensions"), ]; } + $form['file']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Element: (Excluded) Types. $form['types'] = [ @@ -416,25 +501,19 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['types']['excluded_elements']['#header']['description']['width'] = '50%'; // Add warning to all password elements. foreach ($form['types']['excluded_elements']['#options'] as $element_type => &$excluded_element_option) { - if (strpos($element_type,'password') !== FALSE) { - $excluded_element_option['description'] = [ - 'data' => [ - 'description' => ['#markup' => $excluded_element_option['description']], - 'message' => [ - '#type' => 'webform_message', - '#message_type' => 'warning', - '#message_message' => $this->t('Webform submissions store passwords as plain text.') . ' ' . - $this->t('Any webform that includes this element should enable <a href=":href">encryption</a>.', [':href' => 'https://www.drupal.org/project/webform_encrypt']), - '#attributes' => ['class' => ['js-form-wrapper']], - '#states' => [ - 'visible' => [ - ':input[name="excluded_elements[' . $element_type . ']"]' => ['checked' => TRUE], - ], - ], + if (strpos($element_type, 'password') !== FALSE) { + $excluded_element_option['description']['data']['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Webform submissions store passwords as plain text.') . ' ' . + $this->t('Any webform that includes this element should enable <a href=":href">encryption</a>.', [':href' => 'https://www.drupal.org/project/webform_encrypt']), + '#attributes' => ['class' => ['js-form-wrapper']], + '#states' => [ + 'visible' => [ + ':input[name="excluded_elements[' . $element_type . ']"]' => ['checked' => TRUE], ], ], ]; - } } @@ -492,7 +571,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Items format'), '#title_display' => 'invisible', '#field_suffix' => [ - '#type' => 'webform_help', + '#help_title' => $element_plugin_label, '#help' => $this->t('Defaults to: %value', ['%value' => $items_default_format_label]), ], '#empty_option' => $this->t('- Default -'), @@ -516,6 +595,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'item' => ['data' => $this->t('Item format'), 'width' => '25%'], 'items' => ['data' => $this->t('Items format'), 'width' => '25%'], ], + '#sticky' => TRUE, ] + $rows; return parent::buildForm($form, $form_state); @@ -535,19 +615,30 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Excluded elements. $excluded_elements = $this->convertIncludedToExcludedPluginIds($this->elementManager, $form_state->getValue('excluded_elements')); + // Update config and submit form. $config = $this->config('webform.settings'); + $config->set('element', $form_state->getValue('element') + $form_state->getValue('checkbox') + $form_state->getValue('location') + $form_state->getValue('select') + ['excluded_elements' => $excluded_elements] ); + $config->set('html_editor', $form_state->getValue('html_editor')); - $config->set('file', $form_state->getValue('file')); + + $file = $form_state->getValue('file'); + $config->set('settings.default_form_file_limit', $file['default_form_file_limit']); + unset($file['default_form_file_limit']); + $config->set('file', $file); + $config->set('format', $format); - $config->save(); parent::submitForm($form, $form_state); + + // Reset libraries cached. + // @see webform_library_info_build() + \Drupal::service('library.discovery')->clearCachedDefinitions(); } /** @@ -563,8 +654,13 @@ public static function validateExtensions($element, FormStateInterface $form_sta * Wrapper for FileItem::validateMaxFilesize. */ public static function validateMaxFilesize($element, FormStateInterface $form_state) { - if (class_exists('\Drupal\file\Plugin\Field\FieldType\FileItem')) { - FileItem::validateMaxFilesize($element, $form_state); + // Issue #2359675: File field's Maximum upload size always passes validation. + // if (class_exists('\Drupal\file\Plugin\Field\FieldType\FileItem')) { + // FileItem::validateMaxFilesize($element, $form_state); + // } + // @see \Drupal\file\Plugin\Field\FieldType\FileItem::validateMaxFilesize + if (!empty($element['#value']) && !Bytes::toInt($element['#value'])) { + $form_state->setError($element, t('The "@name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', ['@name' => $element['#title']])); } } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigExportersForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigExportersForm.php index 7334c78b04..ac8c62679c 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigExportersForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigExportersForm.php @@ -4,6 +4,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormState; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Plugin\WebformExporterManagerInterface; use Drupal\webform\WebformSubmissionExporterInterface; @@ -14,6 +15,13 @@ */ class WebformAdminConfigExportersForm extends WebformAdminConfigBaseForm { + /** + * The file system service. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + /** * The webform exporter manager. * @@ -40,13 +48,16 @@ public function getFormId() { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\File\FileSystemInterface $file_system + * The file system service. * @param \Drupal\webform\Plugin\WebformExporterManagerInterface $exporter_manager * The webform exporter manager. * @param \Drupal\webform\WebformSubmissionExporterInterface $submission_exporter * The webform submission exporter. */ - public function __construct(ConfigFactoryInterface $config_factory, WebformExporterManagerInterface $exporter_manager, WebformSubmissionExporterInterface $submission_exporter) { + public function __construct(ConfigFactoryInterface $config_factory, FileSystemInterface $file_system, WebformExporterManagerInterface $exporter_manager, WebformSubmissionExporterInterface $submission_exporter) { parent::__construct($config_factory); + $this->fileSystem = $file_system; $this->exporterManager = $exporter_manager; $this->submissionExporter = $submission_exporter; } @@ -57,6 +68,7 @@ public function __construct(ConfigFactoryInterface $config_factory, WebformExpor public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('file_system'), $container->get('plugin.manager.webform.exporter'), $container->get('webform_submission.exporter') ); @@ -68,17 +80,30 @@ public static function create(ContainerInterface $container) { public function buildForm(array $form, FormStateInterface $form_state) { $config = $this->config('webform.settings'); - // Export. $form['export_settings'] = [ '#type' => 'details', - '#title' => $this->t('Default export settings'), + '#title' => $this->t('Export general settings'), + '#open' => TRUE, + ]; + $form['export_settings']['temp_directory'] = [ + '#type' => 'textfield', + '#title' => $this->t('Temporary directory'), + '#description' => $this->t('A local file system path where temporary export files will be stored. This directory should be persistent between requests and should not be accessible over the web.'), + '#required' => TRUE, + '#default_value' => $config->get('export.temp_directory') ?: file_directory_temp(), + ]; + + // Export. + $form['export_default_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Export default settings'), '#description' => $this->t('Enter default export settings to be used by all webforms.'), '#open' => TRUE, ]; $export_options = $config->get('export'); $export_form_state = new FormState(); - $this->submissionExporter->buildExportOptionsForm($form['export_settings'], $export_form_state, $export_options); + $this->submissionExporter->buildExportOptionsForm($form['export_default_settings'], $export_form_state, $export_options); // (Excluded) Exporters. $form['exporter_types'] = [ @@ -95,16 +120,37 @@ public function buildForm(array $form, FormStateInterface $form_state) { return parent::buildForm($form, $form_state); } + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // Copied from: system_check_directory(). + $temp_directory = $form_state->getValue('temp_directory'); + if (!is_dir($temp_directory) && !$this->fileSystem->mkdir($temp_directory, NULL, TRUE)) { + $form_state->setErrorByName('temp_directory', $this->t('The directory %directory does not exist and could not be created.', ['%directory' => $temp_directory])); + } + if (is_dir($temp_directory) && !is_writable($temp_directory) && !$this->fileSystem->chmod($temp_directory)) { + $form_state->setErrorByName('temp_directory', $this->t('The directory %directory exists but is not writable and could not be made writable.', ['%directory' => $temp_directory])); + } + parent::validateForm($form, $form_state); + } + /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $excluded_exporters = $this->convertIncludedToExcludedPluginIds($this->exporterManager, $form_state->getValue('excluded_exporters')); - $config = $this->config('webform.settings'); - $config->set('export', $this->submissionExporter->getValuesFromInput($form_state->getValues()) + ['excluded_exporters' => $excluded_exporters]); - $config->save(); + $values = $form_state->getValues(); + + $export = $this->submissionExporter->getValuesFromInput($values) + ['excluded_exporters' => $excluded_exporters]; + // Set custom temp directory. + $export['temp_directory'] = ($values['temp_directory'] === file_directory_temp()) ? '' : $values['temp_directory']; + + // Update config and submit form. + $config = $this->config('webform.settings'); + $config->set('export', $export); parent::submitForm($form, $form_state); } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigFormsForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigFormsForm.php index cde2800ca5..2f32fe2568 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigFormsForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigFormsForm.php @@ -3,10 +3,16 @@ namespace Drupal\webform\Form\AdminConfig; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Url; +use Drupal\filter\Entity\FilterFormat; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\Entity\Webform; +use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\WebformAddonsManagerInterface; +use Drupal\webform\WebformInterface; use Drupal\webform\WebformTokenManagerInterface; use Drupal\webform\WebformThirdPartySettingsManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -16,6 +22,13 @@ */ class WebformAdminConfigFormsForm extends WebformAdminConfigBaseForm { + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * The webform token manager. * @@ -49,6 +62,8 @@ public function getFormId() { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. * @param \Drupal\webform\WebformThirdPartySettingsManagerInterface $third_party_settings_manager @@ -56,8 +71,9 @@ public function getFormId() { * @param \Drupal\webform\WebformAddonsManagerInterface $addons_manager * The webform add-ons manager. */ - public function __construct(ConfigFactoryInterface $config_factory, WebformTokenManagerInterface $token_manager, WebformThirdPartySettingsManagerInterface $third_party_settings_manager, WebformAddonsManagerInterface $addons_manager) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, WebformTokenManagerInterface $token_manager, WebformThirdPartySettingsManagerInterface $third_party_settings_manager, WebformAddonsManagerInterface $addons_manager) { parent::__construct($config_factory); + $this->moduleHandler = $module_handler; $this->tokenManager = $token_manager; $this->thirdPartySettingsManager = $third_party_settings_manager; $this->addonsManager = $addons_manager; @@ -69,6 +85,7 @@ public function __construct(ConfigFactoryInterface $config_factory, WebformToken public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('module_handler'), $container->get('webform.token_manager'), $container->get('webform.third_party_settings_manager'), $container->get('webform.addons_manager') @@ -92,7 +109,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['page_settings']['default_page_base_path'] = [ '#type' => 'textfield', '#title' => $this->t('Default base path for webform URLs'), - '#description' => $this->t('Leave blank to display the automatic generation of URL aliases for all webforms.'), + '#description' => $this->t('Leave blank to disable the automatic generation of URL aliases for all webforms.'), '#default_value' => $settings['default_page_base_path'], ]; $form['page_settings']['default_page_base_path_message'] = [ @@ -109,10 +126,20 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Form settings. $form['form_settings'] = [ '#type' => 'details', - '#title' => $this->t('Form settings'), + '#title' => $this->t('Form general settings'), '#open' => TRUE, '#tree' => TRUE, ]; + $form['form_settings']['default_status'] = [ + '#type' => 'radios', + '#title' => $this->t('Default status'), + '#default_value' => $settings['default_status'], + '#options' => [ + WebformInterface::STATUS_OPEN => $this->t('Open'), + WebformInterface::STATUS_CLOSED => $this->t('Closed'), + ], + '#options_display' => 'side_by_side', + ]; $form['form_settings']['default_form_open_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Default open message'), @@ -135,10 +162,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#required' => TRUE, '#default_value' => $settings['default_form_confidential_message'], ]; - $form['form_settings']['default_form_login_message'] = [ + $form['form_settings']['default_form_access_denied_message'] = [ '#type' => 'webform_html_editor', - '#title' => $this->t('Default login message when access denied to webform'), - '#default_value' => $settings['default_form_login_message'], + '#title' => $this->t('Default access denied message'), + '#required' => TRUE, + '#default_value' => $settings['default_form_access_denied_message'], ]; $form['form_settings']['default_form_required_label'] = [ '#type' => 'textfield', @@ -173,7 +201,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('A list of classes that will be provided in "Button CSS classes" dropdown. Enter one or more classes on each line. These styles should be available in your theme\'s CSS file.'), '#default_value' => $settings['button_classes'], ]; - $form['form_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['form_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Form Behaviors. $form['form_behaviors'] = [ @@ -215,7 +243,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { 'group' => $this->t('Validation'), 'title' => $this->t('Disable inline form errors for all webforms'), 'description' => $this->t('If checked, <a href=":href">inline form errors</a> will be disabled for all webforms.', [':href' => 'https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-form-errors-module-overview']), - 'access' => (\Drupal::moduleHandler()->moduleExists('inline_form_errors') && floatval(\Drupal::VERSION) >= 8.5), + 'access' => \Drupal::moduleHandler()->moduleExists('inline_form_errors'), ], 'default_form_required' => [ 'group' => $this->t('Validation'), @@ -257,7 +285,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Wizard settings. $form['wizard_settings'] = [ '#type' => 'details', - '#title' => $this->t('Wizard settings'), + '#title' => $this->t('Form wizard settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -293,7 +321,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Preview settings. $form['preview_settings'] = [ '#type' => 'details', - '#title' => $this->t('Preview settings'), + '#title' => $this->t('Form preview settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -335,37 +363,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('A list of classes that will be provided in the "Preview CSS classes" dropdown. Enter one or more classes on each line. These styles should be available in your theme\'s CSS file.'), '#default_value' => $config->get('settings.preview_classes'), ]; - - // Draft settings. - $form['draft_settings'] = [ - '#type' => 'details', - '#title' => $this->t('Draft settings'), - '#open' => TRUE, - '#tree' => TRUE, - ]; - $form['draft_settings']['default_draft_button_label'] = [ - '#type' => 'textfield', - '#title' => $this->t('Default draft button label'), - '#required' => TRUE, - '#size' => 20, - '#default_value' => $settings['default_draft_button_label'], - ]; - $form['draft_settings']['default_draft_saved_message'] = [ - '#type' => 'webform_html_editor', - '#title' => $this->t('Default draft save message'), - '#default_value' => $settings['default_draft_saved_message'], - ]; - $form['draft_settings']['default_draft_loaded_message'] = [ - '#type' => 'webform_html_editor', - '#title' => $this->t('Default draft load message'), - '#default_value' => $settings['default_draft_loaded_message'], - ]; - $form['draft_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['preview_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Confirmation settings. $form['confirmation_settings'] = [ '#type' => 'details', - '#title' => $this->t('Confirmation settings'), + '#title' => $this->t('Form confirmation settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -392,7 +395,126 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('A list of classes that will be provided in the "Confirmation back link CSS classes" dropdown. Enter one or more classes on each line. These styles should be available in your theme\'s CSS file.'), '#default_value' => $settings['confirmation_back_classes'], ]; - $form['confirmation_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['confirmation_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + + // Dialog settings. + $form['dialog_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Form dialog settings'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['dialog_settings']['dialog_options'] = [ + '#title' => $this->t('Dialog options'), + '#description' => [ + '#markup' => $this->t('Enter preset dialog options available to all webforms.'), + 'options' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Name must be lower-case and contain only letters, numbers, and underscores.'), + $this->t('Width and height are optional.'), + ], + ], + ], + '#type' => 'webform_multiple', + '#key' => 'name', + '#header' => [ + ['data' => $this->t('Machine name'), 'width' => '40%'], + ['data' => $this->t('Title'), 'width' => '40%'], + ['data' => $this->t('Width'), 'width' => '10%'], + ['data' => $this->t('Height'), 'width' => '10%'], + ], + '#element' => [ + 'name' => [ + '#type' => 'textfield', + '#title' => $this->t('Dialog machine name'), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Enter machine name…'), + '#pattern' => '^[a-z0-9_]*$', + '#error_no_message' => TRUE, + ], + 'title' => [ + '#type' => 'textfield', + '#title' => $this->t('Dialog title'), + '#placeholder' => $this->t('Enter title…'), + '#title_display' => 'invisible', + '#error_no_message' => TRUE, + ], + 'width' => [ + '#type' => 'number', + '#title' => $this->t('Dialog width'), + '#title_display' => 'invisible', + '#field_suffix' => 'px', + '#error_no_message' => TRUE, + ], + 'height' => [ + '#type' => 'number', + '#title' => $this->t('Dialog height'), + '#title_display' => 'invisible', + '#field_suffix' => 'px', + '#error_no_message' => TRUE, + ], + ], + '#error_no_message' => TRUE, + '#default_value' => $settings['dialog_options'], + '#parents' => ['dialog_settings', 'dialog_options'], + ]; + $form['dialog_settings']['dialog'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable site-wide dialog support'), + '#description' => $this->t('If checked, the webform dialog library will be added to every page on your website, this allows any webform to be opened in a modal dialog.') + . '<br /><br />' + . $this->t('Webform specific dialog links will be included on all webform settings form.'), + '#return_value' => TRUE, + '#default_value' => $settings['dialog'], + ]; + $form['dialog_settings']['dialog_messages'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="dialog_settings[dialog]"]' => ['checked' => TRUE], + ], + ], + ]; + // Display warning when text formats do not support adding the class + // attribute to links. + if ($this->moduleHandler->moduleExists('filter')) { + /** @var \Drupal\filter\FilterFormatInterface[] $filter_formats */ + $filter_formats = FilterFormat::loadMultiple(); + $dialog_not_allowed = []; + foreach ($filter_formats as $filter_format) { + $html_restrictions = $filter_format->getHtmlRestrictions(); + if ($html_restrictions && isset($html_restrictions['allowed']) && isset($html_restrictions['allowed']['a']) && !isset($html_restrictions['allowed']['a']['class'])) { + $dialog_not_allowed[] = $filter_format->label(); + } + } + if ($dialog_not_allowed) { + $t_args = [ + '@labels' => WebformArrayHelper::toString($dialog_not_allowed), + '@tag' => '<a href hreflang class>', + ':href' => Url::fromRoute('filter.admin_overview')->toString(), + ]; + $form['dialog_settings']['dialog_messages']['filter_formats_message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('<strong>IMPORTANT:</strong> To insert dialog links using the @labels <a href=":href">text formats</a> the @tag must be added to the allowed HTML tags.', $t_args), + '#message_type' => 'warning', + ]; + } + } + // Display install link module message. + if (!$this->moduleHandler->moduleExists('editor_advanced_link') && !$this->moduleHandler->moduleExists('menu_link_attributes')) { + $t_args = [ + ':editor_advanced_link_href' => 'https://www.drupal.org/project/editor_advanced_link', + ':menu_link_attributes_href' => 'https://www.drupal.org/project/menu_link_attributes', + ]; + $form['dialog_settings']['dialog_messages']['module_message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('To add the .webform-dialog class to a link\'s attributes, please use the <a href=":editor_advanced_link_href">D8 Editor Advanced link</a> or <a href=":menu_link_attributes_href">Menu Link Attributes</a> module.', $t_args), + '#message_type' => 'info', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } // Third party settings. $form['third_party_settings'] = [ @@ -440,17 +562,23 @@ public function submitForm(array &$form, FormStateInterface $form_state) { + $form_state->getValue('form_behaviors') + $form_state->getValue('wizard_settings') + $form_state->getValue('preview_settings') - + $form_state->getValue('draft_settings') - + $form_state->getValue('confirmation_settings'); + + $form_state->getValue('confirmation_settings') + + $form_state->getValue('dialog_settings'); // Track if we need to trigger an update of all webform paths // because the 'default_page_base_path' changed. $update_paths = ($settings['default_page_base_path'] != $this->config('webform.settings')->get('settings.default_page_base_path')) ? TRUE : FALSE; + // Filter empty dialog options. + foreach ($settings['dialog_options'] as $dialog_name => $dialog_options) { + $settings['dialog_options'][$dialog_name] = array_filter($dialog_options); + } + + // Update config and submit form. $config = $this->config('webform.settings'); $config->set('settings', $settings + $config->get('settings')); $config->set('third_party_settings', $form_state->getValue('third_party_settings') ?: []); - $config->save(); + parent::submitForm($form, $form_state); /* Update paths */ @@ -461,8 +589,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $webform->updatePaths(); } } - - parent::submitForm($form, $form_state); } } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigHandlersForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigHandlersForm.php index dc2939984c..6e3a51a1c0 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigHandlersForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigHandlersForm.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Form\AdminConfig; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Plugin\WebformHandlerManagerInterface; use Drupal\webform\WebformTokenManagerInterface; @@ -13,6 +14,13 @@ */ class WebformAdminConfigHandlersForm extends WebformAdminConfigBaseForm { + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * The webform token manager. * @@ -39,13 +47,16 @@ public function getFormId() { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. * @param \Drupal\webform\Plugin\WebformHandlerManagerInterface $handler_manager * The webform handler manager. */ - public function __construct(ConfigFactoryInterface $config_factory, WebformTokenManagerInterface $token_manager, WebformHandlerManagerInterface $handler_manager) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, WebformTokenManagerInterface $token_manager, WebformHandlerManagerInterface $handler_manager) { parent::__construct($config_factory); + $this->moduleHandler = $module_handler; $this->tokenManager = $token_manager; $this->handlerManager = $handler_manager; } @@ -56,6 +67,7 @@ public function __construct(ConfigFactoryInterface $config_factory, WebformToken public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('module_handler'), $container->get('webform.token_manager'), $container->get('plugin.manager.webform.handler') ); @@ -77,7 +89,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['mail']['roles'] = [ '#type' => 'webform_roles', - '#title' => $this->t('Recipent roles'), + '#title' => $this->t('Recipient roles'), '#description' => $this->t("Select roles that can be assigned to receive a webform's email. <em>Please note: Selected roles will be available to all webforms.</em>"), '#include_anonymous' => FALSE, '#default_value' => $config->get('mail.roles'), @@ -148,7 +160,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#required' => TRUE, '#default_value' => $config->get('mail.default_body_html'), ]; - $form['mail']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['mail']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Email / Handler: Types. $form['handler_types'] = [ @@ -156,6 +168,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Submission handlers'), '#description' => $this->t('Select available submission handlers'), '#open' => TRUE, + '#weight' => 10, ]; $form['handler_types']['excluded_handlers'] = $this->buildExcludedPlugins( $this->handlerManager, @@ -188,11 +201,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { $excluded_handlers = $this->convertIncludedToExcludedPluginIds($this->handlerManager, $form_state->getValue('excluded_handlers')); + // Update config and submit form. $config = $this->config('webform.settings'); $config->set('handler', ['excluded_handlers' => $excluded_handlers]); $config->set('mail', $form_state->getValue('mail')); - $config->save(); - parent::submitForm($form, $form_state); } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigLibrariesForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigLibrariesForm.php index 542613192e..5751cf1154 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigLibrariesForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigLibrariesForm.php @@ -4,6 +4,8 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\webform\Plugin\WebformElement\TableSelect; use Drupal\webform\WebformLibrariesManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -90,32 +92,63 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $config->get('assets.javascript'), ]; - // Libraries. - $form['libraries'] = [ + // Libraries optional. + $form['libraries_optional'] = [ '#type' => 'details', - '#title' => $this->t('External libraries'), + '#title' => $this->t('External optional libraries'), '#description' => $this->t('Uncheck the below optional external libraries that you do not want to be used by any webforms.') . '</br>' . '<em>' . $this->t('Please note, you can also exclude element types that are dependent on specific libraries.') . '</em>', '#open' => TRUE, - '#tree' => TRUE, ]; $libraries_header = [ 'title' => ['data' => $this->t('Title')], + 'version' => ['data' => $this->t('Version')], 'description' => ['data' => $this->t('Description/Notes'), 'class' => [RESPONSIVE_PRIORITY_LOW]], + 'elements' => ['data' => $this->t('Required elements'), 'class' => [RESPONSIVE_PRIORITY_LOW]], + 'provider' => ['data' => $this->t('Provider'), 'class' => [RESPONSIVE_PRIORITY_LOW]], + 'resources' => ['data' => $this->t('Resources'), 'class' => [RESPONSIVE_PRIORITY_LOW]], ]; $this->libraries = []; - $libraries_options = []; + $libraries_optional_options = []; + $libraries_required_option = []; $libraries = $this->librariesManager->getLibraries(); foreach ($libraries as $library_name => $library) { - // Only optional libraries can be excluded. - if (empty($library['optional'])) { - continue; + $operations = []; + $operations['homepage'] = [ + 'title' => $this->t('Homepage'), + 'url' => $library['homepage_url'], + ]; + if (isset($library['download_url'])) { + $operations['download'] = [ + 'title' => $this->t('Download'), + 'url' => $library['download_url'], + ]; + } + if (isset($library['issues_url'])) { + $issues_url = $library['issues_url']; + } + elseif (isset($library['download_url']) && preg_match('#https://github.com/[^/]+/[^/]+#', $library['download_url']->toString(), $match)) { + $issues_url = Url::fromUri($match[0] . '/issues'); + } + else { + $issues_url = NULL; + } + if ($issues_url) { + $operations['issues'] = [ + 'title' => $this->t('Open Issues'), + 'url' => $issues_url, + ]; + $accessibility_url = clone $issues_url; + $operations['accessibility'] = [ + 'title' => $this->t('Accessibility Issues'), + 'url' => $accessibility_url->setOption('query', ['q' => 'is:issue is:open accessibility ']), + ]; } - $this->libraries[$library_name] = $library_name; - $libraries_options[$library_name] = [ + $library_option = [ 'title' => $library['title'], + 'version' => $library['version'], 'description' => [ 'data' => [ 'content' => ['#markup' => $library['description'], '#suffix' => '<br />'], @@ -127,33 +160,70 @@ public function buildForm(array $form, FormStateInterface $form_state) { ] : [], ], ], + 'elements' => ['data' => ['#markup' => (isset($library['elements'])) ? implode('<br/>', $library['elements']) : '']], + 'provider' => $library['provider'], + 'resources' => [ + 'data' => [ + '#type' => 'operations', + '#links' => $operations, + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ], + ], ]; + + // Only optional libraries can be excluded. + if (empty($library['optional'])) { + $libraries_required_options[$library_name] = $library_option; + } + else { + $this->libraries[$library_name] = $library_name; + $libraries_optional_options[$library_name] = $library_option; + } } - $form['libraries']['excluded_libraries'] = [ + $form['libraries_optional']['excluded_libraries'] = [ '#type' => 'tableselect', '#title' => $this->t('Libraries'), '#header' => $libraries_header, '#js_select' => FALSE, - '#options' => $libraries_options, + '#options' => $libraries_optional_options, '#default_value' => array_diff($this->libraries, array_combine($config->get('libraries.excluded_libraries'), $config->get('libraries.excluded_libraries'))), ]; + TableSelect::setProcessTableSelectCallback($form['libraries_optional']['excluded_libraries']); + + // Display warning message when select2 and chosen are enabled. $t_args = [ ':select2_href' => $libraries['jquery.select2']['homepage_url']->toString(), ':chosen_href' => $libraries['jquery.chosen']['homepage_url']->toString(), ]; - $form['libraries']['select_message'] = [ + $form['libraries_optional']['select_message'] = [ '#type' => 'webform_message', '#message_type' => 'warning', '#message_message' => $this->t('<a href=":select2_href">Select2</a> and <a href=":chosen_href">Chosen</a> provide very similar functionality, most websites should only have one of these libraries enabled.', $t_args), '#states' => [ 'visible' => [ - ':input[name="libraries[excluded_libraries][jquery.select2]"]' => ['checked' => TRUE], - ':input[name="libraries[excluded_libraries][jquery.chosen]"]' => ['checked' => TRUE], + ':input[name="excluded_libraries[jquery.select2]"]' => ['checked' => TRUE], + ':input[name="excluded_libraries[jquery.chosen]"]' => ['checked' => TRUE], ], ], ]; + // Libraries required. + if ($libraries_required_option) { + $form['libraries_required'] = [ + '#type' => 'details', + '#title' => $this->t('External required libraries'), + '#description' => $this->t('The below external libraries are required by specified webform elements or modules.'), + '#open' => TRUE, + ]; + $form['libraries_required']['required_libraries'] = [ + '#type' => 'table', + '#header' => $libraries_header, + '#rows' => $libraries_required_options, + ]; + } + return parent::buildForm($form, $form_state); } @@ -162,25 +232,23 @@ public function buildForm(array $form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { // Convert list of included types to excluded types. - $libraries = $form_state->getValue('libraries'); - $libraries['excluded_libraries'] = array_diff($this->libraries, array_filter($libraries['excluded_libraries'])); - ksort($libraries['excluded_libraries']); + $excluded_libraries = array_diff($this->libraries, array_filter($form_state->getValue('excluded_libraries'))); + ksort($excluded_libraries); // Note: Must store a simple array of libraries because library names // may contain periods, which is not supported by Drupal's // config management. - $libraries['excluded_libraries'] = array_keys($libraries['excluded_libraries']); + $excluded_libraries = array_keys($excluded_libraries); + // Update config and submit form. $config = $this->config('webform.settings'); $config->set('assets', $form_state->getValue('assets')); - $config->set('libraries', $libraries); - $config->save(); + $config->set('libraries.excluded_libraries', $excluded_libraries); + parent::submitForm($form, $form_state); // Reset libraries cached. // @see webform_library_info_build() \Drupal::service('library.discovery')->clearCachedDefinitions(); - - parent::submitForm($form, $form_state); } } diff --git a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigSubmissionsForm.php b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigSubmissionsForm.php index 7653644591..65c79a31c4 100644 --- a/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigSubmissionsForm.php +++ b/web/modules/webform/src/Form/AdminConfig/WebformAdminConfigSubmissionsForm.php @@ -3,7 +3,9 @@ namespace Drupal\webform\Form\AdminConfig; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -12,6 +14,13 @@ */ class WebformAdminConfigSubmissionsForm extends WebformAdminConfigBaseForm { + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * The webform token manager. * @@ -31,11 +40,14 @@ public function getFormId() { * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. */ - public function __construct(ConfigFactoryInterface $config_factory, WebformTokenManagerInterface $token_manager) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, WebformTokenManagerInterface $token_manager) { parent::__construct($config_factory); + $this->moduleHandler = $module_handler; $this->tokenManager = $token_manager; } @@ -45,6 +57,7 @@ public function __construct(ConfigFactoryInterface $config_factory, WebformToken public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), + $container->get('module_handler'), $container->get('webform.token_manager') ); } @@ -59,15 +72,15 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Submission settings. $form['submission_settings'] = [ '#type' => 'details', - '#title' => $this->t('Submission settings'), + '#title' => $this->t('Submission general settings'), '#open' => TRUE, '#tree' => TRUE, ]; - $form['submission_settings']['default_submission_login_message'] = [ + $form['submission_settings']['default_submission_access_denied_message'] = [ '#type' => 'webform_html_editor', - '#title' => $this->t('Default login message when access denied to submission'), + '#title' => $this->t('Default access denied message'), '#required' => TRUE, - '#default_value' => $settings['default_submission_login_message'], + '#default_value' => $settings['default_submission_access_denied_message'], ]; $form['submission_settings']['default_submission_exception_message'] = [ '#type' => 'webform_html_editor', @@ -81,6 +94,18 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#required' => TRUE, '#default_value' => $settings['default_submission_locked_message'], ]; + $form['submission_settings']['default_previous_submission_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default previous submission message'), + '#required' => TRUE, + '#default_value' => $settings['default_previous_submission_message'], + ]; + $form['submission_settings']['default_previous_submissions_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default previous submissions message'), + '#required' => TRUE, + '#default_value' => $settings['default_previous_submissions_message'], + ]; $form['submission_settings']['default_autofill_message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Default autofill message'), @@ -93,7 +118,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#required' => TRUE, '#default_value' => $settings['default_submission_label'], ]; - $form['submission_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['submission_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Submission Behaviors. $form['submission_behaviors'] = [ @@ -105,7 +130,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { $behavior_elements = [ 'default_submission_log' => [ 'title' => $this->t('Log all submission events for all webforms'), - 'description' => $this->t('If checked, all submission events will be logged to dedicated submission log available to all webforms and submissions.'), + 'description' => $this->t('If checked, all submission events will be logged to dedicated submission log available to all webforms and submissions.') . '<br/><br/>' . + '<em>' . t('The webform submission log will track more detailed user information including email addresses and subjects.') . '</em>', ], ]; foreach ($behavior_elements as $behavior_key => $behavior_element) { @@ -117,11 +143,25 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $settings[$behavior_key], ]; } + if (!$this->moduleHandler->moduleExists('webform_submission_log')) { + $form['submission_behaviors']['webform_submission_log_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_message' => $this->t("Enable the 'Webform Submission Log' module to better track and permanently store submission logs."), + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + ':input[name="submission_behaviors[default_submission_log]"]' => ['checked' => TRUE], + ], + ], + ]; + } // Submission limits. $form['submission_limits'] = [ '#type' => 'details', - '#title' => $this->t('Submission limits'), + '#title' => $this->t('Submission limit settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -135,12 +175,38 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Default per user submission limit message'), '#default_value' => $config->get('settings.default_limit_user_message'), ]; - $form['submission_limits']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['submission_limits']['token_tree_link'] = $this->tokenManager->buildTreeElement(); + + // Draft settings. + $form['draft_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Submission draft settings'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['draft_settings']['default_draft_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default draft button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_draft_button_label'], + ]; + $form['draft_settings']['default_draft_saved_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default draft save message'), + '#default_value' => $settings['default_draft_saved_message'], + ]; + $form['draft_settings']['default_draft_loaded_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default draft load message'), + '#default_value' => $settings['default_draft_loaded_message'], + ]; + $form['draft_settings']['token_tree_link'] = $this->tokenManager->buildTreeElement(); // Submission purging. $form['purge'] = [ '#type' => 'details', - '#title' => $this->t('Submission purging'), + '#title' => $this->t('Submission purge settings'), '#open' => TRUE, '#tree' => TRUE, ]; @@ -152,6 +218,33 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('Enter the amount of submissions to be purged during single cron run. You may want to lower this number if you are facing memory or timeout issues when purging via cron.'), ]; + // Submission views. + $form['views_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Submission views settings'), + '#open' => TRUE, + '#tree' => TRUE, + ]; + $form['views_settings']['default_submission_views'] = [ + '#type' => 'webform_submission_views', + '#title' => $this->t('Submission views'), + '#title_display' => 'invisible', + '#global' => TRUE, + '#default_value' => $settings['default_submission_views'], + ]; + $form['views_settings']['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_message' => $this->t('Uncheck the below settings to allow webform administrators to choose which results should be replaced with submission views.'), + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + $form['views_settings']['default_submission_views_replace'] = [ + '#type' => 'webform_submission_views_replace', + '#global' => TRUE, + '#default_value' => $settings['default_submission_views_replace'], + ]; + $this->tokenManager->elementValidate($form); return parent::buildForm($form, $form_state); @@ -163,13 +256,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { $settings = $form_state->getValue('submission_settings') + $form_state->getValue('submission_behaviors') - + $form_state->getValue('submission_limits'); + + $form_state->getValue('submission_limits') + + $form_state->getValue('draft_settings') + + $form_state->getValue('views_settings'); + // Update config and submit form. $config = $this->config('webform.settings'); $config->set('settings', $settings + $config->get('settings')); $config->set('purge', $form_state->getValue('purge')); - $config->save(); - parent::submitForm($form, $form_state); } diff --git a/web/modules/webform/src/Form/WebformAjaxFormTrait.php b/web/modules/webform/src/Form/WebformAjaxFormTrait.php index 0129e9354e..cfd42b2866 100644 --- a/web/modules/webform/src/Form/WebformAjaxFormTrait.php +++ b/web/modules/webform/src/Form/WebformAjaxFormTrait.php @@ -8,10 +8,12 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Url; +use Drupal\webform\Ajax\WebformAnnounceCommand; use Drupal\webform\Ajax\WebformCloseDialogCommand; use Drupal\webform\Ajax\WebformRefreshCommand; use Drupal\webform\Ajax\WebformScrollTopCommand; use Drupal\webform\Ajax\WebformSubmissionAjaxResponse; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\WebformSubmissionForm; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -46,7 +48,7 @@ abstract public function cancelAjaxForm(array &$form, FormStateInterface $form_s * Get default ajax callback settings. * * @return array - * An associative array containing default ajax callback settings. + * An associative array containing default ajax callback settings. */ protected function getDefaultAjaxSettings() { return [ @@ -122,25 +124,28 @@ protected function buildAjaxForm(array &$form, FormStateInterface $form_state, a // Apply default settings. $settings += $this->getDefaultAjaxSettings(); - // Make sure the form has (submit) actions. - if (!isset($form['actions'])) { - return $form; - } - // Add Ajax callback to all submit buttons. - foreach (Element::children($form['actions']) as $key) { - $is_submit_button = (isset($form['actions'][$key]['#type']) && $form['actions'][$key]['#type'] == 'submit'); - if ($is_submit_button) { - $form['actions'][$key]['#ajax'] = [ - 'callback' => '::submitAjaxForm', - 'event' => 'click', - ] + $settings; + foreach (Element::children($form) as $element_key) { + if (!WebformElementHelper::isType($form[$element_key], 'actions')) { + continue; + } + + $actions =& $form[$element_key]; + foreach (Element::children($actions) as $action_key) { + if (WebformElementHelper::isType($actions[$action_key], 'submit')) { + $actions[$action_key]['#ajax'] = [ + 'callback' => '::submitAjaxForm', + 'event' => 'click', + ] + $settings; + } } } - // Add Ajax wrapper around the form. - $form['#form_wrapper_id'] = $this->getWrapperId(); - $form['#prefix'] = '<div id="' . $this->getWrapperId() . '">'; + // Add Ajax wrapper with wrapper content bookmark around the form. + // @see Drupal.AjaxCommands.prototype.webformScrollTop + $wrapper_id = $this->getWrapperId(); + $form['#form_wrapper_id'] = $wrapper_id; + $form['#prefix'] = '<a id="' . $wrapper_id . '-content" tabindex="-1"></a><div id="' . $wrapper_id . '">'; $form['#suffix'] = '</div>'; // Add Ajax library which contains 'Scroll to top' Ajax command and @@ -164,13 +169,16 @@ protected function buildAjaxForm(array &$form, FormStateInterface $form_state, a */ public function submitAjaxForm(array &$form, FormStateInterface $form_state) { $scroll_top_target = (isset($form['#webform_ajax_scroll_top'])) ? $form['#webform_ajax_scroll_top'] : 'form'; + if ($form_state->hasAnyErrors()) { // Display validation errors and scroll to the top of the page. $response = $this->replaceForm($form, $form_state); if ($scroll_top_target) { $response->addCommand(new WebformScrollTopCommand('#' . $this->getWrapperId(), $scroll_top_target)); } - return $response; + + // Announce validation errors. + $this->announce($this->t('Form validation errors have been found.')); } elseif ($form_state->isRebuilding()) { // Rebuild form. @@ -178,18 +186,26 @@ public function submitAjaxForm(array &$form, FormStateInterface $form_state) { if ($scroll_top_target) { $response->addCommand(new WebformScrollTopCommand('#' . $this->getWrapperId(), $scroll_top_target)); } - return $response; } elseif ($redirect_url = $this->getFormStateRedirectUrl($form_state)) { // Redirect to URL. $response = $this->createAjaxResponse($form, $form_state); $response->addCommand(new WebformCloseDialogCommand()); $response->addCommand(new WebformRefreshCommand($redirect_url)); - return $response; } else { - return $this->cancelAjaxForm($form, $form_state); + $response = $this->cancelAjaxForm($form, $form_state); } + + // Add announcements to Ajax response and then reset the announcements. + // @see \Drupal\webform\Form\WebformAjaxFormTrait::announce + $announcements = $this->getAnnouncements(); + foreach ($announcements as $announcement) { + $response->addCommand(new WebformAnnounceCommand($announcement['text'], $announcement['priority'])); + } + $this->resetAnnouncements(); + + return $response; } /** @@ -268,7 +284,7 @@ protected function replaceForm(array $form, FormStateInterface $form_state) { */ protected function getFormStateRedirectUrl(FormStateInterface $form_state) { // Always check the ?destination which is used by the off-canvas/system tray. - if (\Drupal::request()->get('destination')) { + if ($this->getRequest()->get('destination')) { $destination = $this->getRedirectDestination()->get(); return (strpos($destination, $destination) === 0) ? $destination : base_path() . $destination; } @@ -280,7 +296,7 @@ protected function getFormStateRedirectUrl(FormStateInterface $form_state) { // Re-enable redirect, grab the URL, and then disable again. $no_redirect = $form_state->isRedirectDisabled(); $form_state->disableRedirect(FALSE); - $redirect = $form_state->getRedirect() ?: $form_state->getResponse(); + $redirect = $form_state->getResponse() ?: $form_state->getRedirect(); $form_state->disableRedirect($no_redirect); if ($redirect instanceof RedirectResponse) { @@ -294,4 +310,66 @@ protected function getFormStateRedirectUrl(FormStateInterface $form_state) { } } + /****************************************************************************/ + // Drupal.announce handling. + // + // Announcements are stored in the user session because the $form_state + // is already serialized (and can't be altered) when announcements + // are added to Ajax response. + // @see \Drupal\webform\Form\WebformAjaxFormTrait::submitAjaxForm + /****************************************************************************/ + + /** + * Queue announcement with Ajax response. + * + * @param string $text + * A string to be read by the UA. + * @param string $priority + * A string to indicate the priority of the message. Can be either + * 'polite' or 'assertive'. + * + * @see \Drupal\webform\Ajax\WebformAnnounceCommand + * @see \Drupal\webform\Form\WebformAjaxFormTrait::submitAjaxForm + */ + protected function announce($text, $priority = 'polite') { + $announcements = $this->getAnnouncements(); + $announcements[] = [ + 'text' => $text, + 'priority' => $priority, + ]; + $this->setAnnouncements($announcements); + } + + /** + * Get announcements. + * + * @return array + * An associative array of announcements. + */ + protected function getAnnouncements() { + $session = $this->getRequest()->getSession(); + return $session->get('announcements') ?: []; + } + + /** + * Set announcements. + * + * @param array $announcements + * An associative array of announcements. + */ + protected function setAnnouncements(array $announcements) { + $session = $this->getRequest()->getSession(); + $session->set('announcements', $announcements); + $session->save(); + } + + /** + * Reset announcements. + */ + protected function resetAnnouncements() { + $session = $this->getRequest()->getSession(); + $session->remove('announcements'); + $session->save(); + } + } diff --git a/web/modules/webform/src/Form/WebformConfigEntityDeleteFormBase.php b/web/modules/webform/src/Form/WebformConfigEntityDeleteFormBase.php new file mode 100644 index 0000000000..6393b38a6a --- /dev/null +++ b/web/modules/webform/src/Form/WebformConfigEntityDeleteFormBase.php @@ -0,0 +1,199 @@ +<?php + +namespace Drupal\webform\Form; + +use Drupal\Core\Entity\EntityDeleteFormTrait; +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\ConfirmFormHelper; +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a generic base class for a webform entity deletion form. + * + * Copied from: \Drupal\Core\Entity\EntityConfirmFormBase. + */ +abstract class WebformConfigEntityDeleteFormBase extends EntityForm implements WebformDeleteFormInterface { + + use EntityDeleteFormTrait; + use WebformDialogFormTrait; + + /** + * Display confirmation checkbox. + * + * @var bool + */ + protected $confirmCheckbox = TRUE; + + /** + * {@inheritdoc} + */ + public function getBaseFormId() { + return $this->entity->getEntityTypeId() . '_confirm_form'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + if ($this->isDialog()) { + $t_args = [ + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '@label' => $this->getEntity()->label(), + ]; + return $this->t("Delete '@label' @entity-type?", $t_args); + } + else { + $t_args = [ + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '%label' => $this->getEntity()->label(), + ]; + return $this->t('Delete %label @entity-type?', $t_args); + } + } + + /** + * {@inheritdoc} + */ + public function getWarning() { + $t_args = [ + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '%label' => $this->getEntity()->label(), + ]; + + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to delete the %label @entity-type?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getDetails() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfirmInput() { + $t_args = [ + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '%label' => $this->getEntity()->label(), + ]; + + if ($this->confirmCheckbox) { + return [ + '#type' => 'checkbox', + '#title' => $this->t('Yes, I want to delete the %label @entity-type', $t_args), + '#required' => TRUE, + ]; + } + else { + return []; + } + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function getCancelText() { + return $this->t('Cancel'); + } + + /** + * {@inheritdoc} + */ + public function getFormName() { + return 'webform_config_entity_delete'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form['#attributes']['class'][] = 'confirmation'; + $form['#theme'] = 'confirm_form'; + $form[$this->getFormName()] = ['#type' => 'hidden', '#value' => 1]; + + // Title. + $form['#title'] = $this->getQuestion(); + + // Warning. + $form['warning'] = $this->getWarning(); + + // Description. + $form['description'] = $this->getDescription(); + + // Details and confirm input. + $details = $this->getDetails(); + $confirm_input = $this->getConfirmInput(); + if ($details) { + $form['details'] = $details; + } + if (!$details && $confirm_input) { + $form['hr'] = ['#markup' => '<p><hr/></p>']; + } + if ($confirm_input) { + $form['confirm'] = $confirm_input; + } + + // Dialog. + return $this->buildDialogConfirmForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + return [ + 'submit' => [ + '#type' => 'submit', + '#value' => $this->getConfirmText(), + '#submit' => [ + [$this, 'submitForm'], + ], + ], + 'cancel' => ConfirmFormHelper::buildCancelLink($this, $this->getRequest()), + ]; + } + + /** + * {@inheritdoc} + * + * The save() method is not used in EntityConfirmFormBase. This overrides the + * default implementation that saves the entity. + * + * Confirmation forms should override submitForm() instead for their logic. + */ + public function save(array $form, FormStateInterface $form_state) {} + + /** + * {@inheritdoc} + * + * The delete() method is not used in EntityConfirmFormBase. This overrides + * the default implementation that redirects to the delete-form confirmation + * form. + * + * Confirmation forms should override submitForm() instead for their logic. + */ + public function delete(array $form, FormStateInterface $form_state) {} + +} diff --git a/web/modules/webform/src/Form/WebformContributeForm.php b/web/modules/webform/src/Form/WebformContributeForm.php index 55d91570c9..61bb605fed 100644 --- a/web/modules/webform/src/Form/WebformContributeForm.php +++ b/web/modules/webform/src/Form/WebformContributeForm.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Form; -use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\HtmlCommand; use Drupal\Core\Ajax\RedirectCommand; @@ -11,7 +10,6 @@ use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Routing\RouteBuilderInterface; use Drupal\Core\Url; use Drupal\webform\WebformContributeManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -21,20 +19,6 @@ */ class WebformContributeForm extends ConfigFormBase { - /** - * The render cache bin. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $renderCache; - - /** - * The router builder. - * - * @var \Drupal\Core\Routing\RouteBuilderInterface - */ - protected $routerBuilder; - /** * The contribute manager. * @@ -57,21 +41,15 @@ public function getEditableConfigNames() { } /** - * Constructs a ContributeSettingsForm object. + * Constructs a WebformContributeForm object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. - * @param \Drupal\Core\Cache\CacheBackendInterface $render_cache - * The render cache service. - * @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder - * The router builder service. * @param \Drupal\webform\WebformContributeManagerInterface $contribute_manager * The contribute manager. */ - public function __construct(ConfigFactoryInterface $config_factory, CacheBackendInterface $render_cache, RouteBuilderInterface $router_builder, WebformContributeManagerInterface $contribute_manager) { + public function __construct(ConfigFactoryInterface $config_factory, WebformContributeManagerInterface $contribute_manager) { parent::__construct($config_factory); - $this->renderCache = $render_cache; - $this->routerBuilder = $router_builder; $this->contributeManager = $contribute_manager; } @@ -81,8 +59,6 @@ public function __construct(ConfigFactoryInterface $config_factory, CacheBackend public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), - $container->get('cache.render'), - $container->get('router.builder'), $container->get('webform.contribute_manager') ); } @@ -213,12 +189,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) { if ((string) $form_state->getValue('op') === (string) $this->t('Clear')) { $account_type = NULL; $account_id = NULL; - drupal_set_message($this->t('Community information has been cleared.')); + $this->messenger()->addStatus($this->t('Community information has been cleared.')); } else { $account_type = $form_state->getValue('account_type'); $account_id = $form_state->getValue($account_type . '_id'); - drupal_set_message($this->t('Your community information has been saved.')); + $this->messenger()->addStatus($this->t('Your community information has been saved.')); } // Always clear cached information. diff --git a/web/modules/webform/src/Form/WebformDeleteFormBase.php b/web/modules/webform/src/Form/WebformDeleteFormBase.php new file mode 100644 index 0000000000..c4f36934c4 --- /dev/null +++ b/web/modules/webform/src/Form/WebformDeleteFormBase.php @@ -0,0 +1,105 @@ +<?php + +namespace Drupal\webform\Form; + +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\ConfirmFormHelper; +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a generic base class for a webform deletion form. + */ +abstract class WebformDeleteFormBase extends ConfirmFormBase implements WebformDeleteFormInterface { + + use WebformDialogFormTrait; + + /** + * {@inheritdoc} + */ + public function getFormName() { + return 'webform_delete'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['#attributes']['class'][] = 'confirmation'; + $form['#theme'] = 'confirm_form'; + $form[$this->getFormName()] = ['#type' => 'hidden', '#value' => 1]; + + // Title. + $form['#title'] = $this->getQuestion(); + + // Warning. + $form['warning'] = $this->getWarning(); + + // Description. + $form['description'] = $this->getDescription(); + + // Details and confirm input. + $details = $this->getDetails(); + $confirm_input = $this->getConfirmInput(); + if ($details) { + $form['details'] = $details; + } + if (!$details && $confirm_input) { + $form['hr'] = ['#markup' => '<p><hr/></p>']; + } + if ($confirm_input) { + $form['confirm'] = $confirm_input; + } + + // Actions. + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->getConfirmText(), + '#button_type' => 'primary', + ]; + $form['actions']['cancel'] = ConfirmFormHelper::buildCancelLink($this, $this->getRequest()); + + return $this->buildDialogConfirmForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function getWarning() { + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to delete this?') . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getDetails() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfirmInput() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + +} diff --git a/web/modules/webform/src/Form/WebformDeleteFormInterface.php b/web/modules/webform/src/Form/WebformDeleteFormInterface.php new file mode 100644 index 0000000000..6e7b7b42be --- /dev/null +++ b/web/modules/webform/src/Form/WebformDeleteFormInterface.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\webform\Form; + +use Drupal\Core\Form\ConfirmFormInterface; + +/** + * Defines an interface for webform delete forms. + */ +interface WebformDeleteFormInterface extends ConfirmFormInterface { + + /** + * Returns warning message to display. + * + * @return array + * A renderable array containing a warning message. + */ + public function getWarning(); + + /** + * {@inheritdoc} + */ + public function getDescription(); + + /** + * Returns details to display. + * + * @return array + * A renderable array containing details. + */ + public function getDetails(); + + /** + * Returns confirm input to display. + * + * @return array + * A renderable array containing confirm input. + */ + public function getConfirmInput(); + +} diff --git a/web/modules/webform/src/Form/WebformDialogFormTrait.php b/web/modules/webform/src/Form/WebformDialogFormTrait.php index 4e41a781cf..994926e0e6 100644 --- a/web/modules/webform/src/Form/WebformDialogFormTrait.php +++ b/web/modules/webform/src/Form/WebformDialogFormTrait.php @@ -5,6 +5,8 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\CloseDialogCommand; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\webform\Utility\WebformDialogHelper; /** * Trait class for Webform Ajax dialog support. @@ -59,8 +61,8 @@ protected function buildDialogConfirmForm(array &$form, FormStateInterface $form $form['actions']['cancel'] = [ '#type' => 'submit', '#value' => $this->t('Cancel'), + '#validate' => ['::noValidate'], '#submit' => ['::noSubmit'], - '#validate' => ['::noSubmit'], '#weight' => 100, '#ajax' => [ 'callback' => '::cancelAjaxForm', @@ -70,6 +72,46 @@ protected function buildDialogConfirmForm(array &$form, FormStateInterface $form return $form; } + /** + * Build webform dialog delete link. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param \Drupal\Core\Url $url + * The delete URL. + */ + protected function buildDialogDeleteAction(array &$form, FormStateInterface $form_state, Url $url) { + // WORKAROUND: + // Create a hidden link that is clicked using jQuery. + if ($this->isDialog()) { + $form['delete'] = [ + '#type' => 'link', + '#title' => $this->t('Delete'), + '#url' => $url, + '#attributes' => ['style' => 'display:none'] + WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['webform-dialog-delete-link']), + ]; + $form['actions']['delete'] = [ + '#type' => 'submit', + '#value' => $this->t('Delete'), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + 'onclick' => "jQuery('.webform-dialog-delete-link').click(); return false;", + ], + ]; + } + else { + $form['actions']['delete'] = [ + '#type' => 'link', + '#title' => $this->t('Delete'), + '#url' => $url, + '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['button', 'button--danger']), + ]; + } + WebformDialogHelper::attachLibraries($form); + } + /****************************************************************************/ // Ajax submit callbacks. /****************************************************************************/ @@ -83,11 +125,16 @@ public function cancelAjaxForm(array &$form, FormStateInterface $form_state) { return $response; } + /** + * Validate callback to clear validation errors. + */ + public function noValidate(array &$form, FormStateInterface $form_state) { + // Clear all validation errors. + $form_state->clearErrors(); + } + /** * Empty submit callback used to only have the submit button to use an #ajax submit callback. - * - * This allows modal dialog to using ::submitCallback to validate and submit - * the form via one ajax request. */ public function noSubmit(array &$form, FormStateInterface $form_state) { // Do nothing. diff --git a/web/modules/webform/src/Form/WebformEntityFilterForm.php b/web/modules/webform/src/Form/WebformEntityFilterForm.php index fc30e8ffd5..3ff0291ec3 100644 --- a/web/modules/webform/src/Form/WebformEntityFilterForm.php +++ b/web/modules/webform/src/Form/WebformEntityFilterForm.php @@ -5,10 +5,11 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\WebformInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides the webform filter webform. + * Provides the webform filter form. */ class WebformEntityFilterForm extends FormBase { @@ -58,11 +59,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $search = ]; $form['filter']['search'] = [ '#type' => 'search', - '#title' => $this->t('Filter by title, description, elements, user name, or role'), + '#title' => $this->t('Keyword'), '#title_display' => 'invisible', - '#autocomplete_route_name' => 'entity.webform.autocomplete', + '#autocomplete_route_name' => 'entity.webform.autocomplete' . ($state === WebformInterface::STATUS_ARCHIVED ? '.archived' : ''), '#placeholder' => $this->t('Filter by title, description, elements, user name, or role'), - '#maxlength' => 128, + // Allow autocomplete to use long webform titles. + '#maxlength' => 500, '#size' => 45, '#default_value' => $search, ]; diff --git a/web/modules/webform/src/Form/WebformHandlerAddForm.php b/web/modules/webform/src/Form/WebformHandlerAddForm.php index 031e9277c2..a59c8093ff 100644 --- a/web/modules/webform/src/Form/WebformHandlerAddForm.php +++ b/web/modules/webform/src/Form/WebformHandlerAddForm.php @@ -2,9 +2,12 @@ namespace Drupal\webform\Form; +use Drupal\Component\Transliteration\TransliterationInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\webform\Plugin\WebformHandlerManagerInterface; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -23,10 +26,17 @@ class WebformHandlerAddForm extends WebformHandlerFormBase { /** * Constructs a WebformHandlerAddForm. * + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration + * The transliteration helper. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. * @param \Drupal\webform\Plugin\WebformHandlerManagerInterface $webform_handler * The webform handler manager. */ - public function __construct(WebformHandlerManagerInterface $webform_handler) { + public function __construct(LanguageManagerInterface $language_manager, TransliterationInterface $transliteration, WebformTokenManagerInterface $token_manager, WebformHandlerManagerInterface $webform_handler) { + parent::__construct($language_manager, $transliteration, $token_manager); $this->webformHandlerManager = $webform_handler; } @@ -35,6 +45,9 @@ public function __construct(WebformHandlerManagerInterface $webform_handler) { */ public static function create(ContainerInterface $container) { return new static( + $container->get('language_manager'), + $container->get('transliteration'), + $container->get('webform.token_manager'), $container->get('plugin.manager.webform.handler') ); } diff --git a/web/modules/webform/src/Form/WebformHandlerDeleteForm.php b/web/modules/webform/src/Form/WebformHandlerDeleteForm.php index 6f841c95c6..9ea7c88c87 100644 --- a/web/modules/webform/src/Form/WebformHandlerDeleteForm.php +++ b/web/modules/webform/src/Form/WebformHandlerDeleteForm.php @@ -2,16 +2,13 @@ namespace Drupal\webform\Form; -use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\WebformInterface; /** * Form for deleting a webform handler. */ -class WebformHandlerDeleteForm extends ConfirmFormBase { - - use WebformDialogFormTrait; +class WebformHandlerDeleteForm extends WebformDeleteFormBase { /** * The webform containing the webform handler to be deleted. @@ -31,16 +28,56 @@ class WebformHandlerDeleteForm extends ConfirmFormBase { * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to delete the @handler handler from the %webform webform?', ['%webform' => $this->webform->label(), '@handler' => $this->webformHandler->label()]); + if ($this->isDialog()) { + $t_args = [ + '@title' => $this->webformHandler->label(), + ]; + return $this->t("Delete the '@title' handler?", $t_args); + } + else { + $t_args = [ + '%webform' => $this->webform->label(), + '%title' => $this->webformHandler->label(), + ]; + return $this->t('Delete the %title handler from the %webform webform?', $t_args); + } } /** * {@inheritdoc} */ - public function getConfirmText() { - return $this->t('Delete'); + public function getWarning() { + $t_args = ['%title' => $this->webformHandler->label()]; + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to delete the %title handler?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; } + /** + * {@inheritdoc} + */ + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove this handler'), + $this->t('Cancel all pending actions'), + ], + ], + ]; + } + + /****************************************************************************/ + // Form methods. + /****************************************************************************/ + /** * {@inheritdoc} */ @@ -62,9 +99,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn $this->webform = $webform; $this->webformHandler = $this->webform->getHandler($webform_handler); - $form = parent::buildForm($form, $form_state); - $this->buildDialogConfirmForm($form, $form_state); - return $form; + return parent::buildForm($form, $form_state); } /** @@ -72,7 +107,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->webform->deleteWebformHandler($this->webformHandler); - drupal_set_message($this->t('The webform handler %name has been deleted.', ['%name' => $this->webformHandler->label()])); + $this->messenger()->addStatus($this->t('The webform handler %name has been deleted.', ['%name' => $this->webformHandler->label()])); $form_state->setRedirectUrl($this->webform->toUrl('handlers')); } diff --git a/web/modules/webform/src/Form/WebformHandlerEditForm.php b/web/modules/webform/src/Form/WebformHandlerEditForm.php index c0aeee4a2a..89fd6a97c4 100644 --- a/web/modules/webform/src/Form/WebformHandlerEditForm.php +++ b/web/modules/webform/src/Form/WebformHandlerEditForm.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Form; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\webform\WebformInterface; /** @@ -16,6 +17,11 @@ class WebformHandlerEditForm extends WebformHandlerFormBase { public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL, $webform_handler = NULL) { $form = parent::buildForm($form, $form_state, $webform, $webform_handler); $form['#title'] = $this->t('Edit @label handler', ['@label' => $this->webformHandler->label()]); + + // Delete action. + $url = new Url('entity.webform.handler.delete_form', ['webform' => $webform->id(), 'webform_handler' => $this->webformHandler->getHandlerId()]); + $this->buildDialogDeleteAction($form, $form_state, $url); + return $form; } diff --git a/web/modules/webform/src/Form/WebformHandlerFormBase.php b/web/modules/webform/src/Form/WebformHandlerFormBase.php index c215c2f45c..ed2376968a 100644 --- a/web/modules/webform/src/Form/WebformHandlerFormBase.php +++ b/web/modules/webform/src/Form/WebformHandlerFormBase.php @@ -2,14 +2,18 @@ namespace Drupal\webform\Form; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; +use Drupal\Component\Transliteration\TransliterationInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; -use Drupal\Component\Plugin\Exception\PluginNotFoundException; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\webform\Plugin\WebformHandlerInterface; use Drupal\webform\Utility\WebformFormHelper; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a base webform for webform handlers. @@ -18,6 +22,32 @@ abstract class WebformHandlerFormBase extends FormBase { use WebformDialogFormTrait; + /** + * Machine name maxlenght. + */ + const MACHINE_NAME_MAXLENGHTH = 64; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * The transliteration helper. + * + * @var \Drupal\Component\Transliteration\TransliterationInterface + */ + protected $transliteration; + + /** + * The token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + /** * The webform. * @@ -39,6 +69,33 @@ public function getFormId() { return 'webform_handler_form'; } + /** + * Constructs a WebformHandlerFormBase. + * + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration + * The transliteration helper. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. + */ + public function __construct(LanguageManagerInterface $language_manager, TransliterationInterface $transliteration, WebformTokenManagerInterface $token_manager) { + $this->languageManager = $language_manager; + $this->transliteration = $transliteration; + $this->tokenManager = $token_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('language_manager'), + $container->get('transliteration'), + $container->get('webform.token_manager') + ); + } + /** * Form constructor. * @@ -122,7 +179,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn ]; $form['general']['handler_id'] = [ '#type' => 'machine_name', - '#maxlength' => 64, + '#maxlength' => static::MACHINE_NAME_MAXLENGHTH, '#description' => $this->t('A unique name for this handler instance. Must be alpha-numeric and underscore separated.'), '#default_value' => $this->webformHandler->getHandlerId() ?: $this->getUniqueMachineName($this->webformHandler), '#required' => TRUE, @@ -189,13 +246,6 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn '#value' => $request->query->has('weight') ? (int) $request->query->get('weight') : $this->webformHandler->getWeight(), ]; - $form['actions'] = ['#type' => 'actions']; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Save'), - '#button_type' => 'primary', - ]; - // Build tabs. $tabs = [ 'conditions' => [ @@ -217,6 +267,20 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformIn ]; $form = WebformFormHelper::buildTabs($form, $tabs); + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#button_type' => 'primary', + ]; + + // Add token links below the form and on every tab. + $form['token_tree_link'] = $this->tokenManager->buildTreeElement(); + if ($form['token_tree_link']) { + $form['token_tree_link'] += [ + '#weight' => 101, + ]; + } return $this->buildDialogForm($form, $form_state); } @@ -264,35 +328,49 @@ public function submitForm(array &$form, FormStateInterface $form_state) { if ($this instanceof WebformHandlerAddForm) { $this->webform->addWebformHandler($this->webformHandler); - drupal_set_message($this->t('The webform handler was successfully added.')); + $this->messenger()->addStatus($this->t('The webform handler was successfully added.')); } else { $this->webform->updateWebformHandler($this->webformHandler); - drupal_set_message($this->t('The webform handler was successfully updated.')); + $this->messenger()->addStatus($this->t('The webform handler was successfully updated.')); } $form_state->setRedirectUrl($this->webform->toUrl('handlers', ['query' => ['update' => $this->webformHandler->getHandlerId()]])); } /** - * Generates a unique machine name for a webform handler instance. + * Generates a unique translated machine name for a webform handler instance. * * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler * The webform handler. * * @return string - * Returns the unique name. + * Returns a unique machine based the handler's plugin label. + * + * @see \Drupal\Core\Render\Element\MachineName + * @see \Drupal\system\MachineNameController::transliterate */ public function getUniqueMachineName(WebformHandlerInterface $handler) { - $suggestion = $handler->getPluginId(); + // Get label which default to the plugin's label for new instances. + $label = (string) $this->webformHandler->label(); + + // Get current langcode. + $langcode = $this->languageManager->getCurrentLanguage()->getId(); + + // Get machine name. + $suggestion = $this->transliteration->transliterate($label, $langcode, '_', static::MACHINE_NAME_MAXLENGHTH); + $suggestion = mb_strtolower($suggestion); + $suggestion = preg_replace('@' . strtr('[^a-z0-9_]+', ['@' => '\@', chr(0) => '']) . '@', '_', $suggestion); + + // Increment the machine name. $count = 1; $machine_default = $suggestion; $instance_ids = $this->webform->getHandlers()->getInstanceIds(); while (isset($instance_ids[$machine_default])) { $machine_default = $suggestion . '_' . $count++; } - // Only return a suggestion if it is not the default plugin id. - return ($machine_default != $handler->getPluginId()) ? $machine_default : ''; + + return $machine_default; } /** @@ -306,10 +384,29 @@ public function getUniqueMachineName(WebformHandlerInterface $handler) { */ public function exists($handler_id) { $instance_ids = $this->webform->getHandlers()->getInstanceIds(); - return (isset($instance_ids[$handler_id])) ? TRUE : FALSE; } + /** + * Get the webform handler's webform. + * + * @return \Drupal\webform\WebformInterface + * A webform. + */ + public function getWebform() { + return $this->webform; + } + + /** + * Get the webform handler. + * + * @return \Drupal\webform\Plugin\WebformHandlerInterface + * A webform handler. + */ + public function getWebformHandler() { + return $this->webformHandler; + } + /** * Process handler webform errors in webform. * diff --git a/web/modules/webform/src/Form/WebformHelpVideoForm.php b/web/modules/webform/src/Form/WebformHelpVideoForm.php index a7240ed147..4273391f4a 100644 --- a/web/modules/webform/src/Form/WebformHelpVideoForm.php +++ b/web/modules/webform/src/Form/WebformHelpVideoForm.php @@ -86,14 +86,16 @@ public function buildForm(array $form, FormStateInterface $form_state, $id = NUL } // Related resources. - $form['resources'] = [ - '#type' => 'details', - '#title' => $this->t('Additional resources'), - 'links' => [ - '#theme' => 'links', - '#links' => $this->helpManager->getVideoLinks($id), - ], - ]; + if ($video_links = $this->helpManager->getVideoLinks($this->videoId)) { + $form['resources'] = [ + '#type' => 'details', + '#title' => $this->t('Additional resources'), + 'links' => [ + '#theme' => 'links', + '#links' => $video_links, + ], + ]; + } // Actions. if (isset($video['submit_label'])) { diff --git a/web/modules/webform/src/Form/WebformOptionsFilterForm.php b/web/modules/webform/src/Form/WebformOptionsFilterForm.php new file mode 100644 index 0000000000..7c2bcc735f --- /dev/null +++ b/web/modules/webform/src/Form/WebformOptionsFilterForm.php @@ -0,0 +1,88 @@ +<?php + +namespace Drupal\webform\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides the webform options filter form. + */ +class WebformOptionsFilterForm extends FormBase { + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_options_filter_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $search = NULL, $category = NULL, array $categories = []) { + $form['#attributes'] = ['class' => ['webform-filter-form']]; + $form['filter'] = [ + '#type' => 'details', + '#title' => $this->t('Filter options'), + '#open' => TRUE, + '#attributes' => ['class' => ['container-inline']], + ]; + $form['filter']['search'] = [ + '#type' => 'search', + '#title' => $this->t('Keyword'), + '#title_display' => 'invisible', + '#autocomplete_route_name' => 'entity.webform_options.autocomplete', + '#placeholder' => $this->t('Filter by title or options'), + '#size' => 45, + '#default_value' => $search, + ]; + $form['filter']['category'] = [ + '#type' => 'select', + '#title' => $this->t('Category'), + '#title_display' => 'invisible', + '#options' => $categories, + '#empty_option' => ($category) ? $this->t('Show all options') : $this->t('Filter by category'), + '#default_value' => $category, + ]; + $form['filter']['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Filter'), + ]; + if (!empty($search) || !empty($category)) { + $form['filter']['reset'] = [ + '#type' => 'submit', + '#submit' => ['::resetForm'], + '#value' => $this->t('Reset'), + ]; + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $query = [ + 'search' => trim($form_state->getValue('search')), + 'category' => trim($form_state->getValue('category')), + ]; + $form_state->setRedirect($this->getRouteMatch()->getRouteName(), $this->getRouteMatch()->getRawParameters()->all(), [ + 'query' => $query , + ]); + } + + /** + * Resets the filter selection. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function resetForm(array &$form, FormStateInterface $form_state) { + $form_state->setRedirect($this->getRouteMatch()->getRouteName(), $this->getRouteMatch()->getRawParameters()->all()); + } + +} diff --git a/web/modules/webform/src/Form/WebformResultsClearForm.php b/web/modules/webform/src/Form/WebformResultsClearForm.php index 8043fad37a..0f8509065b 100644 --- a/web/modules/webform/src/Form/WebformResultsClearForm.php +++ b/web/modules/webform/src/Form/WebformResultsClearForm.php @@ -2,6 +2,8 @@ namespace Drupal\webform\Form; +use Drupal\Core\Form\FormStateInterface; + /** * Webform for webform results clear webform. */ @@ -18,13 +20,78 @@ public function getFormId() { * {@inheritdoc} */ public function getQuestion() { - if ($this->sourceEntity) { - $t_args = ['%title' => $this->sourceEntity->label()]; + $t_args = ['%label' => $this->getLabel()]; + return $this->t('Clear all %label submissions?', $t_args); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $submission_total = $this->submissionStorage->getTotal($this->webform, $this->sourceEntity); + if ($submission_total) { + return parent::buildForm($form, $form_state); } else { - $t_args = ['%title' => $this->webform->label()]; + $t_args = ['%label' => $this->getLabel()]; + $form['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'error', + '#message_message' => $this->t('There are no %label submissions.', $t_args), + ]; + return $form; } - return $this->t('Are you sure you want to delete all submissions to the %title webform?', $t_args); + } + + /** + * {@inheritdoc} + */ + public function getWarning() { + $t_args = ['%label' => $this->getLabel()]; + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to clear all %label submissions?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + $submission_total = $this->submissionStorage->getTotal($this->webform, $this->sourceEntity); + + $t_args = [ + '%label' => $this->getLabel(), + '@total' => $submission_total, + '@submissions' => $this->formatPlural($submission_total, 'submission', 'submissions'), + ]; + + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove @total %label @submissions', $t_args), + ['#markup' => '<em>' . $this->t('Take a few minutes to complete') . '</em>'], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getConfirmInput() { + $t_args = ['%label' => $this->getLabel()]; + return [ + '#type' => 'checkbox', + '#title' => $this->t('Yes, I want to clear all %label submissions', $t_args), + '#required' => TRUE, + ]; } /** @@ -38,13 +105,8 @@ public function getCancelUrl() { * {@inheritdoc} */ public function getMessage() { - if ($this->sourceEntity) { - $t_args = ['%title' => $this->sourceEntity->label()]; - } - else { - $t_args = ['%title' => $this->webform->label()]; - } - $this->t('Webform %title submissions cleared.', $t_args); + $t_args = ['%label' => $this->getLabel()]; + $this->t('Webform %label submissions cleared.', $t_args); } } diff --git a/web/modules/webform/src/Form/WebformResultsCustomForm.php b/web/modules/webform/src/Form/WebformResultsCustomForm.php index ddcc570f55..c3cd752930 100644 --- a/web/modules/webform/src/Form/WebformResultsCustomForm.php +++ b/web/modules/webform/src/Form/WebformResultsCustomForm.php @@ -123,12 +123,16 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['sort'] = [ '#prefix' => '<div class="container-inline">', '#type' => 'select', + '#title' => $this->t('Sort by'), + '#title_display' => 'invisible', '#field_prefix' => $this->t('Sort by'), '#options' => $sort_options, '#default_value' => $sort, ]; $form['direction'] = [ '#type' => 'select', + '#title' => $this->t('Direction'), + '#title_display' => 'invisible', '#field_prefix' => ' ' . $this->t('in', [], ['context' => 'Sort by {sort} in {direction} order']) . ' ', '#field_suffix' => ' ' . $this->t('order', [], ['context' => 'Sort by {sort} in {direction} order']), '#options' => [ @@ -143,6 +147,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { $limit = $this->webform->getState($this->getStateKey('limit'), NULL); $form['limit'] = [ '#type' => 'select', + '#title' => $this->t('Results per page'), + '#title_display' => 'invisible', '#field_prefix' => $this->t('Show', [], ['context' => 'Show {limit} results per page']), '#field_suffix' => $this->t('results per page'), '#options' => [ @@ -151,10 +157,8 @@ public function buildForm(array $form, FormStateInterface $form_state) { '100' => '100', '200' => '200', '500' => '500', - '1000' => '1000', - '0' => $this->t('All'), ], - '#default_value' => ($limit !== NULL) ? $limit : 50, + '#default_value' => ($limit !== NULL) ? $limit : 20, ]; // Default configuration. @@ -202,11 +206,31 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#default_value' => $format['element_format'], ]; + // Submission settings. + $form['submission'] = [ + '#type' => 'details', + '#title' => $this->t('Submission settings'), + ]; + // Get link types. + // @see entity.webform_submission.* route names. + $link_type_options = [ + 'canonical' => $this->t('View'), + 'table' => $this->t('Table'), + ]; + $form['submission']['link_type'] = [ + '#type' => 'select', + '#title' => $this->t('Link submissions to…'), + '#descriptions' => $this->t('Please note: Drafts will always be linked to submission form.'), + '#options' => $link_type_options, + '#default_value' => $this->webform->getState($this->getStateKey('link_type'), 'canonical'), + ]; + // Build actions. $form['actions']['#type'] = 'actions'; $form['actions']['save'] = [ '#type' => 'submit', '#value' => $this->t('Save'), + '#button_type' => 'primary', ]; $form['actions']['delete'] = [ '#type' => 'submit', @@ -285,6 +309,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $this->webform->setState($this->getStateKey('direction'), $form_state->getValue('direction')); $this->webform->setState($this->getStateKey('limit'), (int) $form_state->getValue('limit')); $this->webform->setState($this->getStateKey('format'), $form_state->getValue('format')); + $this->webform->setState($this->getStateKey('link_type'), $form_state->getValue('link_type')); // Set default. if (empty($this->sourceEntity)) { @@ -292,7 +317,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } // Display message. - drupal_set_message($this->t('The customized table has been saved.')); + $this->messenger()->addStatus($this->t('The customized table has been saved.')); // Set redirect. $redirect_url = $this->requestHandler->getUrl($this->webform, $this->sourceEntity, 'webform.results_submissions'); @@ -314,7 +339,7 @@ public function delete(array &$form, FormStateInterface $form_state) { $this->webform->deleteState($this->getStateKey('limit')); $this->webform->deleteState($this->getStateKey('default')); $this->webform->deleteState($this->getStateKey('format')); - drupal_set_message($this->t('The customized table has been reset.')); + $this->messenger()->addStatus($this->t('The customized table has been reset.')); } /** diff --git a/web/modules/webform/src/Form/WebformResultsExportForm.php b/web/modules/webform/src/Form/WebformResultsExportForm.php index 0d672768f3..2964f1c5c9 100644 --- a/web/modules/webform/src/Form/WebformResultsExportForm.php +++ b/web/modules/webform/src/Form/WebformResultsExportForm.php @@ -80,6 +80,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#access' => ($saved_options) ? TRUE : FALSE, '#submit' => ['::delete'], ]; + + // Disable single submit. + $form['#attributes']['class'][] = 'webform-remove-single-submit'; + $form['#attached']['library'][] = 'webform/webform.form'; + return $form; } @@ -121,7 +126,7 @@ public function save(array &$form, FormStateInterface $form_state) { // Save the export options to the webform's state. $export_options = $this->submissionExporter->getValuesFromInput($form_state->getValues()); $this->submissionExporter->setWebformOptions($export_options); - drupal_set_message($this->t('The download settings have been saved.')); + $this->messenger()->addStatus($this->t('The download settings have been saved.')); } /** @@ -134,7 +139,7 @@ public function save(array &$form, FormStateInterface $form_state) { */ public function delete(array &$form, FormStateInterface $form_state) { $this->submissionExporter->deleteWebformOptions(); - drupal_set_message($this->t('The download settings have been reset.')); + $this->messenger()->addStatus($this->t('The download settings have been reset.')); } } diff --git a/web/modules/webform/src/Form/WebformSubmissionDeleteForm.php b/web/modules/webform/src/Form/WebformSubmissionDeleteForm.php index d98f591fe6..0bc0170d7c 100644 --- a/web/modules/webform/src/Form/WebformSubmissionDeleteForm.php +++ b/web/modules/webform/src/Form/WebformSubmissionDeleteForm.php @@ -11,7 +11,9 @@ /** * Provides a confirmation webform for deleting a webform submission. */ -class WebformSubmissionDeleteForm extends ContentEntityDeleteForm { +class WebformSubmissionDeleteForm extends ContentEntityDeleteForm implements WebformDeleteFormInterface { + + use WebformDialogFormTrait; /** * The webform entity. @@ -20,7 +22,6 @@ class WebformSubmissionDeleteForm extends ContentEntityDeleteForm { */ protected $webform; - /** * The webform submission entity. * @@ -71,7 +72,12 @@ public static function create(ContainerInterface $container) { public function buildForm(array $form, FormStateInterface $form_state) { list($this->webformSubmission, $this->sourceEntity) = $this->requestHandler->getWebformSubmissionEntities(); $this->webform = $this->webformSubmission->getWebform(); - return parent::buildForm($form, $form_state); + + $form['warning'] = $this->getWarning(); + $form = parent::buildForm($form, $form_state); + $form['description'] = $this->getDescription(); + + return $this->buildDialogConfirmForm($form, $form_state); } /** @@ -80,7 +86,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { protected function actions(array $form, FormStateInterface $form_state) { // Issue #2582295: Confirmation cancel links are incorrect if installed in // a subdirectory - // Work-around: Remove sudirectory from destination before generating + // Work-around: Remove subdirectory from destination before generating // actions. $request = $this->getRequest(); $destination = $request->query->get('destination'); @@ -101,14 +107,67 @@ protected function actions(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to delete @title?', ['@title' => $this->webformSubmission->label()]); + $t_args = [ + '%label' => $this->getEntity()->label(), + ]; + return $this->t('Delete %label?', $t_args); + } + + /** + * {@inheritdoc} + */ + public function getWarning() { + $t_args = [ + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '%label' => $this->getEntity()->label(), + ]; + + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to delete the %label @entity-type?', $t_args) . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove records from the database'), + $this->t('Delete any uploaded files'), + $this->t('Cancel all pending actions'), + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getDetails() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfirmInput() { + return []; } /** * {@inheritdoc} */ protected function getDeletionMessage() { - return $this->t('@title has been deleted.', ['@title' => $this->webformSubmission->label()]); + return $this->t('%label has been deleted.', ['%label' => $this->webformSubmission->label()]); } /** diff --git a/web/modules/webform/src/Form/WebformSubmissionDeleteMultiple.php b/web/modules/webform/src/Form/WebformSubmissionDeleteMultiple.php index a250e0ebf2..ecb673f192 100644 --- a/web/modules/webform/src/Form/WebformSubmissionDeleteMultiple.php +++ b/web/modules/webform/src/Form/WebformSubmissionDeleteMultiple.php @@ -6,7 +6,7 @@ use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; -use Drupal\user\PrivateTempStoreFactory; +use Drupal\Core\TempStore\PrivateTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -25,7 +25,7 @@ class WebformSubmissionDeleteMultiple extends ConfirmFormBase { /** * The tempstore factory. * - * @var \Drupal\user\PrivateTempStoreFactory + * @var \Drupal\Core\TempStore\PrivateTempStoreFactory */ protected $tempStoreFactory; @@ -39,7 +39,7 @@ class WebformSubmissionDeleteMultiple extends ConfirmFormBase { /** * Constructs a WebformSubmissionDeleteMultiple object. * - * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory * The tempstore factory. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager * The entity type manager. @@ -54,7 +54,7 @@ public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityT */ public static function create(ContainerInterface $container) { return new static( - $container->get('user.private_tempstore'), + $container->get('tempstore.private'), $container->get('entity_type.manager') ); } diff --git a/web/modules/webform/src/Form/WebformSubmissionFilterForm.php b/web/modules/webform/src/Form/WebformSubmissionFilterForm.php index 03bd235371..0ddc78d42b 100644 --- a/web/modules/webform/src/Form/WebformSubmissionFilterForm.php +++ b/web/modules/webform/src/Form/WebformSubmissionFilterForm.php @@ -4,9 +4,10 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\WebformInterface; /** - * Provides the webform submission filter webform. + * Provides the webform submission filter form. */ class WebformSubmissionFilterForm extends FormBase { @@ -20,7 +21,7 @@ public function getFormId() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $search = NULL, $state = NULL, array $state_options = []) { + public function buildForm(array $form, FormStateInterface $form_state, $search = NULL, $state = NULL, array $state_options = [], $source_entity = NULL, $source_entity_options = []) { $form['#attributes'] = ['class' => ['webform-filter-form']]; $form['filter'] = [ '#type' => 'details', @@ -30,13 +31,36 @@ public function buildForm(array $form, FormStateInterface $form_state, $search = ]; $form['filter']['search'] = [ '#type' => 'search', - '#title' => $this->t('Filter by submitted data and/or notes'), + '#title' => $this->t('Keyword'), '#title_display' => 'invisible', '#placeholder' => $this->t('Filter by submitted data and/or notes'), '#maxlength' => 128, '#size' => 40, '#default_value' => $search, ]; + if ($source_entity_options) { + if ($source_entity_options instanceof WebformInterface) { + $form['filter']['entity'] = [ + '#type' => 'search', + '#title' => $this->t('Submitted to'), + '#title_display' => 'invisible', + '#autocomplete_route_name' => 'entity.webform.results.source_entity.autocomplete', + '#autocomplete_route_parameters' => ['webform' => $source_entity_options->id()], + '#placeholder' => $this->t('Enter submitted to…'), + '#size' => 20, + '#default_value' => $source_entity, + ]; + } + else { + $form['filter']['entity'] = [ + '#type' => 'select', + '#title' => $this->t('Submitted to'), + '#title_display' => 'invisible', + '#options' => ['' => $this->t('Filter by submitted to')] + $source_entity_options, + '#default_value' => $source_entity, + ]; + } + } $form['filter']['state'] = [ '#type' => 'select', '#title' => $this->t('State'), @@ -66,7 +90,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $query = [ 'search' => trim($form_state->getValue('search')), 'state' => trim($form_state->getValue('state')), + 'entity' => trim($form_state->getValue('entity')), ]; + $query = array_filter($query); + if (!empty($query['entity']) && preg_match('#\(([^)]+)\)#', $query['entity'], $match)) { + $query['entity'] = $match[1]; + } $form_state->setRedirect($this->getRouteMatch()->getRouteName(), $this->getRouteMatch()->getRawParameters()->all(), [ 'query' => $query , ]); diff --git a/web/modules/webform/src/Form/WebformSubmissionResendForm.php b/web/modules/webform/src/Form/WebformSubmissionResendForm.php index a48336f783..cd8de42f44 100644 --- a/web/modules/webform/src/Form/WebformSubmissionResendForm.php +++ b/web/modules/webform/src/Form/WebformSubmissionResendForm.php @@ -147,12 +147,12 @@ public function buildForm(array $form, FormStateInterface $form_state, WebformSu // Add submission navigation. $source_entity = $this->requestHandler->getCurrentSourceEntity('webform_submission'); $form['navigation'] = [ - '#theme' => 'webform_submission_navigation', + '#type' => 'webform_submission_navigation', '#webform_submission' => $webform_submission, '#weight' => -20, ]; $form['information'] = [ - '#theme' => 'webform_submission_information', + '#type' => 'webform_submission_information', '#webform_submission' => $webform_submission, '#source_entity' => $source_entity, '#weight' => -19, @@ -179,7 +179,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $t_args = [ '%label' => $message_handler->label(), ]; - drupal_set_message($this->t('Successfully re-sent %label.', $t_args)); + $this->messenger()->addStatus($this->t('Successfully re-sent %label.', $t_args)); } /** diff --git a/web/modules/webform/src/Form/WebformSubmissionsDeleteFormBase.php b/web/modules/webform/src/Form/WebformSubmissionsDeleteFormBase.php index 18b2aa175c..d1e36e81e4 100644 --- a/web/modules/webform/src/Form/WebformSubmissionsDeleteFormBase.php +++ b/web/modules/webform/src/Form/WebformSubmissionsDeleteFormBase.php @@ -4,7 +4,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\WebformInterface; use Drupal\webform\WebformRequestInterface; @@ -13,7 +12,7 @@ /** * Base webform for deleting webform submission. */ -abstract class WebformSubmissionsDeleteFormBase extends ConfirmFormBase { +abstract class WebformSubmissionsDeleteFormBase extends WebformDeleteFormBase { /** * Default number of submission to be deleted during batch processing. @@ -36,6 +35,13 @@ abstract class WebformSubmissionsDeleteFormBase extends ConfirmFormBase { */ protected $sourceEntity; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + /** * The webform submission storage. * @@ -59,8 +65,11 @@ abstract class WebformSubmissionsDeleteFormBase extends ConfirmFormBase { * The webform request handler. */ public function __construct(EntityTypeManagerInterface $entity_type_manager, WebformRequestInterface $request_handler) { + $this->entityTypeManager = $entity_type_manager; $this->submissionStorage = $entity_type_manager->getStorage('webform_submission'); $this->requestHandler = $request_handler; + + list($this->webform, $this->sourceEntity) = $this->requestHandler->getWebformEntities(); } /** @@ -80,14 +89,6 @@ public function getConfirmText() { return $this->t('Clear'); } - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - list($this->webform, $this->sourceEntity) = $this->requestHandler->getWebformEntities(); - return parent::buildForm($form, $form_state); - } - /** * {@inheritdoc} */ @@ -95,13 +96,31 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $form_state->setRedirectUrl($this->getCancelUrl()); if ($this->submissionStorage->getTotal($this->webform, $this->sourceEntity) < $this->getBatchLimit()) { $this->submissionStorage->deleteAll($this->webform, $this->sourceEntity); - drupal_set_message($this->getFinishedMessage()); + $this->messenger()->addStatus($this->getFinishedMessage()); } else { $this->batchSet($this->webform, $this->sourceEntity); } } + /** + * Get webform or source entity label. + * + * @return null|string + * Webform or source entity label. + */ + public function getLabel() { + if ($this->sourceEntity) { + return $this->sourceEntity->label(); + } + elseif ($this->webform->label()) { + return $this->webform->label(); + } + else { + return ''; + } + } + /** * Message to displayed after submissions are deleted. * @@ -112,6 +131,10 @@ public function getFinishedMessage() { return $this->t('Webform submissions cleared.'); } + /****************************************************************************/ + // Batch API. + /****************************************************************************/ + /** * Batch API; Initialize batch operations. * @@ -180,7 +203,7 @@ public function batchProcess(WebformInterface $webform = NULL, EntityInterface $ // Track progress. $context['sandbox']['progress'] += $this->submissionStorage->deleteAll($webform, $entity, $this->getBatchLimit(), $max_sid); - $context['message'] = $this->t('Deleting @count of @total submissions...', ['@count' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]); + $context['message'] = $this->t('Deleting @count of @total submissions…', ['@count' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']]); // Track finished. if ($context['sandbox']['progress'] != $context['sandbox']['max']) { @@ -200,10 +223,10 @@ public function batchProcess(WebformInterface $webform = NULL, EntityInterface $ */ public function batchFinish($success = FALSE, array $results, array $operations) { if (!$success) { - drupal_set_message($this->t('Finished with an error.')); + $this->messenger()->addStatus($this->t('Finished with an error.')); } else { - drupal_set_message($this->getFinishedMessage()); + $this->messenger()->addStatus($this->getFinishedMessage()); } } diff --git a/web/modules/webform/src/Form/WebformSubmissionsPurgeForm.php b/web/modules/webform/src/Form/WebformSubmissionsPurgeForm.php index 040c765044..36e4088191 100644 --- a/web/modules/webform/src/Form/WebformSubmissionsPurgeForm.php +++ b/web/modules/webform/src/Form/WebformSubmissionsPurgeForm.php @@ -21,38 +21,57 @@ public function getFormId() { * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to delete all submissions?'); + return $this->t('Purge all submissions?'); } /** * {@inheritdoc} */ - public function getConfirmText() { - return $this->t('Purge'); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - return new Url('entity.webform_submission.collection'); + public function buildForm(array $form, FormStateInterface $form_state) { + $submission_total = $this->entityTypeManager + ->getStorage('webform_submission') + ->getQuery() + ->count() + ->execute(); + if ($submission_total) { + return parent::buildForm($form, $form_state); + } + else { + $form['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'error', + '#message_message' => $this->t('There are no webform submissions.'), + ]; + return $form; + } } /** * {@inheritdoc} */ - public function getFinishedMessage() { - return $this->t('Webform submissions purged.'); + public function getWarning() { + return [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Are you sure you want to purge all submissions?') . '<br/>' . + '<strong>' . $this->t('This action cannot be undone.') . '</strong>', + ]; } /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); - - $submission_total = \Drupal::entityQuery('webform_submission')->count()->execute(); - $form_total = \Drupal::entityQuery('webform')->count()->execute(); + public function getDescription() { + $submission_total = $this->entityTypeManager + ->getStorage('webform_submission') + ->getQuery() + ->count() + ->execute(); + $form_total = $this->entityTypeManager + ->getStorage('webform') + ->getQuery() + ->count() + ->execute(); $t_args = [ '@submission_total' => $submission_total, @@ -61,14 +80,50 @@ public function buildForm(array $form, FormStateInterface $form_state) { '@forms' => $this->formatPlural($form_total, 'webform', 'webforms'), ]; - $form['confirm'] = [ + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove @submission_total @submissions in @form_total @forms', $t_args), + ['#markup' => '<em>' . $this->t('Take a few minutes to complete') . '</em>'], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function getConfirmInput() { + return [ '#type' => 'checkbox', - '#title' => $this->t('Are you sure you want to delete @submission_total @submissions in @form_total @forms?', $t_args), + '#title' => $this->t('Yes, I want to purge all submissions'), '#required' => TRUE, - '#weight' => -10, ]; + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Purge'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.webform_submission.collection'); + } - return $form; + /** + * {@inheritdoc} + */ + public function getFinishedMessage() { + return $this->t('Webform submissions purged.'); } } diff --git a/web/modules/webform/src/Plugin/Action/DeleteWebformSubmission.php b/web/modules/webform/src/Plugin/Action/DeleteWebformSubmission.php index d262a7233f..682839ad6b 100644 --- a/web/modules/webform/src/Plugin/Action/DeleteWebformSubmission.php +++ b/web/modules/webform/src/Plugin/Action/DeleteWebformSubmission.php @@ -5,7 +5,7 @@ use Drupal\Core\Action\ActionBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\user\PrivateTempStoreFactory; +use Drupal\Core\TempStore\PrivateTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -23,7 +23,7 @@ class DeleteWebformSubmission extends ActionBase implements ContainerFactoryPlug /** * The tempstore factory. * - * @var \Drupal\user\PrivateTempStoreFactory + * @var \Drupal\Core\TempStore\PrivateTempStoreFactory */ protected $tempStoreFactory; @@ -43,7 +43,7 @@ class DeleteWebformSubmission extends ActionBase implements ContainerFactoryPlug * The plugin ID for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. - * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory * The tempstore factory. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. @@ -63,7 +63,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('user.private_tempstore'), + $container->get('tempstore.private'), $container->get('current_user') ); } diff --git a/web/modules/webform/src/Plugin/Block/WebformBlock.php b/web/modules/webform/src/Plugin/Block/WebformBlock.php index 226b5aceeb..8a1282060f 100644 --- a/web/modules/webform/src/Plugin/Block/WebformBlock.php +++ b/web/modules/webform/src/Plugin/Block/WebformBlock.php @@ -2,13 +2,16 @@ namespace Drupal\webform\Plugin\Block; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Block\BlockBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\webform\WebformInterface; use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; /** * Provides a 'Webform' block. @@ -21,6 +24,13 @@ */ class WebformBlock extends BlockBase implements ContainerFactoryPluginInterface { + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + /** * Entity type manager. * @@ -44,13 +54,16 @@ class WebformBlock extends BlockBase implements ContainerFactoryPluginInterface * The plugin_id for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager service. + * The entity type manager. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, WebformTokenManagerInterface $token_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, WebformTokenManagerInterface $token_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->requestStack = $request_stack; $this->entityTypeManager = $entity_type_manager; $this->tokenManager = $token_manager; } @@ -63,6 +76,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, + $container->get('request_stack'), $container->get('entity_type.manager'), $container->get('webform.token_manager') ); @@ -75,6 +89,7 @@ public function defaultConfiguration() { return [ 'webform_id' => '', 'default_data' => '', + 'redirect' => FALSE, ]; } @@ -91,13 +106,37 @@ public function blockForm($form, FormStateInterface $form_state) { ]; $form['default_data'] = [ '#title' => $this->t('Default webform submission data (YAML)'), - '#description' => $this->t('Enter webform submission data as name and value pairs which will be used to prepopulate the selected webform. You may use tokens.'), '#type' => 'webform_codemirror', '#mode' => 'yaml', '#default_value' => $this->configuration['default_data'], + '#webform_element' => TRUE, + '#description' => [ + 'content' => ['#markup' => $this->t('Enter submission data as name and value pairs as <a href=":href">YAML</a> which will be used to prepopulate the selected webform.', [':href' => 'https://en.wikipedia.org/wiki/YAML']), '#suffix' => ' '], + 'token' => $this->tokenManager->buildTreeLink(), + ], + '#more_title' => $this->t('Example'), + '#more' => [ + '#theme' => 'webform_codemirror', + '#type' => 'yaml', + '#code' => "# This is an example of a comment. +element_key: 'some value' + +# The below example uses a token to get the current node's title. +# Add ':clear' to the end token to return an empty value when the token is missing. +title: '[webform_submission:node:title:clear]' +# The below example uses a token to get a field value from the current node. +full_name: '[webform_submission:node:field_full_name:clear]", + ], + ]; + $form['redirect'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Redirect to the webform'), + '#default_value' => $this->configuration['redirect'], + '#return_value' => TRUE, + '#description' => $this->t('If your webform has multiple pages, this will change the behavior of the "Next" button. This will also affect where validation messages show up after an error.'), ]; - $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['token_tree_link'] = $this->tokenManager->buildTreeElement(); $this->tokenManager->elementValidate($form); @@ -110,24 +149,46 @@ public function blockForm($form, FormStateInterface $form_state) { public function blockSubmit($form, FormStateInterface $form_state) { $this->configuration['webform_id'] = $form_state->getValue('webform_id'); $this->configuration['default_data'] = $form_state->getValue('default_data'); + $this->configuration['redirect'] = $form_state->getValue('redirect'); } /** * {@inheritdoc} */ public function build() { - return [ + $build = [ '#type' => 'webform', '#webform' => $this->getWebform(), '#default_data' => $this->configuration['default_data'], ]; + + // If redirect, set the #action property on the form. + if ($this->configuration['redirect']) { + $build['#action'] = $this->getWebform()->toUrl() + ->setOption('query', $this->requestStack->getCurrentRequest()->query->all()) + ->toString(); + } + + return $build; } /** * {@inheritdoc} */ protected function blockAccess(AccountInterface $account) { - return $this->getWebform()->access('submission_create', $account, TRUE); + $webform = $this->getWebform(); + if (!$webform) { + return AccessResult::forbidden(); + } + + $access_result = $webform->access('submission_create', $account, TRUE); + if ($access_result->isAllowed()) { + return $access_result; + } + + $has_access_denied_message = ($webform->getSetting('form_access_denied') !== WebformInterface::ACCESS_DENIED_DEFAULT); + return AccessResult::allowedIf($has_access_denied_message) + ->addCacheableDependency($access_result); } /** @@ -142,14 +203,6 @@ public function calculateDependencies() { return $dependencies; } - /** - * {@inheritdoc} - */ - public function getCacheMaxAge() { - // Caching strategy is handled by the webform. - return 0; - } - /** * Get this block instance webform. * diff --git a/web/modules/webform/src/Plugin/Block/WebformSubmissionLimitBlock.php b/web/modules/webform/src/Plugin/Block/WebformSubmissionLimitBlock.php index ca7153fc45..a1f3d15eef 100644 --- a/web/modules/webform/src/Plugin/Block/WebformSubmissionLimitBlock.php +++ b/web/modules/webform/src/Plugin/Block/WebformSubmissionLimitBlock.php @@ -141,7 +141,7 @@ public function blockForm($form, FormStateInterface $form_state) { '#description' => self::buildTokens($this->configuration['type'], $this->configuration['source_entity']), ]; - $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['token_tree_link'] = $this->tokenManager->buildTreeElement(); $form['progress_bar'] = [ '#title' => $this->t('Show progress bar'), diff --git a/web/modules/webform/src/Plugin/Condition/Webform.php b/web/modules/webform/src/Plugin/Condition/Webform.php index 20b06da85f..62b1514b06 100644 --- a/web/modules/webform/src/Plugin/Condition/Webform.php +++ b/web/modules/webform/src/Plugin/Condition/Webform.php @@ -90,9 +90,10 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#type' => 'select', '#options' => $options, '#multiple' => $options, + '#select2' => TRUE, '#default_value' => $this->configuration['webforms'], ]; - WebformElementHelper::enhanceSelect($form['webforms'], TRUE); + WebformElementHelper::process($form['webforms']); if (empty($this->configuration['context_mapping'])) { $form['message'] = [ @@ -130,7 +131,9 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { - $this->configuration['webforms'] = array_filter($form_state->getValue('webforms')); + $values = $form_state->getValue('webforms') ?: []; + $values = array_filter($values); + $this->configuration['webforms'] = array_combine($values, $values); parent::submitConfigurationForm($form, $form_state); } diff --git a/web/modules/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php b/web/modules/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php index 1ff1fb9f05..02015f0c22 100644 --- a/web/modules/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php +++ b/web/modules/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Serialization\Yaml; use Drupal\devel_generate\DevelGenerateBase; @@ -89,6 +90,13 @@ class WebformSubmissionDevelGenerate extends DevelGenerateBase implements Contai */ protected $webformEntityReferenceManager; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a WebformSubmissionDevelGenerate object. * @@ -99,25 +107,27 @@ class WebformSubmissionDevelGenerate extends DevelGenerateBase implements Contai * @param mixed $plugin_definition * The plugin implementation definition. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack - * The request stack service. + * The request stack. * @param \Drupal\Core\Database\Connection $database * The database. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. * @param \Drupal\webform\WebformSubmissionGenerateInterface $webform_submission_generate * The webform submission generator. * @param \Drupal\webform\WebformEntityReferenceManagerInterface $webform_entity_reference_manager * The webform entity reference manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, Connection $database, EntityTypeManagerInterface $entity_type_manager, WebformSubmissionGenerateInterface $webform_submission_generate, WebformEntityReferenceManagerInterface $webform_entity_reference_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, RequestStack $request_stack, Connection $database, EntityTypeManagerInterface $entity_type_manager, MessengerInterface $messenger, WebformSubmissionGenerateInterface $webform_submission_generate, WebformEntityReferenceManagerInterface $webform_entity_reference_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->request = $request_stack->getCurrentRequest(); $this->database = $database; $this->entityTypeManager = $entity_type_manager; + $this->messenger = $messenger; $this->webformSubmissionGenerate = $webform_submission_generate; $this->webformEntityReferenceManager = $webform_entity_reference_manager; - $this->webformStorage = $entity_type_manager->getStorage('webform'); $this->webformSubmissionStorage = $entity_type_manager->getStorage('webform_submission'); } @@ -127,10 +137,13 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( - $configuration, $plugin_id, $plugin_definition, + $configuration, + $plugin_id, + $plugin_definition, $container->get('request_stack'), $container->get('database'), $container->get('entity_type.manager'), + $container->get('messenger'), $container->get('webform_submission.generate'), $container->get('webform.entity_reference_manager') ); @@ -140,7 +153,11 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { - drupal_set_message($this->t('Please note that no emails will be sent while generating webform submissions.'), 'warning'); + $form['message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('Please note that no emails will be sent while generating webform submissions.'), + '#message_type' => 'warning', + ]; $options = []; foreach ($this->webformStorage->loadMultiple() as $webform) { @@ -197,7 +214,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { $form['submitted']['entity-type'] = [ '#type' => 'select', '#title' => $this->t('Entity type'), - '#title_display' => 'Invisible', + '#title_display' => 'invisible', '#empty_option' => $this->t('- None -'), '#options' => $entity_types, '#default_value' => $this->getSetting('entity-type'), @@ -205,7 +222,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { $form['submitted']['entity-id'] = [ '#type' => 'number', '#title' => $this->t('Entity id'), - '#title_display' => 'Invisible', + '#title_display' => 'invisible', '#default_value' => $this->getSetting('entity-id'), '#min' => 1, '#size' => 10, diff --git a/web/modules/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php b/web/modules/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php index 8b08e1043a..834af4e2ea 100644 --- a/web/modules/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php +++ b/web/modules/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php @@ -37,6 +37,8 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') $query = parent::buildEntityQuery($match, $match_operator); // Exclude templates. $query->condition('template', FALSE); + // Exclude archived. + $query->condition('archive', FALSE); return $query; } diff --git a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php index 862ac15f5c..c13056eb3c 100644 --- a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php +++ b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php @@ -2,9 +2,18 @@ namespace Drupal\webform\Plugin\Field\FieldFormatter; +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\webform\WebformMessageManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Plugin implementation of the 'Webform rendered entity' formatter. @@ -20,6 +29,83 @@ */ class WebformEntityReferenceEntityFormatter extends WebformEntityReferenceFormatterBase { + /** + * The route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The webform message manager. + * + * @var \Drupal\webform\WebformMessageManagerInterface + */ + protected $messageManager; + + /** + * WebformEntityReferenceEntityFormatter constructor. + * + * @param string $plugin_id + * The plugin_id for the formatter. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the formatter is associated. + * @param array $settings + * The formatter settings. + * @param string $label + * The formatter label display setting. + * @param string $view_mode + * The view mode. + * @param array $third_party_settings + * Third party settings. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\webform\WebformMessageManagerInterface $message_manager + * The webform message manager. + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, RendererInterface $renderer, ConfigFactoryInterface $config_factory, RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager, WebformMessageManagerInterface $message_manager) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $renderer, $config_factory); + + $this->routeMatch = $route_match; + $this->entityTypeManager = $entity_type_manager; + $this->messageManager = $message_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['view_mode'], + $configuration['third_party_settings'], + $container->get('renderer'), + $container->get('config.factory'), + $container->get('current_route_match'), + $container->get('entity_type.manager'), + $container->get('webform.message_manager') + ); + } + /** * {@inheritdoc} */ @@ -42,12 +128,20 @@ public function settingsSummary() { * {@inheritdoc} */ public function settingsForm(array $form, FormStateInterface $form_state) { - $entity_type_definition = \Drupal::entityTypeManager()->getDefinition( - $this->fieldDefinition->getTargetEntityTypeId()); + if ($this->fieldDefinition->getTargetEntityTypeId() === 'paragraph') { + $title = $this->t("Use this paragraph field's main entity as the webform submission's source entity."); + $description = $this->t("If unchecked, the current page's entity will be used as the webform submission's source entity."); + } + else { + $entity_type_definition = $this->entityTypeManager->getDefinition($this->fieldDefinition->getTargetEntityTypeId()); + $title = $this->t("Use this field's %entity_type entity as the webform submission's source entity.", ['%entity_type' => $entity_type_definition->getLabel()]); + $description = $this->t("If unchecked, the current page's entity will be used as the webform submission's source entity. For example, if this webform was displayed on a node's page, the current node would be used as the webform submission's source entity.", ['%entity_type' => $entity_type_definition->getLabel()]); + } + $form = parent::settingsForm($form, $form_state); $form['source_entity'] = [ - '#title' => $this->t("Use this field's %entity_type entity as the webform submission's source entity.", ['%entity_type' => $entity_type_definition->getLabel()]), - '#description' => $this->t("If unchecked, the current page's entity will be used as the webform submission's source entity. For example, if this webform was displayed on a node's page, the current node would be used as the webform submission's source entity.", ['%entity_type' => $entity_type_definition->getLabel()]), + '#title' => $title, + '#description' => $description, '#type' => 'checkbox', '#return_type' => TRUE, '#default_value' => $this->getSetting('source_entity'), @@ -59,20 +153,21 @@ public function settingsForm(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { + // Get source entity, which is the entity that the webform + // is directly attached to. For Paragraphs this would be the field's + // paragraph entity. $source_entity = $items->getEntity(); - $this->messageManager->setSourceEntity($source_entity); - // Determine if webform is previewed within a Paragraph on .edit_form. - $is_paragraph_edit_preview = ($source_entity->getEntityTypeId() === 'paragraph' && preg_match('/\.edit_form$/', \Drupal::routeMatch()->getRouteName())) ? TRUE : FALSE; + // Determine if webform is previewed within a Paragraph on + // node edit forms (via *.edit_form or .content_translation_add routes). + $route = $this->routeMatch->getRouteName(); + $is_node_edit = (preg_match('/\.edit_form$/', $route) || preg_match('/\.content_translation_add$/', $route)); + $is_paragraph = ($source_entity && $source_entity->getEntityTypeId() === 'paragraph'); + $is_paragraph_node_edit = ($is_paragraph && $is_node_edit); $elements = []; foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { - // Do not display the webform if the current user can't - // create submissions. - if ($entity->id() && !$entity->access('submission_create')) { - $elements[$delta] = []; - } - elseif ($is_paragraph_edit_preview) { + if ($is_paragraph_node_edit) { // Webform can not be nested within node edit form because the nested // <form> tags will cause unexpected validation issues. $elements[$delta] = [ @@ -82,21 +177,26 @@ public function viewElements(FieldItemListInterface $items, $langcode) { ]; } else { - $values = []; - if ($this->getSetting('source_entity')) { - $values += [ - 'entity_type' => $source_entity->getEntityTypeId(), - 'entity_id' => $source_entity->id(), - ]; - } - if (!empty($items[$delta]->default_data)) { - $values['data'] = Yaml::decode($items[$delta]->default_data); - } - $elements[$delta] = $entity->getSubmissionForm($values); + $elements[$delta] = [ + '#type' => 'webform', + '#webform' => $entity, + '#default_data' => (!empty($items[$delta]->default_data)) ? Yaml::decode($items[$delta]->default_data) : [], + '#entity' => ($this->getSetting('source_entity')) ? $source_entity : NULL, + ]; } $this->setCacheContext($elements[$delta], $entity, $items[$delta]); } return $elements; } + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity) { + // Always allow access so that the Webform element can determine if the + // Webform is accessible or an access denied message should be displayed. + // @see \Drupal\webform\Element\Webform + return AccessResult::allowed(); + } + } diff --git a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceFormatterBase.php b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceFormatterBase.php index 96fa8849c6..fbf6bd8600 100644 --- a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceFormatterBase.php +++ b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceFormatterBase.php @@ -2,13 +2,14 @@ namespace Drupal\webform\Plugin\Field\FieldFormatter; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\RendererInterface; use Drupal\webform\Plugin\Field\FieldType\WebformEntityReferenceItem; use Drupal\webform\WebformInterface; -use Drupal\webform\WebformMessageManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -17,14 +18,21 @@ abstract class WebformEntityReferenceFormatterBase extends EntityReferenceFormatterBase implements ContainerFactoryPluginInterface { /** - * The webform message manager. + * The renderer. * - * @var \Drupal\webform\WebformMessageManagerInterface + * @var \Drupal\Core\Render\RendererInterface */ - protected $messageManager; + protected $renderer; /** - * WebformEntityReferenceEntityFormatter constructor. + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * WebformEntityReferenceLinkFormatter constructor. * * @param string $plugin_id * The plugin_id for the formatter. @@ -40,13 +48,16 @@ abstract class WebformEntityReferenceFormatterBase extends EntityReferenceFormat * The view mode. * @param array $third_party_settings * Third party settings. - * @param \Drupal\webform\WebformMessageManagerInterface $message_manager - * The webform message manager. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, WebformMessageManagerInterface $message_manager) { + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, RendererInterface $renderer, ConfigFactoryInterface $config_factory) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); - $this->messageManager = $message_manager; + $this->configFactory = $config_factory; + $this->renderer = $renderer; } /** @@ -61,7 +72,8 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], - $container->get('webform.message_manager') + $container->get('renderer'), + $container->get('config.factory') ); } @@ -100,11 +112,11 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item */ protected function setCacheContext(array &$elements, WebformInterface $webform, WebformEntityReferenceItem $item) { // Track if webform.settings is updated. - $config = \Drupal::config('webform.settings'); - \Drupal::service('renderer')->addCacheableDependency($elements, $config); + $config = $this->configFactory->get('webform.settings'); + $this->renderer->addCacheableDependency($elements, $config); - // Track if the webfor is updated. - \Drupal::service('renderer')->addCacheableDependency($elements, $webform); + // Track if the webform is updated. + $this->renderer->addCacheableDependency($elements, $webform); // Calculate the max-age based on the open/close data/time for the item // and webform. @@ -115,7 +127,7 @@ protected function setCacheContext(array &$elements, WebformInterface $webform, $item_state = $item->$state; if ($item_state && strtotime($item_state) > time()) { $item_seconds = strtotime($item_state) - time(); - if (!$max_age || $item_seconds > $max_age) { + if (!$max_age && $item_seconds > $max_age) { $max_age = $item_seconds; } } @@ -124,7 +136,7 @@ protected function setCacheContext(array &$elements, WebformInterface $webform, $webform_state = $webform->get($state); if ($webform_state && strtotime($webform_state) > time()) { $webform_seconds = strtotime($webform_state) - time(); - if (!$max_age || $webform_seconds > $max_age) { + if (!$max_age && $webform_seconds > $max_age) { $max_age = $webform_seconds; } } diff --git a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php index 8f6b2dcaa1..7b4c316350 100644 --- a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php +++ b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php @@ -2,11 +2,14 @@ namespace Drupal\webform\Plugin\Field\FieldFormatter; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Utility\Token; +use Drupal\Core\Render\RendererInterface; +use Drupal\webform\Element\WebformMessage; use Drupal\webform\WebformMessageManagerInterface; +use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -23,6 +26,13 @@ */ class WebformEntityReferenceLinkFormatter extends WebformEntityReferenceFormatterBase { + /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + /** * The webform message manager. * @@ -31,11 +41,11 @@ class WebformEntityReferenceLinkFormatter extends WebformEntityReferenceFormatte protected $messageManager; /** - * The token service. + * The webform token manager. * - * @var \Drupal\Core\Utility\Token + * @var \Drupal\webform\WebformTokenManagerInterface */ - protected $token; + protected $tokenManager; /** * WebformEntityReferenceLinkFormatter constructor. @@ -54,15 +64,20 @@ class WebformEntityReferenceLinkFormatter extends WebformEntityReferenceFormatte * The view mode. * @param array $third_party_settings * Third party settings. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. * @param \Drupal\webform\WebformMessageManagerInterface $message_manager * The webform message manager. - * @param \Drupal\Core\Utility\Token $token - * The token service. + * @param \Drupal\webform\WebformTokenManagerInterface $token_manager + * The webform token manager. */ - public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, WebformMessageManagerInterface $message_manager, Token $token) { - parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $message_manager); + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, RendererInterface $renderer, ConfigFactoryInterface $config_factory, WebformMessageManagerInterface $message_manager, WebformTokenManagerInterface $token_manager) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $renderer, $config_factory); - $this->token = $token; + $this->messageManager = $message_manager; + $this->tokenManager = $token_manager; } /** @@ -77,8 +92,10 @@ public static function create(ContainerInterface $container, array $configuratio $configuration['label'], $configuration['view_mode'], $configuration['third_party_settings'], + $container->get('renderer'), + $container->get('config.factory'), $container->get('webform.message_manager'), - $container->get('token') + $container->get('webform.token_manager') ); } @@ -88,6 +105,8 @@ public static function create(ContainerInterface $container, array $configuratio public static function defaultSettings() { return [ 'label' => 'Go to [webform:title] webform', + 'dialog' => '', + 'attributes' => [], ] + parent::defaultSettings(); } @@ -97,6 +116,10 @@ public static function defaultSettings() { public function settingsSummary() { $summary = parent::settingsSummary(); $summary[] = $this->t('Label: @label', ['@label' => $this->getSetting('label')]); + $dialog_option_name = $this->getSetting('dialog'); + if ($dialog_option = $this->configFactory->get('webform.settings')->get('settings.dialog_options.' . $dialog_option_name)) { + $summary[] = $this->t('Dialog: @dialog', ['@dialog' => (isset($dialog_option['title']) ? $dialog_option['title'] : $dialog_option_name)]); + } return $summary; } @@ -105,12 +128,43 @@ public function settingsSummary() { */ public function settingsForm(array $form, FormStateInterface $form_state) { $form = parent::settingsForm($form, $form_state); + + if ($this->fieldDefinition->getTargetEntityTypeId() === 'paragraph') { + $form['message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t("This paragraph field's main entity will be used as the webform submission's source entity."), + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } + $form['label'] = [ '#title' => $this->t('Label'), '#type' => 'textfield', '#default_value' => $this->getSetting('label'), '#required' => TRUE, ]; + + $dialog_options = $this->configFactory->get('webform.settings')->get('settings.dialog_options'); + if ($dialog_options) { + $options = []; + foreach ($dialog_options as $dialog_option_name => $dialog_option) { + $options[$dialog_option_name] = (isset($dialog_option['title'])) ? $dialog_option['title'] : $dialog_option_name; + } + $form['dialog'] = [ + '#title' => $this->t('Dialog'), + '#type' => 'select', + '#empty_option' => t('- Select dialog -'), + '#default_value' => $this->getSetting('dialog'), + '#options' => $options, + ]; + $form['attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Link'), + '#default_value' => $this->getSetting('attributes'), + ]; + } return $form; } @@ -138,13 +192,23 @@ public function viewElements(FieldItemListInterface $items, $langcode) { 'source_entity_id' => $source_entity->id(), ], ]; - $elements[$delta] = [ + $link = [ '#type' => 'link', - '#title' => $this->t($this->token->replace($this->getSetting('label'), [ - 'webform' => $entity, - ])), + '#title' => ['#markup' => $this->tokenManager->replace($this->getSetting('label'), $entity)], '#url' => $entity->toUrl('canonical', $link_options), + '#attributes' => $this->getSetting('attributes') ?: [], ]; + if ($dialog = $this->getSetting('dialog')) { + $link['#attributes']['class'][] = 'webform-dialog'; + $link['#attributes']['class'][] = 'webform-dialog-' . $dialog; + // Attach webform dialog library and options if they are not + // on every page. + if (!\Drupal::config('webform.settings')->get('settings.dialog')) { + $link['#attached']['library'][] = 'webform/webform.dialog'; + $link['#attached']['drupalSettings']['webform']['dialog']['options'] = \Drupal::config('webform.settings')->get('settings.dialog_options'); + } + } + $elements[$delta] = $link; } else { $this->messageManager->setWebform($entity); diff --git a/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceUrlFormatter.php b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceUrlFormatter.php new file mode 100644 index 0000000000..cfaf261321 --- /dev/null +++ b/web/modules/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceUrlFormatter.php @@ -0,0 +1,52 @@ +<?php + +namespace Drupal\webform\Plugin\Field\FieldFormatter; + +use Drupal\Core\Field\FieldItemListInterface; + +/** + * Plugin implementation of the 'Webform url' formatter. + * + * @FieldFormatter( + * id = "webform_entity_reference_url", + * label = @Translation("URL"), + * description = @Translation("Display URL to the referenced webform."), + * field_types = { + * "webform" + * } + * ) + */ +class WebformEntityReferenceUrlFormatter extends WebformEntityReferenceFormatterBase { + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $source_entity = $items->getEntity(); + + $elements = []; + + /** @var \Drupal\webform\WebformInterface[] $entities */ + $entities = $this->getEntitiesToView($items, $langcode); + + foreach ($entities as $delta => $entity) { + $link_options = [ + 'query' => [ + 'source_entity_type' => $source_entity->getEntityTypeId(), + 'source_entity_id' => $source_entity->id(), + ], + ]; + + $link = [ + '#plain_text' => $entity->toUrl('canonical', $link_options)->toString(), + ]; + + $elements[$delta] = $link; + + $this->setCacheContext($elements[$delta], $entity, $items[$delta]); + } + + return $elements; + } + +} diff --git a/web/modules/webform/src/Plugin/Field/FieldType/WebformEntityReferenceItem.php b/web/modules/webform/src/Plugin/Field/FieldType/WebformEntityReferenceItem.php index 13125bdb36..cafeb981f5 100644 --- a/web/modules/webform/src/Plugin/Field/FieldType/WebformEntityReferenceItem.php +++ b/web/modules/webform/src/Plugin/Field/FieldType/WebformEntityReferenceItem.php @@ -6,8 +6,8 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\DataDefinition; -use Drupal\webform\WebformInterface; /** * Defines the 'webform_entity_reference' entity field type. @@ -35,18 +35,6 @@ public static function defaultStorageSettings() { ] + parent::defaultStorageSettings(); } - /** - * {@inheritdoc} - */ - public static function defaultFieldSettings() { - return [ - 'default_data' => '', - 'status' => WebformInterface::STATUS_OPEN, - 'open' => '', - 'close' => '', - ] + parent::defaultFieldSettings(); - } - /** * {@inheritdoc} */ @@ -120,4 +108,13 @@ public static function getPreconfiguredOptions() { return []; } + /** + * {@inheritdoc} + */ + public function getSettableOptions(AccountInterface $account = NULL) { + /** @var \Drupal\webform\WebformEntityStorageInterface $webform_storage */ + $webform_storage = \Drupal::service('entity_type.manager')->getStorage('webform'); + return $webform_storage->getOptions(FALSE); + } + } diff --git a/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php index 3662fd1674..a13cff27e8 100644 --- a/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php +++ b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php @@ -2,12 +2,9 @@ namespace Drupal\webform\Plugin\Field\FieldWidget; -use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget; use Drupal\Core\Form\FormStateInterface; -use Drupal\webform\Utility\WebformDateHelper; -use Drupal\webform\WebformInterface; /** * Plugin implementation of the 'webform_entity_reference_autocomplete' widget. @@ -23,204 +20,32 @@ */ class WebformEntityReferenceAutocompleteWidget extends EntityReferenceAutocompleteWidget { - /** - * {@inheritdoc} - */ - public static function defaultSettings() { - return [ - 'default_data' => TRUE, - ] + parent::defaultSettings(); - } - - /** - * {@inheritdoc} - */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $element = parent::settingsForm($form, $form_state); - $element['default_data'] = [ - '#type' => 'checkbox', - '#title' => t('Enable default submission data (YAML)'), - '#description' => t('If checked, site builders will be able to define default submission data (YAML)'), - '#default_value' => $this->getSetting('default_data'), - ]; - return $element; - } + use WebformEntityReferenceWidgetTrait; /** * {@inheritdoc} */ - public function settingsSummary() { - $summary = parent::settingsSummary(); - $summary[] = t('Default submission data: @default_data', ['@default_data' => $this->getSetting('default_data') ? $this->t('Yes') : $this->t('No')]); - return $summary; - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - if (!isset($items[$delta]->status)) { - $items[$delta]->status = WebformInterface::STATUS_OPEN; - } - - $element = parent::formElement($items, $delta, $element, $form, $form_state); + public function getTargetIdElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + // Get default value. + $referenced_entities = $items->referencedEntities(); + $default_value = isset($referenced_entities[$delta]) ? $referenced_entities[$delta] : NULL; - // Get field name. - $field_name = $items->getName(); + // Append the match operation to the selection settings. + $selection_settings = $this->getFieldSetting('handler_settings') + ['match_operator' => $this->getSetting('match_operator')]; - // Get field input name from field parents, field name, and the delta. - $field_parents = array_merge($element['target_id']['#field_parents'], [$field_name, $delta]); - $field_input_name = (array_shift($field_parents)) . ('[' . implode('][', $field_parents) . ']'); - - // Set element 'target_id' default properties. - $element['target_id'] += [ - '#weight' => 0, - ]; - - // Get weight. - $weight = $element['target_id']['#weight']; - - $element['settings'] = [ - '#type' => 'details', - '#title' => $this->t('@title settings', ['@title' => $element['target_id']['#title']]), - '#element_validate' => [[$this, 'validateOpenClose']], - '#open' => ($items[$delta]->target_id) ? TRUE : FALSE, - '#weight' => $weight++, - ]; - - $element['settings']['status'] = [ - '#type' => 'radios', - '#title' => $this->t('Status'), - '#options' => [ - WebformInterface::STATUS_OPEN => $this->t('Open'), - WebformInterface::STATUS_CLOSED => $this->t('Closed'), - WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled'), - ], - '#options_display' => 'side_by_side', - '#default_value' => $items[$delta]->status, - ]; - - $element['settings']['scheduled'] = [ - '#type' => 'item', - '#title' => $element['target_id']['#title'], - '#title_display' => 'invisible', - '#input' => FALSE, - '#states' => [ - 'visible' => [ - 'input[name="' . $field_input_name . '[settings][status]"]' => ['value' => WebformInterface::STATUS_SCHEDULED], - ], - ], - ]; - $element['settings']['scheduled']['open'] = [ - '#type' => 'datetime', - '#title' => $this->t('Open'), - '#default_value' => $items[$delta]->open ? DrupalDateTime::createFromTimestamp(strtotime($items[$delta]->open)) : NULL, - '#prefix' => '<div class="container-inline form-item">', - '#suffix' => '</div>', - '#help' => FALSE, - '#description' => [ - '#type' => 'webform_help', - '#help' => $this->t('If the open date/time is left blank, this form will immediately be opened.'), - ], - ]; - $element['settings']['scheduled']['close'] = [ - '#type' => 'datetime', - '#title' => $this->t('Close'), - '#default_value' => $items[$delta]->close ? DrupalDateTime::createFromTimestamp(strtotime($items[$delta]->close)) : NULL, - '#prefix' => '<div class="container-inline form-item">', - '#suffix' => '</div>', - '#help' => FALSE, - '#description' => [ - '#type' => 'webform_help', - '#help' => $this->t('If the close date/time is left blank, this webform will never be closed.'), - ], + return [ + '#type' => 'entity_autocomplete', + '#target_type' => $this->getFieldSetting('target_type'), + '#selection_handler' => $this->getFieldSetting('handler'), + '#selection_settings' => $selection_settings, + // Entity reference field items are handling validation themselves via + // the 'ValidReference' constraint. + '#validate_reference' => FALSE, + '#maxlength' => 1024, + '#default_value' => $default_value, + '#size' => $this->getSetting('size'), + '#placeholder' => $this->getSetting('placeholder'), ]; - - if ($this->getSetting('default_data')) { - $element['settings']['default_data'] = [ - '#type' => 'webform_codemirror', - '#mode' => 'yaml', - '#title' => $this->t('Default submission data (YAML)'), - '#description' => $this->t('Enter submission data as name and value pairs which will be used to prepopulate the selected webform. You may use tokens.'), - '#default_value' => $items[$delta]->default_data, - ]; - /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ - $token_manager = \Drupal::service('webform.token_manager'); - $element['settings']['token_tree_link'] = $token_manager->buildTreeLink(); - } - - return $element; - } - - /** - * {@inheritdoc} - */ - public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { - parent::massageFormValues($values, $form, $form_state); - - // Massage open/close dates. - // @see \Drupal\webform\WebformEntitySettingsForm::save - // @see \Drupal\datetime\Plugin\Field\FieldWidget\DateTimeWidgetBase::massageFormValues - foreach ($values as &$item) { - $item += $item['settings']; - unset($item['settings']); - - if ($item['status'] === WebformInterface::STATUS_SCHEDULED) { - $states = ['open', 'close']; - foreach ($states as $state) { - if (!empty($item['scheduled'][$state]) && $item['scheduled'][$state] instanceof DrupalDateTime) { - $item[$state] = WebformDateHelper::formatStorage($item['scheduled'][$state]); - } - else { - $item[$state] = ''; - } - } - } - else { - $item['open'] = ''; - $item['close'] = ''; - } - unset($item['scheduled']); - } - return $values; - } - - /** - * Validate callback to ensure that the open date <= the close date. - * - * @param array $element - * An associative array containing the properties and children of the - * generic form element. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * @param array $complete_form - * The complete form structure. - * - * @see \Drupal\webform\WebformEntitySettingsForm::validateForm - * @see \Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeWidgetBase::validateOpenClose - */ - public function validateOpenClose(array &$element, FormStateInterface $form_state, array &$complete_form) { - $status = $element['status']['#value']; - if ($status === WebformInterface::STATUS_SCHEDULED) { - $open_date = $element['scheduled']['open']['#value']['object']; - $close_date = $element['scheduled']['close']['#value']['object']; - - // Require open or close dates. - if (empty($open_date) && empty($close_date)) { - $form_state->setError($element['scheduled']['open'], $this->t('Please enter an open or close date')); - $form_state->setError($element['scheduled']['close'], $this->t('Please enter an open or close date')); - } - - // Make sure open date is not after close date. - if ($open_date instanceof DrupalDateTime && $close_date instanceof DrupalDateTime) { - if ($open_date->getTimestamp() !== $close_date->getTimestamp()) { - $interval = $open_date->diff($close_date); - if ($interval->invert === 1) { - $form_state->setError($element['scheduled']['open'], $this->t('The @title close date cannot be before the open date', ['@title' => $element['#title']])); - } - } - } - } } } diff --git a/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php index 9741db399f..83fd6a4068 100644 --- a/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php +++ b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php @@ -2,9 +2,12 @@ namespace Drupal\webform\Plugin\Field\FieldWidget; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Entity\Webform; use Drupal\webform\WebformInterface; +use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsWidgetBase; /** * Plugin implementation of the 'webform_entity_reference_select' widget. @@ -18,76 +21,50 @@ * } * ) * - * @see \Drupal\webform\Plugin\Field\FieldWidget\WebformEntityReferenceAutocompleteWidget - * @see \Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget - * @see \Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget + * @see \Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsWidgetBase */ -class WebformEntityReferenceSelectWidget extends WebformEntityReferenceAutocompleteWidget { +class WebformEntityReferenceSelectWidget extends OptionsWidgetBase { - /** - * {@inheritdoc} - */ - public static function defaultSettings() { - return [ - 'default_data' => TRUE, - ]; - } + use WebformEntityReferenceWidgetTrait; /** * {@inheritdoc} */ - public function settingsForm(array $form, FormStateInterface $form_state) { - $element = []; - $element['default_data'] = [ - '#type' => 'checkbox', - '#title' => t('Enable default submission data (YAML)'), - '#description' => t('If checked, site builders will be able to define default submission data (YAML)'), - '#default_value' => $this->getSetting('default_data'), - ]; - return $element; - } - - /** - * {@inheritdoc} - */ - public function settingsSummary() { - $summary = []; - $summary[] = t('Default submission data: @default_data', ['@default_data' => $this->getSetting('default_data') ? $this->t('Yes') : $this->t('No')]); - return $summary; - } - - /** - * {@inheritdoc} - */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - $element = parent::formElement($items, $delta, $element, $form, $form_state); + public function getTargetIdElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + // Get default value (webform ID). + $referenced_entities = $items->referencedEntities(); + $default_value = isset($referenced_entities[$delta]) ? $referenced_entities[$delta] : NULL; + // Convert default_value's Webform to a simple entity_id. + if ($default_value instanceof WebformInterface) { + $default_value = $default_value->id(); + } - // Convert 'entity_autocomplete' to 'webform_entity_select' element. - $element['target_id']['#type'] = 'webform_entity_select'; + // Get options grouped by category. + $options = $this->getOptions($items->getEntity()); + // Make sure if an archived webform is the #default_value always include + // it as an option. + if ($default_value && $webform = Webform::load($default_value)) { + if ($webform->isArchived()) { + $options[(string) t('Archived')][$webform->id()] = $webform->label(); + } + } - /** @var \Drupal\webform\WebformEntityStorageInterface $webform_storage */ - $webform_storage = \Drupal::service('entity_type.manager')->getStorage('webform'); - $element['target_id']['#options'] = $webform_storage->getOptions(FALSE); + $target_element = [ + '#type' => 'webform_entity_select', + '#options' => $options, + '#default_value' => $default_value, + ]; // Set empty option. if (empty($element['#required'])) { - $element['target_id']['#empty_option'] = $this->t('- Select -'); - $element['target_id']['#empty_value'] = ''; - } - - // Convert default_value's Webform to a simple entity_id. - if (!empty($element['target_id']['#default_value']) && $element['target_id']['#default_value'] instanceof WebformInterface) { - $element['target_id']['#default_value'] = $element['target_id']['#default_value']->id(); + $target_element['#empty_option'] = $this->t('- Select -'); + $target_element['#empty_value'] = ''; } - // Remove properties that are not applicable. - unset($element['target_id']['#size']); - unset($element['target_id']['#maxlength']); - unset($element['target_id']['#placeholder']); - - $element['#element_validate'] = [[get_class($this), 'validateWebformEntityReferenceSelectWidget']]; + // Set validation callback. + $target_element['#element_validate'] = [[get_class($this), 'validateWebformEntityReferenceSelectWidget']]; - return $element; + return $target_element; } /** @@ -97,8 +74,41 @@ public static function validateWebformEntityReferenceSelectWidget(&$element, For // Below prevents the below error. // Fatal error: Call to a member function uuid() on a non-object in // core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php. - $value = (!empty($element['target_id']['#value'])) ? $element['target_id']['#value'] : NULL; - $form_state->setValueForElement($element['target_id'], $value); + $value = (!empty($element['#value'])) ? $element['#value'] : NULL; + $form_state->setValueForElement($element, $value); + } + + /** + * Returns the array of options for the widget. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The entity for which to return options. + * + * @return array + * The array of options for the widget. + */ + protected function getOptions(FieldableEntityInterface $entity) { + if (!isset($this->options)) { + // Limit the settable options for the current user account. + // Note: All active webforms are returned and grouped by category. + // @see \Drupal\webform\Plugin\Field\FieldType\WebformEntityReferenceItem::getSettableOptions + // @see \Drupal\webform\WebformEntityStorageInterface::getOptions + $options = $this->fieldDefinition + ->getFieldStorageDefinition() + ->getOptionsProvider($this->column, $entity) + ->getSettableOptions(\Drupal::currentUser()); + + $module_handler = \Drupal::moduleHandler(); + $context = [ + 'fieldDefinition' => $this->fieldDefinition, + 'entity' => $entity, + ]; + $module_handler->alter('options_list', $options, $context); + + array_walk_recursive($options, [$this, 'sanitizeLabel']); + $this->options = $options; + } + return $this->options; } } diff --git a/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceWidgetTrait.php b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceWidgetTrait.php new file mode 100644 index 0000000000..7c9d5fc32a --- /dev/null +++ b/web/modules/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceWidgetTrait.php @@ -0,0 +1,271 @@ +<?php + +namespace Drupal\webform\Plugin\Field\FieldWidget; + +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformDateHelper; +use Drupal\webform\WebformInterface; + +/** + * Trait for webform entity reference and autocomplete widget. + */ +trait WebformEntityReferenceWidgetTrait { + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + 'default_data' => TRUE, + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $element = parent::settingsForm($form, $form_state); + $element['default_data'] = [ + '#type' => 'checkbox', + '#title' => t('Enable default submission data (YAML)'), + '#description' => t('If checked, site builders will be able to define default submission data (YAML)'), + '#default_value' => $this->getSetting('default_data'), + ]; + return $element; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + $summary[] = t('Default submission data: @default_data', ['@default_data' => $this->getSetting('default_data') ? $this->t('Yes') : $this->t('No')]); + return $summary; + } + + /** + * Returns the target id element form for a single webform field widget. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * Array of default values for this field. + * @param int $delta + * The order of this item in the array of sub-elements (0, 1, 2, etc.). + * @param array $element + * A form element array containing basic properties for the widget. + * @param array $form + * The form structure where widgets are being attached to. This might be a + * full form structure, or a sub-element of a larger form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form elements for a single widget for this field. + */ + abstract protected function getTargetIdElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state); + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + // Set item default status to open. + if (!isset($items[$delta]->status)) { + $items[$delta]->status = WebformInterface::STATUS_OPEN; + } + + // Get field name. + $field_name = $items->getName(); + + // Get field input name from field parents, field name, and the delta. + $field_parents = array_merge($element['#field_parents'], [$field_name, $delta]); + $field_input_name = (array_shift($field_parents)) . ('[' . implode('][', $field_parents) . ']'); + + // Get target ID element. + $target_id_element = $this->getTargetIdElement($items, $delta, $element, $form, $form_state); + + // Determine if this is a paragraph. + $is_paragraph = ($items->getEntity()->getEntityTypeId() === 'paragraph'); + + // Merge target ID and default element and set default #weight. + // @see \Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget::formElement + $element = [ + 'target_id' => $target_id_element + $element + ['#weight' => 0], + ]; + + // Get weight. + $weight = $element['target_id']['#weight']; + + $element['settings'] = [ + '#type' => 'details', + '#title' => $this->t('@title settings', ['@title' => $element['target_id']['#title']]), + '#element_validate' => [[$this, 'validateOpenClose']], + '#open' => ($items[$delta]->target_id) ? TRUE : FALSE, + '#weight' => $weight++, + ]; + + $element['settings']['status'] = [ + '#type' => 'radios', + '#title' => $this->t('Status'), + '#options' => [ + WebformInterface::STATUS_OPEN => $this->t('Open'), + WebformInterface::STATUS_CLOSED => $this->t('Closed'), + WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled'), + ], + '#options_display' => 'side_by_side', + '#default_value' => $items[$delta]->status, + ]; + + $element['settings']['scheduled'] = [ + '#type' => 'item', + '#title' => $element['target_id']['#title'], + '#title_display' => 'invisible', + '#input' => FALSE, + '#states' => [ + 'visible' => [ + 'input[name="' . $field_input_name . '[settings][status]"]' => ['value' => WebformInterface::STATUS_SCHEDULED], + ], + ], + ]; + $element['settings']['scheduled']['open'] = [ + '#type' => 'datetime', + '#title' => $this->t('Open'), + '#default_value' => $items[$delta]->open ? DrupalDateTime::createFromTimestamp(strtotime($items[$delta]->open)) : NULL, + '#prefix' => '<div class="container-inline form-item">', + '#suffix' => '</div>', + '#help' => FALSE, + '#description' => [ + '#type' => 'webform_help', + '#help' => $this->t('If the open date/time is left blank, this form will immediately be opened.'), + '#help_title' => $this->t('Open'), + ], + ]; + $element['settings']['scheduled']['close'] = [ + '#type' => 'datetime', + '#title' => $this->t('Close'), + '#default_value' => $items[$delta]->close ? DrupalDateTime::createFromTimestamp(strtotime($items[$delta]->close)) : NULL, + '#prefix' => '<div class="container-inline form-item">', + '#suffix' => '</div>', + '#help' => FALSE, + '#description' => [ + '#type' => 'webform_help', + '#help' => $this->t('If the close date/time is left blank, this webform will never be closed.'), + '#help_title' => $this->t('Close'), + ], + ]; + + if ($this->getSetting('default_data')) { + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $token_types = ['webform', 'webform_submission']; + + $default_data_example = "# This is an example of a comment. +element_key: 'some value' + +# The below example uses a token to get the current node's title. +# Add ':clear' to the end token to return an empty value when the token is missing. +title: '[webform_submission:node:title:clear]' +# The below example uses a token to get a field value from the current node. +full_name: '[webform_submission:node:field_full_name:clear]"; + if ($is_paragraph) { + $token_types[] = 'paragraph'; + $default_data_example .= PHP_EOL . "# You can also use paragraphs tokens. +some_value: '[paragraph:some_value:clear]"; + } + $element['settings']['default_data'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Default submission data (YAML)'), + '#placeholder' => $this->t("Enter 'name': 'value' pairs…"), + '#default_value' => $items[$delta]->default_data, + '#webform_element' => TRUE, + '#description' => [ + 'content' => ['#markup' => $this->t('Enter submission data as name and value pairs as <a href=":href">YAML</a> which will be used to prepopulate the selected webform.', [':href' => 'https://en.wikipedia.org/wiki/YAML']), '#suffix' => ' '], + 'token' => $token_manager->buildTreeLink($token_types), + ], + '#more_title' => $this->t('Example'), + '#more' => [ + '#theme' => 'webform_codemirror', + '#type' => 'yaml', + '#code' => $default_data_example, + ], + ]; + $element['settings']['token_tree_link'] = $token_manager->buildTreeElement($token_types); + $token_manager->elementValidate($element['settings']['default_data'], $token_types); + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + parent::massageFormValues($values, $form, $form_state); + + // Massage open/close dates. + // @see \Drupal\webform\WebformEntitySettingsForm::save + // @see \Drupal\datetime\Plugin\Field\FieldWidget\DateTimeWidgetBase::massageFormValues + foreach ($values as &$item) { + $item += $item['settings']; + unset($item['settings']); + + if ($item['status'] === WebformInterface::STATUS_SCHEDULED) { + $states = ['open', 'close']; + foreach ($states as $state) { + if (!empty($item['scheduled'][$state]) && $item['scheduled'][$state] instanceof DrupalDateTime) { + $item[$state] = WebformDateHelper::formatStorage($item['scheduled'][$state]); + } + else { + $item[$state] = ''; + } + } + } + else { + $item['open'] = ''; + $item['close'] = ''; + } + unset($item['scheduled']); + } + return $values; + } + + /** + * Validate callback to ensure that the open date <= the close date. + * + * @param array $element + * An associative array containing the properties and children of the + * generic form element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @see \Drupal\webform\WebformEntitySettingsForm::validateForm + * @see \Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeWidgetBase::validateOpenClose + */ + public function validateOpenClose(array &$element, FormStateInterface $form_state, array &$complete_form) { + $status = $element['status']['#value']; + if ($status === WebformInterface::STATUS_SCHEDULED) { + $open_date = $element['scheduled']['open']['#value']['object']; + $close_date = $element['scheduled']['close']['#value']['object']; + + // Require open or close dates. + if (empty($open_date) && empty($close_date)) { + $form_state->setError($element['scheduled']['open'], $this->t('Please enter an open or close date')); + $form_state->setError($element['scheduled']['close'], $this->t('Please enter an open or close date')); + } + + // Make sure open date is not after close date. + if ($open_date instanceof DrupalDateTime && $close_date instanceof DrupalDateTime) { + if ($open_date->getTimestamp() !== $close_date->getTimestamp()) { + $interval = $open_date->diff($close_date); + if ($interval->invert === 1) { + $form_state->setError($element['scheduled']['open'], $this->t('The @title close date cannot be before the open date', ['@title' => $element['#title']])); + } + } + } + } + } + +} diff --git a/web/modules/webform/src/Plugin/Mail/WebformPhpMail.php b/web/modules/webform/src/Plugin/Mail/WebformPhpMail.php index 53ce535325..7fc6ec5469 100644 --- a/web/modules/webform/src/Plugin/Mail/WebformPhpMail.php +++ b/web/modules/webform/src/Plugin/Mail/WebformPhpMail.php @@ -24,6 +24,19 @@ public function format(array $message) { $message['body'] = implode("\n\n", $message['body']); if (!empty($message['params']['html'])) { + // Wrap body in HTML template if the <html> tag is missing. + if (strpos($message['body'], '<html') === FALSE) { + // Make sure parameters exist. + $message['params'] += ['webform_submission' => NULL, 'handler' => NULL]; + $build = [ + '#theme' => 'webform_email_html', + '#body' => $message['body'], + '#subject' => $message['subject'], + '#webform_submission' => $message['params']['webform_submission'], + '#handler' => $message['params']['handler'], + ]; + $message['body'] = \Drupal::service('renderer')->renderPlain($build); + } return $message; } else { diff --git a/web/modules/webform/src/Plugin/Menu/LocalAction/WebformDialogLocalAction.php b/web/modules/webform/src/Plugin/Menu/LocalAction/WebformDialogLocalAction.php new file mode 100644 index 0000000000..9ddcda0712 --- /dev/null +++ b/web/modules/webform/src/Plugin/Menu/LocalAction/WebformDialogLocalAction.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\webform\Plugin\Menu\LocalAction; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Menu\LocalActionDefault; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\webform\Utility\WebformDialogHelper; + +/** + * Defines a local action plugin with the needed dialog attributes. + */ +class WebformDialogLocalAction extends LocalActionDefault { + + /** + * {@inheritdoc} + */ + public function getOptions(RouteMatchInterface $route_match) { + $options = parent::getOptions($route_match); + + if (isset($this->pluginDefinition['dialog'])) { + $attributes = WebformDialogHelper::getModalDialogAttributes($this->pluginDefinition['dialog']); + } + elseif (isset($this->pluginDefinition['off_canvas'])) { + $attributes = WebformDialogHelper::getOffCanvasDialogAttributes($this->pluginDefinition['off_canvas']); + } + else { + $attributes = []; + } + + $options['attributes'] = (isset($this->pluginDefinition['attributes'])) ? $this->pluginDefinition['attributes'] : []; + $options['attributes'] = NestedArray::mergeDeep($options['attributes'], $attributes); + + return $options; + } + +} diff --git a/web/modules/webform/src/Plugin/WebformElement/Address.php b/web/modules/webform/src/Plugin/WebformElement/Address.php new file mode 100644 index 0000000000..245eaa86e0 --- /dev/null +++ b/web/modules/webform/src/Plugin/WebformElement/Address.php @@ -0,0 +1,429 @@ +<?php + +namespace Drupal\webform\Plugin\WebformElement; + +use CommerceGuys\Addressing\AddressFormat\FieldOverride; +use Drupal\address\FieldHelper; +use Drupal\address\LabelHelper; +use Drupal\Component\Utility\Html; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Mail\MailFormatHelper; +use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Provides a 'address' element. + * + * @WebformElement( + * id = "address", + * label = @Translation("Advanced address"), + * description = @Translation("Provides advanced element for storing, validating and displaying international postal addresses."), + * category = @Translation("Composite elements"), + * composite = TRUE, + * multiline = TRUE, + * states_wrapper = TRUE, + * dependencies = { + * "address", + * } + * ) + * + * @see \Drupal\address\Element\Address + */ +class Address extends WebformCompositeBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + $properties = [ + // Element settings. + 'title' => '', + 'default_value' => [], + // Description/Help. + 'help' => '', + 'help_title' => '', + 'description' => '', + 'more' => '', + 'more_title' => '', + // Form display. + 'title_display' => 'invisible', + 'description_display' => '', + // Form validation. + 'required' => FALSE, + // Submission display. + 'format' => $this->getItemDefaultFormat(), + 'format_html' => '', + 'format_text' => '', + 'format_items' => $this->getItemsDefaultFormat(), + 'format_items_html' => '', + 'format_items_text' => '', + // Address settings. + 'available_countries' => [], + 'field_overrides' => [], + 'langcode_override' => '', + ] + $this->getDefaultBaseProperties() + $this->getDefaultMultipleProperties(); + unset($properties['multiple__header']); + return $properties; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepare($element, $webform_submission); + + $element['#theme_wrappers'] = []; + + // #title display defaults to invisible. + $element += [ + '#title_display' => 'invisible', + ]; + } + + /** + * {@inheritdoc} + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); + + $element['#element_validate'][] = [get_class($this), 'validateAddress']; + } + + /** + * {@inheritdoc} + */ + protected function prepareElementPreRenderCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementPreRenderCallbacks($element, $webform_submission); + + // Replace 'form_element' theme wrapper with composite form element. + // @see \Drupal\Core\Render\Element\PasswordConfirm + $element['#pre_render'] = [[get_called_class(), 'preRenderWebformCompositeFormElement']]; + } + + /** + * {@inheritdoc} + */ + public function getCompositeElements() { + return []; + } + + /** + * {@inheritdoc} + * + * @see \Drupal\address\Plugin\Field\FieldType\AddressItem::schema + */ + public function initializeCompositeElements(array &$element) { + $element['#webform_composite_elements'] = [ + 'given_name' => [ + '#title' => $this->t('Given name'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'family_name' => [ + '#title' => $this->t('Family name'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'additional_name' => [ + '#title' => $this->t('Additional name'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'organization' => [ + '#title' => $this->t('Organization'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'address_line1' => [ + '#title' => $this->t('Address line 1'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'address_line2' => [ + '#title' => $this->t('Address line 2'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'postal_code' => [ + '#title' => $this->t('Postal code'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'locality' => [ + '#title' => $this->t('Locality'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'dependent_locality' => [ + '#title' => $this->t('Dependent_locality'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'administrative_area' => [ + '#title' => $this->t('Administrative area'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + 'country_code' => [ + '#title' => $this->t('Country code'), + '#type' => 'textfield', + '#maxlength' => 2, + ], + 'langcode' => [ + '#title' => $this->t('Language code'), + '#type' => 'textfield', + '#maxlength' => 32, + ], + 'sorting_code' => [ + '#title' => $this->t('Sorting code'), + '#type' => 'textfield', + '#maxlength' => 255, + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $format = $this->getItemFormat($element); + if ($format === 'value') { + return $this->buildAddress($element, $webform_submission, $options); + } + else { + return parent::formatHtmlItem($element, $webform_submission, $options); + } + } + + /** + * {@inheritdoc} + */ + protected function formatTextItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $format = $this->getItemFormat($element); + if ($format === 'value') { + $build = $this->buildAddress($element, $webform_submission, $options); + $html = \Drupal::service('renderer')->renderPlain($build); + return trim(MailFormatHelper::htmlToText($html)); + } + else { + return parent::formatTextItem($element, $webform_submission, $options); + } + } + + /** + * Build formatted address. + * + * The below code is copied form the protected + * AddressDefaultFormatter::viewElements method. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $options + * An array of options. + * + * @return array + * A render array containing the formatted address. + * + * @see \Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter::viewElements + * @see \Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter::viewElement + */ + protected function buildAddress(array $element, WebformSubmissionInterface $webform_submission, array $options) { + /** @var \CommerceGuys\Addressing\AddressFormat\AddressFormatRepositoryInterface $address_format_repository */ + $address_format_repository = \Drupal::service('address.address_format_repository'); + /** @var \CommerceGuys\Addressing\Country\CountryRepositoryInterface $country_repository */ + $country_repository = \Drupal::service('address.country_repository'); + + $value = $this->getValue($element, $webform_submission, $options); + // Skip if value or country code is empty. + if (empty($value) || empty($value['country_code'])) { + return []; + } + + // @see \Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter::viewElements + $build = [ + '#prefix' => '<div class="address" translate="no">', + '#suffix' => '</div>', + '#post_render' => [ + ['\Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter', 'postRender'], + ], + '#cache' => [ + 'contexts' => [ + 'languages:' . LanguageInterface::TYPE_INTERFACE, + ], + ], + ]; + + // @see \Drupal\address\Plugin\Field\FieldFormatter\AddressDefaultFormatter::viewElement + $country_code = $value['country_code']; + $countries = $country_repository->getList(); + $address_format = $address_format_repository->get($country_code); + + $build += [ + '#address_format' => $address_format, + '#locale' => 'und', + ]; + $build['country'] = [ + '#type' => 'html_tag', + '#tag' => 'span', + '#attributes' => ['class' => ['country']], + '#value' => Html::escape($countries[$country_code]), + '#placeholder' => '%country', + ]; + foreach ($address_format->getUsedFields() as $field) { + $property = FieldHelper::getPropertyName($field); + $class = str_replace('_', '-', $property); + $build[$property] = [ + '#type' => 'html_tag', + '#tag' => 'span', + '#attributes' => ['class' => [$class]], + '#value' => (!empty($value[$property])) ? Html::escape($value[$property]) : '', + '#placeholder' => '%' . $field, + ]; + } + + return $build; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Get field overrider from element properties. + $element_properties = $form_state->get('element_properties'); + $field_overrides = $element_properties['field_overrides']; + unset($element_properties['field_overrides']); + $form_state->set('element_properties', $element_properties); + + $form['address'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Address settings'), + ]; + + /**************************************************************************/ + // Copied from: \Drupal\address\Plugin\Field\FieldType\AddressItem::fieldSettingsForm + /**************************************************************************/ + + $languages = \Drupal::languageManager()->getLanguages(LanguageInterface::STATE_ALL); + $language_options = []; + foreach ($languages as $langcode => $language) { + if (!$language->isLocked()) { + $language_options[$langcode] = $language->getName(); + } + } + + $form['address']['available_countries'] = [ + '#type' => 'select', + '#title' => $this->t('Available countries'), + '#description' => $this->t('If no countries are selected, all countries will be available.'), + '#options' => \Drupal::service('address.country_repository')->getList(), + '#multiple' => TRUE, + '#size' => 10, + '#select2' => TRUE, + ]; + WebformElementHelper::process($form['address']['available_countries']); + $form['address']['langcode_override'] = [ + '#type' => 'select', + '#title' => $this->t('Language override'), + '#description' => $this->t('Ensures entered addresses are always formatted in the same language.'), + '#options' => $language_options, + '#empty_option' => $this->t('- No override -'), + '#access' => \Drupal::languageManager()->isMultilingual(), + ]; + $form['address']['field_overrides_title'] = [ + '#type' => 'item', + '#title' => $this->t('Field overrides'), + '#description' => $this->t('Use field overrides to override the country-specific address format, forcing specific fields to always be hidden, optional, or required.'), + '#access' => TRUE, + ]; + $form['address']['field_overrides'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Field'), + $this->t('Override'), + ], + '#access' => TRUE, + ]; + foreach (LabelHelper::getGenericFieldLabels() as $field_name => $label) { + $override = isset($field_overrides[$field_name]) ? $field_overrides[$field_name] : ''; + $form['address']['field_overrides'][$field_name] = [ + '#access' => TRUE, + 'field_label' => [ + '#access' => TRUE, + '#type' => 'markup', + '#markup' => $label, + ], + 'override' => [ + '#access' => TRUE, + '#type' => 'select', + '#default_value' => $override, + '#options' => [ + FieldOverride::HIDDEN => t('Hidden'), + FieldOverride::OPTIONAL => t('Optional'), + FieldOverride::REQUIRED => t('Required'), + ], + '#empty_option' => $this->t('- No override -'), + '#parents' => ['properties', 'field_overrides', $field_name], + ], + ]; + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + $values['field_overrides'] = array_filter($values['field_overrides']); + $form_state->setValues($values); + parent::validateConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + return [ + [ + 'given_name' => 'John', + 'family_name' => 'Smith', + 'organization' => 'Google Inc.', + 'address_line1' => '1098 Alta Ave', + 'postal_code' => '94043', + 'locality' => 'Mountain View', + 'administrative_area' => 'CA', + 'country_code' => 'US', + 'langcode' => 'en', + ], + ]; + } + + /** + * Form API callback. Make sure address element value includes a country code. + */ + public static function validateAddress(array &$element, FormStateInterface $form_state, array &$completed_form) { + $value = $element['#value']; + if (empty($element['#multiple'])) { + if (empty($value['country_code'])) { + $form_state->setValueForElement($element, NULL); + } + } + else { + foreach ($value as $index => $item) { + if (empty($item['country_code'])) { + unset($value[$index]); + } + } + $value = array_values($value); + $form_state->setValueForElement($element, $value ?: NULL); + } + } + +} diff --git a/web/modules/webform/src/Plugin/WebformElement/Captcha.php b/web/modules/webform/src/Plugin/WebformElement/Captcha.php index 8a68149324..0afb510527 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Captcha.php +++ b/web/modules/webform/src/Plugin/WebformElement/Captcha.php @@ -86,7 +86,6 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub parent::prepare($element, $webform_submission); $element['#after_build'][] = [get_class($this), 'afterBuildCaptcha']; - } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/Checkbox.php b/web/modules/webform/src/Plugin/WebformElement/Checkbox.php index 1e3c5d6705..967b3726c0 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Checkbox.php +++ b/web/modules/webform/src/Plugin/WebformElement/Checkbox.php @@ -2,6 +2,9 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\WebformSubmissionInterface; + /** * Provides a 'checkbox' element. * @@ -21,6 +24,8 @@ class Checkbox extends BooleanBase { public function getDefaultProperties() { $properties = [ 'title_display' => 'after', + // Checkbox. + 'exclude_empty' => FALSE, // iCheck settings. 'icheck' => '', ] + parent::getDefaultProperties(); @@ -28,4 +33,42 @@ public function getDefaultProperties() { return $properties; } + /** + * {@inheritdoc} + */ + protected function build($format, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { + if ($this->isEmptyExcluded($element, $options) && !$this->getValue($element, $webform_submission, $options)) { + return NULL; + } + else { + return parent::build($format, $element, $webform_submission, $options); + } + } + + /** + * {@inheritdoc} + */ + public function isEmptyExcluded(array $element, array $options) { + $options += [ + 'exclude_empty_checkbox' => FALSE, + ]; + + return $this->getElementProperty($element, 'exclude_empty') ?: $options['exclude_empty_checkbox']; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['display']['exclude_empty'] = [ + '#title' => $this->t('Exclude unselected checkbox'), + '#type' => 'checkbox', + '#return_value' => TRUE, + ]; + + return $form; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/Checkboxes.php b/web/modules/webform/src/Plugin/WebformElement/Checkboxes.php index 4d3ce2fd9e..2d7ce2a099 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Checkboxes.php +++ b/web/modules/webform/src/Plugin/WebformElement/Checkboxes.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\WebformSubmissionConditionsValidator; use Drupal\webform\WebformSubmissionInterface; @@ -31,6 +32,8 @@ public function getDefaultProperties() { 'options_description_display' => 'description', // iCheck settings. 'icheck' => '', + // Wrapper. + 'wrapper_type' => 'fieldset', ] + parent::getDefaultProperties(); } @@ -48,21 +51,17 @@ public function hasMultipleValues(array $element) { return TRUE; } - /** - * {@inheritdoc} - */ - public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; - parent::prepare($element, $webform_submission); - } - /** * {@inheritdoc} */ protected function getElementSelectorInputsOptions(array $element) { $selectors = $element['#options']; - foreach ($selectors as &$text) { + foreach ($selectors as $index => $text) { + // Remove description from text. + list($text) = explode(WebformOptionsHelper::DESCRIPTION_DELIMITER, $text); + // Append element type to text. $text .= ' [' . $this->t('Checkbox') . ']'; + $selectors[$index] = $text; } return $selectors; } @@ -82,6 +81,13 @@ public function getElementSelectorInputValue($selector, $trigger, array $element } } + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + return []; + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/Container.php b/web/modules/webform/src/Plugin/WebformElement/Container.php index 4f142a9432..2ff27f5ccd 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Container.php +++ b/web/modules/webform/src/Plugin/WebformElement/Container.php @@ -23,14 +23,18 @@ public function getDefaultProperties() { return [ // Attributes. 'attributes' => [], + // Randomize. + 'randomize' => FALSE, // Flexbox. 'flex' => 1, // Conditional logic. 'states' => [], + 'states_clear' => TRUE, // Format. 'format' => $this->getItemDefaultFormat(), 'format_html' => '', 'format_text' => '', + 'format_attributes' => [], ]; } diff --git a/web/modules/webform/src/Plugin/WebformElement/ContainerBase.php b/web/modules/webform/src/Plugin/WebformElement/ContainerBase.php index abf75034ce..77d25815b5 100644 --- a/web/modules/webform/src/Plugin/WebformElement/ContainerBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/ContainerBase.php @@ -2,9 +2,10 @@ namespace Drupal\webform\Plugin\WebformElement; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; use Drupal\webform\Plugin\WebformElementBase; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; @@ -19,16 +20,17 @@ abstract class ContainerBase extends WebformElementBase { public function getDefaultProperties() { return [ 'title' => '', - // General settings. - 'description' => '', // Form validation. 'required' => FALSE, + // Randomize. + 'randomize' => FALSE, // Attributes. 'attributes' => [], // Format. 'format' => $this->getItemDefaultFormat(), 'format_html' => '', 'format_text' => '', + 'format_attributes' => [], ] + $this->getDefaultBaseProperties(); } @@ -61,14 +63,13 @@ public function isContainer(array $element) { public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { parent::prepare($element, $webform_submission); - // Containers can only hide (aka invisible) the title by removing the - // #title attribute. - // @see core/modules/system/templates/fieldset.html.twig - // @see core/modules/system/templates/details.html.twig - if (isset($element['#title_display']) - && $element['#title_display'] === 'invisible' - && ($this instanceof Fieldset || $this instanceof Details)) { - unset($element['#title']); + if (!empty($element['#randomize'])) { + $elements = []; + foreach (Element::children($element) as $child_key) { + $elements[$child_key] = $element[$child_key]; + unset($element[$child_key]); + } + $element += WebformElementHelper::randomize($elements); } } @@ -128,35 +129,34 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we $format = 'header'; } + // Build format attributes. + $attributes = (isset($element['#format_attributes'])) ? $element['#format_attributes'] : []; + $attributes += ['class' => []]; + switch ($format) { case 'details': case 'details-closed': + $attributes['data-webform-element-id'] = $element['#webform_id']; + $attributes['class'][] = 'webform-container'; + $attributes['class'][] = 'webform-container-type-details'; return [ '#type' => 'details', '#title' => $element['#title'], '#id' => $element['#webform_id'], '#open' => ($format === 'details-closed') ? FALSE : TRUE, - '#attributes' => [ - 'data-webform-element-id' => $element['#webform_id'], - 'class' => [ - 'webform-container', - 'webform-container-type-details', - ], - ], + '#attributes' => $attributes, '#children' => $children, ]; case 'fieldset': + $attributes['class'][] = 'webform-container'; + $attributes['class'][] = 'webform-container-type-fieldset'; + return [ '#type' => 'fieldset', '#title' => $element['#title'], '#id' => $element['#webform_id'], - '#attributes' => [ - 'class' => [ - 'webform-container', - 'webform-container-type-fieldset', - ], - ], + '#attributes' => $attributes, '#children' => $children, ]; @@ -167,6 +167,7 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we '#id' => $element['#webform_id'], '#title' => $element['#title'], '#title_tag' => \Drupal::config('webform.settings')->get('element.default_section_title_tag'), + '#attributes' => $attributes, ] + $children; } } @@ -189,18 +190,18 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we '#suffix' => PHP_EOL, ]; $build['divider'] = [ - '#markup' => str_repeat('-', Unicode::strlen($element['#title'])), + '#markup' => str_repeat('-', mb_strlen($element['#title'])), '#suffix' => PHP_EOL, ]; } - $build += $children; + $build['children'] = $children; return $build; } /** * {@inheritdoc} */ - protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { + protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = [], array $context = []) { $name = strtolower($type); // Parse children from template and children to context. @@ -208,12 +209,10 @@ protected function formatCustomItem($type, array &$element, WebformSubmissionInt if (strpos($template, 'children') != FALSE) { /** @var \Drupal\webform\WebformSubmissionViewBuilderInterface $view_builder */ $view_builder = \Drupal::entityTypeManager()->getViewBuilder('webform_submission'); - $options['context'] = [ - 'children' => $view_builder->buildElements($element, $webform_submission, $options, $name), - ]; + $context['children'] = $view_builder->buildElements($element, $webform_submission, $options, $name); } - return parent::formatCustomItem($type, $element, $webform_submission, $options); + return parent::formatCustomItem($type, $element, $webform_submission, $options, $context); } /** @@ -240,6 +239,15 @@ public function getItemFormats() { */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + + // Randomize. + $form['element']['randomize'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Randomize elements'), + '#description' => $this->t('Randomizes the order of the sub-element when they are displayed in the webform.'), + '#return_value' => TRUE, + ]; + // Containers are wrappers, therefore wrapper classes should be used by the // container element. $form['element_attributes']['attributes']['#classes'] = $this->configFactory->get('webform.settings')->get('element.wrapper_classes'); @@ -251,10 +259,6 @@ public function form(array $form, FormStateInterface $form_state) { 'invisible' => $this->t('Invisible'), ]; - // Remove value from item custom display replacement patterns. - $item_patterns = &$form['display']['item']['patterns']['#value']['items']['#items']; - unset($item_patterns['value']); - $item_patterns = ['children' => '{{ children }}'] + $item_patterns; return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/Date.php b/web/modules/webform/src/Plugin/WebformElement/Date.php index 43a4361851..d46278b397 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Date.php +++ b/web/modules/webform/src/Plugin/WebformElement/Date.php @@ -35,6 +35,7 @@ public function getDefaultProperties() { return [ // Date settings. 'datepicker' => FALSE, + 'datepicker_button' => FALSE, 'date_date_format' => $date_format, 'step' => '', 'size' => '', @@ -59,8 +60,7 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // Prepare element after date format has been updated. parent::prepare($element, $webform_submission); - // Set the (input) type attribute to 'date' since #min and #max will - // override the default attributes. + // Set the (input) type attribute to 'date'. // @see \Drupal\Core\Render\Element\Date::getInfo $element['#attributes']['type'] = 'date'; @@ -71,16 +71,25 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // Must manually set 'data-drupal-date-format' to trigger date picker. // @see \Drupal\Core\Render\Element\Date::processDate $element['#attributes']['data-drupal-date-format'] = [$element['#date_date_format']]; + } + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + parent::setDefaultValue($element); - // Format default value. + // Format date picker default value. + if (!empty($element['#datepicker'])) { if (isset($element['#default_value'])) { if ($this->hasMultipleValues($element)) { foreach ($element['#default_value'] as $index => $default_value) { - $element['#default_value'][$index] = date($element['#date_date_format'], strtotime($default_value)); + $element['#default_value'][$index] = static::formatDate($element['#date_date_format'], strtotime($default_value)); } } else { - $element['#default_value'] = date($element['#date_date_format'], strtotime($element['#default_value'])); + $element['#default_value'] = static::formatDate($element['#date_date_format'], strtotime($element['#default_value'])); } } } @@ -111,19 +120,30 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('If checked, the HTML5 date element will be replaced with <a href="https://jqueryui.com/datepicker/">jQuery UI datepicker</a>'), '#return_value' => TRUE, ]; + $form['date']['datepicker_button'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show date picker button'), + '#description' => $this->t('If checked, date picker will include a calendar button'), + '#return_value' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="properties[datepicker]"]' => ['checked' => TRUE], + ], + ], + ]; $date_format = DateFormat::load('html_date')->getPattern(); $form['date']['date_date_format'] = [ '#type' => 'webform_select_other', '#title' => $this->t('Date format'), '#options' => [ - $date_format => $this->t('HTML date - @format (@date)', ['@format' => $date_format, '@date' => date($date_format)]), - 'l, F j, Y' => $this->t('Long date - @format (@date)', ['@format' => 'l, F j, Y', '@date' => date('l, F j, Y')]), - 'D, m/d/Y' => $this->t('Medium date - @format (@date)', ['@format' => 'D, m/d/Y', '@date' => date('D, m/d/Y')]), - 'm/d/Y' => $this->t('Short date - @format (@date)', ['@format' => 'm/d/Y', '@date' => date('m/d/Y')]), + $date_format => $this->t('HTML date - @format (@date)', ['@format' => $date_format, '@date' => static::formatDate($date_format)]), + 'l, F j, Y' => $this->t('Long date - @format (@date)', ['@format' => 'l, F j, Y', '@date' => static::formatDate('l, F j, Y')]), + 'D, m/d/Y' => $this->t('Medium date - @format (@date)', ['@format' => 'D, m/d/Y', '@date' => static::formatDate('D, m/d/Y')]), + 'm/d/Y' => $this->t('Short date - @format (@date)', ['@format' => 'm/d/Y', '@date' => static::formatDate('m/d/Y')]), ], '#description' => $this->t("Date format is only applicable for browsers that do not have support for the HTML5 date element. Browsers that support the HTML5 date element will display the date using the user's preferred format."), - '#other__option_label' => $this->t('Custom...'), - '#other__placeholder' => $this->t('Custom date format...'), + '#other__option_label' => $this->t('Custom…'), + '#other__placeholder' => $this->t('Custom date format…'), '#other__description' => $this->t('Enter date format using <a href="http://php.net/manual/en/function.date.php">Date Input Format</a>.'), '#states' => [ 'visible' => [ diff --git a/web/modules/webform/src/Plugin/WebformElement/DateBase.php b/web/modules/webform/src/Plugin/WebformElement/DateBase.php index 4e42236e86..2330ca7c01 100644 --- a/web/modules/webform/src/Plugin/WebformElement/DateBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/DateBase.php @@ -8,7 +8,10 @@ use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; +use Drupal\webform\Element\WebformMessage as WebformMessageElement; use Drupal\webform\Plugin\WebformElementBase; +use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformInterface; @@ -23,8 +26,8 @@ abstract class DateBase extends WebformElementBase { public function getDefaultProperties() { return [ // Form validation. - 'min' => '', - 'max' => '', + 'date_date_min' => '', + 'date_date_max' => '', ] + parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); } @@ -54,36 +57,53 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // Parse #default_value date input format. $this->parseInputFormat($element, '#default_value'); - // Override min/max attributes. + // Set date min/max attributes. + // This overrides extra attributes set via Datetime::processDatetime. + // @see \Drupal\Core\Datetime\Element\Datetime::processDatetime if (isset($element['#date_date_format'])) { - if (!empty($element['#min'])) { - $element['#attributes']['min'] = date($element['#date_date_format'], strtotime($element['#min'])); - $element['#attributes']['data-min-year'] = date('Y', strtotime($element['#min'])); + $date_min = $this->getElementProperty($element, 'date_date_min') ?: $this->getElementProperty($element, 'date_min'); + if ($date_min) { + $element['#attributes']['min'] = static::formatDate($element['#date_date_format'], strtotime($date_min)); + $element['#attributes']['data-min-year'] = static::formatDate('Y', strtotime($date_min)); } - if (!empty($element['#max'])) { - $element['#attributes']['max'] = date($element['#date_date_format'], strtotime($element['#max'])); - $element['#attributes']['data-max-year'] = date('Y', strtotime($element['#max'])); + $date_max = $this->getElementProperty($element, 'date_date_max') ?: $this->getElementProperty($element, 'date_max'); + if (!empty($date_max)) { + $element['#attributes']['max'] = static::formatDate($element['#date_date_format'], strtotime($date_max)); + $element['#attributes']['data-max-year'] = static::formatDate('Y', strtotime($date_max)); } } - $element['#element_validate'] = array_merge([[get_class($this), 'preValidateDate']], $element['#element_validate']); - $element['#element_validate'][] = [get_class($this), 'validateDate']; + // Display datepicker button. + if (!empty($element['#datepicker_button']) || !empty($element['#date_date_datepicker_button'])) { + $element['#attributes']['data-datepicker-button'] = TRUE; + $element['#attached']['drupalSettings']['webform']['datePicker']['buttonImage'] = base_path() . drupal_get_path('module', 'webform') . '/images/elements/date-calendar.png'; + } // Set first day according to admin/config/regional/settings. $config = $this->configFactory->get('system.date'); $element['#attached']['drupalSettings']['webform']['dateFirstDay'] = $config->get('first_day'); - $cacheability = CacheableMetadata::createFromObject($config); $cacheability->applyTo($element); $element['#attached']['library'][] = 'webform/webform.element.date'; + + $element['#after_build'][] = [get_class($this), 'afterBuild']; + } + + /** + * {@inheritdoc} + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + $element['#element_validate'] = array_merge([[get_class($this), 'preValidateDate']], $element['#element_validate']); + $element['#element_validate'][] = [get_class($this), 'validateDate']; + parent::prepareElementValidateCallbacks($element, $webform_submission); } /** * {@inheritdoc} */ public function setDefaultValue(array &$element) { - if (!empty($element['#multiple'])) { + if ($this->hasMultipleValues($element)) { $element['#default_value'] = (isset($element['#default_value'])) ? (array) $element['#default_value'] : NULL; return; } @@ -96,6 +116,31 @@ public function setDefaultValue(array &$element) { } } + /** + * After build handler for date elements. + */ + public static function afterBuild(array $element, FormStateInterface $form_state) { + // Add parent title to sub-elements to child elements which applies to + // datetime and datelist elements. + $child_keys = Element::children($element); + foreach ($child_keys as $child_key) { + if (isset($element[$child_key]['#title'])) { + $t_args = [ + '@parent' => $element['#title'], + '@child' => $element[$child_key]['#title'], + ]; + $element[$child_key]['#title'] = t('@parent: @child', $t_args); + } + } + + // Remove orphaned form label. + if ($child_keys) { + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + } + + return $element; + } + /****************************************************************************/ // Display submission value methods. /****************************************************************************/ @@ -119,7 +164,7 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we return \Drupal::service('date.formatter')->format($timestamp, $format); } else { - return date($format, $timestamp); + return static::formatDate($format, $timestamp); } } @@ -142,7 +187,7 @@ public function getItemFormats() { // If a default format is defined update the fallback date formats label. // @see \Drupal\webform\Plugin\WebformElementBase::getItemFormat $default_format = $this->configFactory->get('webform.settings')->get('format.' . $this->getPluginId() . '.item'); - if ($default_format && isset($formats[$default_format])) { + if ($default_format && isset($date_formats[$default_format])) { $formats['fallback'] = t('Default date format (@label)', ['@label' => $date_formats[$default_format]->label()]); } return $formats; @@ -174,11 +219,11 @@ public function form(array $form, FormStateInterface $form_state) { $form['default']['default_value']['#description'] .= '<br /><br />' . $this->t('Accepts any date in any <a href="https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats">GNU Date Input Format</a>. Strings such as today, +2 months, and Dec 9 2004 are all valid.'); // Append token date format to #default_value description. - $form['default']['default_value']['#description'] .= '<br /><br />' . $this->t("You may use tokens. Tokens should use the 'html_date' or 'html_datetime' date format. (i.e. @date_format)", ['@date_format' => '[webform-authenticated-user:field_date_of_birth:date:html_date]']); + $form['default']['default_value']['#description'] .= '<br /><br />' . $this->t("You may use tokens. Tokens should use the 'html_date' or 'html_datetime' date format. (i.e. @date_format)", ['@date_format' => '[current-user:field_date_of_birth:date:html_date]']); // Allow custom date formats to be entered. $form['display']['format']['#type'] = 'webform_select_other'; - $form['display']['format']['#other__option_label'] = $this->t('Custom date format...'); + $form['display']['format']['#other__option_label'] = $this->t('Custom date format…'); $form['display']['format']['#other__description'] = $this->t('A user-defined date format. See the <a href="http://php.net/manual/function.date.php">PHP manual</a> for available options.'); $form['date'] = [ @@ -186,19 +231,53 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Date settings'), ]; - $form['date']['min'] = [ + // Date min/max validation. + $form['date']['date_date_min'] = [ '#type' => 'textfield', - '#title' => $this->t('Date min'), + '#title' => $this->t('Date minimum'), '#description' => $this->t('Specifies the minimum date.') . '<br /><br />' . $this->t('Accepts any date in any <a href="https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats">GNU Date Input Format</a>. Strings such as today, +2 months, and Dec 9 2004 are all valid.'), '#weight' => 10, ]; - $form['date']['max'] = [ + $form['date']['date_date_max'] = [ '#type' => 'textfield', - '#title' => $this->t('Date max'), + '#title' => $this->t('Date maximum'), '#description' => $this->t('Specifies the maximum date.') . '<br /><br />' . $this->t('Accepts any date in any <a href="https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats">GNU Date Input Format</a>. Strings such as today, +2 months, and Dec 9 2004 are all valid.'), '#weight' => 10, ]; + // Date/time min/max validation. + if ($this->hasProperty('date_date_min') + && $this->hasProperty('date_time_min') + && $this->hasProperty('date_min')) { + $form['validation']['date_min_max_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#access' => TRUE, + '#message_message' => $this->t("'Date/time' minimum or maximum should not be used with 'Date' or 'Time' specific minimum or maximum.") . '<br/>' . + '<strong>' . $this->t('This can cause unexpected validation errors.') . '</strong>', + '#message_close' => TRUE, + '#message_storage' => WebformMessageElement::STORAGE_SESSION, + '#states' => [ + 'visible' => [ + [':input[name="properties[date_date_min]"]' => ['filled' => TRUE]], + [':input[name="properties[date_date_max]"]' => ['filled' => TRUE]], + [':input[name="properties[date_time_min]"]' => ['filled' => TRUE]], + [':input[name="properties[date_time_max]"]' => ['filled' => TRUE]], + ], + ], + ]; + } + $form['validation']['date_min'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date/time minimum'), + '#description' => $this->t('Specifies the minimum date/time.') . '<br /><br />' . $this->t('Accepts any date in any <a href="https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats">GNU Date/Time Input Format</a>. Strings such as today, +2 months, and Dec 9 2004 10:00 PM are all valid.'), + ]; + $form['validation']['date_max'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date/time maximum'), + '#description' => $this->t('Specifies the maximum date/time.') . '<br /><br />' . $this->t('Accepts any date in any <a href="https://www.gnu.org/software/tar/manual/html_chapter/tar_7.html#Date-input-formats">GNU Date/Time Input Format</a>. Strings such as today, +2 months, and Dec 9 2004 10:00 PM are all valid.'), + ]; + return $form; } @@ -213,12 +292,18 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form $this->setGnuDateInputFormatError($form['properties']['default']['default_value'], $form_state); } - // Validate #min and #max GNU Date Input Format. - $input_formats = ['min', 'max']; - foreach ($input_formats as $input_format) { - if (!$this->validateGnuDateInputFormat($properties, "#$input_format")) { - $this->setGnuDateInputFormatError($form['properties']['date'][$input_format], $form_state); - } + // Validate #*_min and #*_max GNU Date Input Format. + if (!$this->validateGnuDateInputFormat($properties, '#date_min')) { + $this->setGnuDateInputFormatError($form['properties']['validation']['date_min'], $form_state); + } + if (!$this->validateGnuDateInputFormat($properties, '#date_max')) { + $this->setGnuDateInputFormatError($form['properties']['validation']['date_max'], $form_state); + } + if (!$this->validateGnuDateInputFormat($properties, '#date_date_min')) { + $this->setGnuDateInputFormatError($form['properties']['date']['date_date_min'], $form_state); + } + if (!$this->validateGnuDateInputFormat($properties, '#date_date_max')) { + $this->setGnuDateInputFormatError($form['properties']['date']['date_date_max'], $form_state); } parent::validateConfigurationForm($form, $form_state); @@ -350,6 +435,11 @@ public static function preValidateDate(&$element, FormStateInterface $form_state $input_exists = FALSE; $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists); if (!isset($input['object'])) { + // Time picker converts all submitted time values to H:i:s format. + // @see \Drupal\webform\Element\WebformTime::validateWebformTime + if (isset($element['#date_time_element']) && $element['#date_time_element'] === 'timepicker') { + $element['#date_time_format'] = 'H:i:s'; + } $input = $date_class::valueCallback($element, $input, $form_state); $form_state->setValueForElement($element, $input); $element['#value'] = $input; @@ -368,16 +458,15 @@ public static function validateDate(&$element, FormStateInterface $form_state, & $value = $element['#value']; $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title']; $date_date_format = (!empty($element['#date_date_format'])) ? $element['#date_date_format'] : DateFormat::load('html_date')->getPattern(); + $date_time_format = (!empty($element['#date_time_format'])) ? $element['#date_time_format'] : DateFormat::load('html_time')->getPattern(); // Convert DrupalDateTime array and object to ISO datetime. if (is_array($value)) { $value = ($value['object']) ? $value['object']->format(DateFormat::load('html_datetime')->getPattern()) : ''; } elseif ($value) { - // Ensure the input is valid date by creating a date object and comparing - // formatted date object to the submitted date value. - $datetime = date_create_from_format($date_date_format, $value); - if ($datetime === FALSE || date_format($datetime, $date_date_format) != $value) { + $datetime = WebformDateHelper::createFromFormat($date_date_format, $value); + if ($datetime === FALSE || static::formatDate($date_date_format, $datetime->getTimestamp()) !== $value) { $form_state->setError($element, t('%name must be a valid date.', ['%name' => $name])); $value = ''; } @@ -400,24 +489,46 @@ public static function validateDate(&$element, FormStateInterface $form_state, & $time = strtotime($value); - // Ensure that the input is greater than the #min property, if set. - if (isset($element['#min'])) { - $min = strtotime(date('Y-m-d', strtotime($element['#min']))); + // Ensure that the input is greater than the #date_date_min property, if set. + if (isset($element['#date_date_min'])) { + $min = strtotime(static::formatDate('Y-m-d', strtotime($element['#date_date_min']))); if ($time < $min) { $form_state->setError($element, t('%name must be on or after %min.', [ '%name' => $name, - '%min' => date($date_date_format, $min), + '%min' => static::formatDate($date_date_format, $min), ])); } } - // Ensure that the input is less than the #max property, if set. - if (isset($element['#max'])) { - $max = strtotime(date('Y-m-d', strtotime($element['#max']))); + // Ensure that the input is less than the #date_date_max property, if set. + if (isset($element['#date_date_max'])) { + $max = strtotime(static::formatDate('Y-m-d 23:59:59', strtotime($element['#date_date_max']))); if ($time > $max) { $form_state->setError($element, t('%name must be on or before %max.', [ '%name' => $name, - '%max' => date($date_date_format, $max), + '%max' => static::formatDate($date_date_format, $max), + ])); + } + } + + // Ensure that the input is greater than the #date_min property, if set. + if (isset($element['#date_min'])) { + $min = strtotime($element['#date_min']); + if ($time < $min) { + $form_state->setError($element, t('%name must be on or after %min.', [ + '%name' => $name, + '%min' => static::formatDate($date_date_format, $min) . ' ' . static::formatDate($date_time_format, $min), + ])); + } + } + + // Ensure that the input is less than the #date_max property, if set. + if (isset($element['#date_max'])) { + $max = strtotime($element['#date_max']); + if ($time > $max) { + $form_state->setError($element, t('%name must be on or before %max.', [ + '%name' => $name, + '%max' => static::formatDate($date_date_format, $max) . ' ' . static::formatDate($date_time_format, $max), ])); } } @@ -432,16 +543,16 @@ public function getTestValues(array $element, WebformInterface $webform, array $ list($min, $max) = static::datetimeRangeYears($element['#date_year_range']); } else { - $min = !empty($element['#min']) ? strtotime($element['#min']) : strtotime('-10 years'); - $max = !empty($element['#max']) ? strtotime($element['#max']) : max($min, strtotime('+20 years') ?: PHP_INT_MAX); + $min = !empty($element['#date_date_min']) ? strtotime($element['#date_date_min']) : strtotime('-10 years'); + $max = !empty($element['#date_date_max']) ? strtotime($element['#date_date_max']) : max($min, strtotime('+20 years') ?: PHP_INT_MAX); } - return date($format, rand($min, $max)); + return static::formatDate($format, rand($min, $max)); } /** * Specifies the start and end year to use as a date range. * - * Copied from: \Drupal\Core\Datetime\Element\DateElementBase::datetimeRangeYears + * Copied from: DateElementBase::datetimeRangeYears. * * @param string $string * A min and max year string like '-3:+1' or '2000:2010' or '2000:+3'. @@ -451,6 +562,8 @@ public function getTestValues(array $element, WebformInterface $webform, array $ * @return array * A numerically indexed array, containing the minimum and maximum year * described by this pattern. + * + * @see \Drupal\Core\Datetime\Element\DateElementBase::datetimeRangeYears */ protected static function datetimeRangeYears($string, $date = NULL) { $datetime = new DrupalDateTime(); @@ -492,4 +605,21 @@ protected static function datetimeRangeYears($string, $date = NULL) { return [$min_year, $max_year]; } + /** + * Format custom date. + * + * @param string $custom_format + * A PHP date format string suitable for input to date(). + * @param int $timestamp + * (optional) A UNIX timestamp to format. + * + * @return string + * Formatted date. + */ + protected static function formatDate($custom_format, $timestamp = NULL) { + /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ + $date_formatter = \Drupal::service('date.formatter'); + return $date_formatter->format($timestamp ?: time(), 'custom', $custom_format); + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/DateList.php b/web/modules/webform/src/Plugin/WebformElement/DateList.php index a06ca55a95..69e0905ab8 100644 --- a/web/modules/webform/src/Plugin/WebformElement/DateList.php +++ b/web/modules/webform/src/Plugin/WebformElement/DateList.php @@ -28,6 +28,8 @@ class DateList extends DateBase { */ public function getDefaultProperties() { return [ + 'date_min' => '', + 'date_max' => '', // Date settings. 'date_part_order' => [ 'year', @@ -36,12 +38,11 @@ public function getDefaultProperties() { 'hour', 'minute', ], - 'date_text_parts' => [ - 'year', - ], + 'date_text_parts' => [], 'date_year_range' => '1900:2050', 'date_year_range_reverse' => FALSE, 'date_increment' => 1, + 'date_abbreviate' => TRUE, ] + parent::getDefaultProperties(); } @@ -51,7 +52,16 @@ public function getDefaultProperties() { public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { parent::prepare($element, $webform_submission); - $element['#after_build'][] = [get_class($this), 'afterBuild']; + // Remove month abbreviation. + // @see \Drupal\Core\Datetime\Element\Datelist::processDatelist + if (isset($element['#date_abbreviate']) && $element['#date_abbreviate'] === FALSE) { + $element['#date_date_callbacks'][] = '_webform_datelist_date_date_callback'; + } + + // Remove 'for' from the element's label. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + + $element['#attached']['library'][] = 'webform/webform.element.datelist'; } /** @@ -186,6 +196,13 @@ public function form(array $form, FormStateInterface $form_state) { '#size' => 4, '#weight' => 10, ]; + $form['date']['date_abbreviate'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Abbreviate month'), + '#description' => $this->t('If checked, month will be abbreviated to three letters.'), + '#return_value' => TRUE, + ]; + return $form; } @@ -214,6 +231,8 @@ protected function setConfigurationFormDefaultValue(array &$form, array &$elemen * After build handler for Datelist element. */ public static function afterBuild(array $element, FormStateInterface $form_state) { + $element = parent::afterBuild($element, $form_state); + // Reverse years from min:max to max:min. // @see \Drupal\Core\Datetime\Element\DateElementBase::datetimeRangeYears if (!empty($element['#date_year_range_reverse']) && isset($element['year']) && isset($element['year']['#options'])) { @@ -258,20 +277,20 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat // the $form_state. $temp_form_state = clone $form_state; - // Clear error to ensure that we only capture datelist validation errors. - $temp_form_state->clearErrors(); - // Validate the date list element. DatelistElement::validateDatelist($element, $temp_form_state, $complete_form); // Copy $temp_form_state errors to $form_state error and alter // override default required error message is applicable. + $original_errors = $form_state->getErrors(); $errors = $temp_form_state->getErrors(); foreach ($errors as $name => $message) { - if ($message instanceof TranslatableMarkup && $message->getUntranslatedString() === "The %field date is required.") { - $message = $element['#required_error']; + if (empty($original_errors[$name])) { + if ($message instanceof TranslatableMarkup && $message->getUntranslatedString() === "The %field date is required.") { + $message = $element['#required_error']; + } + $form_state->setErrorByName($name, $message); } - $form_state->setErrorByName($name, $message); } } diff --git a/web/modules/webform/src/Plugin/WebformElement/DateTime.php b/web/modules/webform/src/Plugin/WebformElement/DateTime.php index 1afab5cf32..5d733ddbf5 100644 --- a/web/modules/webform/src/Plugin/WebformElement/DateTime.php +++ b/web/modules/webform/src/Plugin/WebformElement/DateTime.php @@ -41,13 +41,15 @@ public function getDefaultProperties() { } return [ + 'date_min' => '', + 'date_max' => '', // Date settings. 'date_date_format' => $date_format, + 'date_date_datepicker_button' => FALSE, 'date_date_element' => 'date', 'date_year_range' => '1900:2050', - 'date_increment' => 1, + // Time settings. 'date_time_format' => $time_format, - 'date_timezone' => '', 'date_time_element' => 'time', 'date_time_min' => '', 'date_time_max' => '', @@ -66,6 +68,9 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#default_value'] = NULL; } + // Remove 'for' from the element's label. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + /* Date */ $date_element = (isset($element['#date_date_element'])) ? $element['#date_date_element'] : 'date'; @@ -76,12 +81,23 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub unset($element['date_date_format']); } + // Set date year range. + $element += ['#date_year_range' => '']; + if (empty($element['#date_year_range'])) { + $date_min = $this->getElementProperty($element, 'date_date_min') ?: $this->getElementProperty($element, 'date_min'); + $min_year = ($date_min) ? static::formatDate('Y', strtotime($date_min)) : '1900'; + $date_max = $this->getElementProperty($element, 'date_date_max') ?: $this->getElementProperty($element, 'date_max'); + $max_year = ($date_max) ? static::formatDate('Y', strtotime($date_max)) : '2050'; + $element['#date_year_range'] = "$min_year:$max_year"; + } + // Set date format. if (!isset($element['#date_date_format'])) { $element['#date_date_format'] = $this->getDefaultProperty('date_date_format'); } - $element['#date_date_callbacks'][] = '_webform_datetime_datepicker'; + // Add date callback. + $element['#date_date_callbacks'][] = '_webform_datetime_date'; /* Time */ @@ -90,13 +106,11 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#date_time_format'] = $this->getDefaultProperty('date_time_format'); } - // Add timepicker callback. - $element['#date_time_callbacks'][] = '_webform_datetime_timepicker'; + // Add time callback. + $element['#date_time_callbacks'][] = '_webform_datetime_time'; // Prepare element after date/time formats have been updated. parent::prepare($element, $webform_submission); - - $element['#after_build'][] = [get_class($this), 'afterBuildDateTime']; } /** @@ -150,6 +164,18 @@ public function form(array $form, FormStateInterface $form_state) { 'none' => $this->t('None - Do not display a date element'), ], ]; + $form['date']['date_date_datepicker_button'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show date picker button'), + '#description' => $this->t('If checked, date picker will include a calendar button'), + '#return_value' => TRUE, + '#states' => [ + 'visible' => [ + [':input[name="properties[date_date_element]"]' => ['value' => 'datepicker']], + ], + ], + + ]; $form['date']['date_date_element_datetime_warning'] = [ '#type' => 'webform_message', '#message_type' => 'warning', @@ -166,7 +192,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['date']['date_date_element_none_warning'] = [ '#type' => 'webform_message', '#message_type' => 'warning', - '#message_message' => $this->t('You should consider using a dedicated Time element, instead of this Date/time element, which will preprend the current date to the submitted time.'), + '#message_message' => $this->t('You should consider using a dedicated Time element, instead of this Date/time element, which will prepend the current date to the submitted time.'), '#access' => TRUE, '#states' => [ 'visible' => [ @@ -179,13 +205,13 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_select_other', '#title' => $this->t('Date format'), '#options' => [ - $date_format => $this->t('HTML date - @format (@date)', ['@format' => $date_format, '@date' => date($date_format)]), - 'l, F j, Y' => $this->t('Long date - @format (@date)', ['@format' => 'l, F j, Y', '@date' => date('l, F j, Y')]), - 'D, m/d/Y' => $this->t('Medium date - @format (@date)', ['@format' => 'D, m/d/Y', '@date' => date('D, m/d/Y')]), - 'm/d/Y' => $this->t('Short date - @format (@date)', ['@format' => 'm/d/Y', '@date' => date('m/d/Y')]), + $date_format => $this->t('HTML date - @format (@date)', ['@format' => $date_format, '@date' => static::formatDate($date_format)]), + 'l, F j, Y' => $this->t('Long date - @format (@date)', ['@format' => 'l, F j, Y', '@date' => static::formatDate('l, F j, Y')]), + 'D, m/d/Y' => $this->t('Medium date - @format (@date)', ['@format' => 'D, m/d/Y', '@date' => static::formatDate('D, m/d/Y')]), + 'm/d/Y' => $this->t('Short date - @format (@date)', ['@format' => 'm/d/Y', '@date' => static::formatDate('m/d/Y')]), ], - '#other__option_label' => $this->t('Custom...'), - '#other__placeholder' => $this->t('Custom date format...'), + '#other__option_label' => $this->t('Custom…'), + '#other__placeholder' => $this->t('Custom date format…'), '#other__description' => $this->t('Enter date format using <a href="http://php.net/manual/en/function.date.php">Date Input Format</a>.'), '#states' => [ 'visible' => [ @@ -195,15 +221,6 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; - $form['date']['date_timezone'] = [ - '#type' => 'select', - '#title' => $this->t('Date timezone override'), - '#options' => system_time_zones(TRUE), - '#description' => $this->t('Generally this should be left empty and it will be set correctly for the user using the webform.') . ' ' . - $this->t('Useful if the default value is empty to designate a desired timezone for dates created in webform processing.') . ' ' . - $this->t('If a default date is provided, this value will be ignored, the timezone in the default date takes precedence.') . ' ' . - $this->t('Defaults to the value returned by drupal_get_user_timezone().'), - ]; $form['date']['date_year_range'] = [ '#type' => 'textfield', '#title' => $this->t('Date year range'), @@ -218,23 +235,6 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; - $form['date']['date_increment'] = [ - '#type' => 'number', - '#title' => $this->t('Date increment'), - '#description' => $this->t("The increment to use for minutes and seconds, i.e. '15' would show only :00, :15, :30 and :45. Used for HTML5 step values and jQueryUI (fallback) datepicker settings. Defaults to 1 to show every minute."), - '#min' => 1, - '#attributes' => ['data-webform-states-no-clear' => TRUE], - '#states' => [ - 'invisible' => [ - [':input[name="properties[date_date_element]"]' => ['value' => 'datetime']], - 'xor', - [':input[name="properties[date_date_element]"]' => ['value' => 'datetime-local']], - 'xor', - [':input[name="properties[date_time_element]"]' => ['value' => 'none']], - ], - ], - '#weight' => 10, - ]; // Time. $form['time'] = [ @@ -263,16 +263,16 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Time format'), '#description' => $this->t("Time format is only applicable for browsers that do not have support for the HTML5 time element. Browsers that support the HTML5 time element will display the time using the user's preferred format."), '#options' => [ - 'H:i:s' => $this->t('24 hour with seconds - @format (@time)', ['@format' => 'H:i:s', '@time' => date('H:i:s')]), - 'H:i' => $this->t('24 hour - @format (@time)', ['@format' => 'H:i', '@time' => date('H:i')]), - 'g:i:s A' => $this->t('12 hour with seconds - @format (@time)', ['@format' => 'g:i:s A', '@time' => date('g:i:s A')]), - 'g:i A' => $this->t('12 hour - @format (@time)', ['@format' => 'g:i A', '@time' => date('g:i A')]), + 'H:i:s' => $this->t('24 hour with seconds - @format (@time)', ['@format' => 'H:i:s', '@time' => static::formatDate('H:i:s')]), + 'H:i' => $this->t('24 hour - @format (@time)', ['@format' => 'H:i', '@time' => static::formatDate('H:i')]), + 'g:i:s A' => $this->t('12 hour with seconds - @format (@time)', ['@format' => 'g:i:s A', '@time' => static::formatDate('g:i:s A')]), + 'g:i A' => $this->t('12 hour - @format (@time)', ['@format' => 'g:i A', '@time' => static::formatDate('g:i A')]), ], - '#other__option_label' => $this->t('Custom...'), - '#other__placeholder' => $this->t('Custom time format...'), + '#other__option_label' => $this->t('Custom…'), + '#other__placeholder' => $this->t('Custom time format…'), '#other__description' => $this->t('Enter time format using <a href="http://php.net/manual/en/function.date.php">Time Input Format</a>.'), '#states' => [ - 'invisible' => [ + 'invisible' => [ [':input[name="properties[date_date_element]"]' => ['value' => 'datetime']], 'or', [':input[name="properties[date_date_element]"]' => ['value' => 'datetime-local']], @@ -286,20 +286,20 @@ public function form(array $form, FormStateInterface $form_state) { $form['time']['date_time_container'] = $this->getFormInlineContainer(); $form['time']['date_time_container']['date_time_min'] = [ '#type' => 'webform_time', - '#title' => $this->t('Time min'), + '#title' => $this->t('Time minimum'), '#description' => $this->t('Specifies the minimum time.'), '#states' => [ - 'invisible' => [ + 'invisible' => [ [':input[name="properties[date_time_element]"]' => ['value' => 'none']], ], ], ]; $form['time']['date_time_container']['date_time_max'] = [ '#type' => 'webform_time', - '#title' => $this->t('Time max'), + '#title' => $this->t('Time maximum'), '#description' => $this->t('Specifies the maximum time.'), '#states' => [ - 'invisible' => [ + 'invisible' => [ [':input[name="properties[date_time_element]"]' => ['value' => 'none']], ], ], @@ -309,8 +309,8 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Time step'), '#description' => $this->t('Specifies the minute intervals.'), '#options' => [ - '' => $this->t('1 minute'), - 30 => $this->t('5 minutes'), + 60 => $this->t('1 minute'), + 300 => $this->t('5 minutes'), 600 => $this->t('10 minutes'), 900 => $this->t('15 minutes'), 1200 => $this->t('20 minutes'), @@ -318,6 +318,11 @@ public function form(array $form, FormStateInterface $form_state) { ], '#other__type' => 'number', '#other__description' => $this->t('Enter interval in seconds.'), + '#states' => [ + 'invisible' => [ + [':input[name="properties[date_time_element]"]' => ['value' => 'none']], + ], + ], ]; return $form; } @@ -377,25 +382,4 @@ public function getConfigurationFormProperties(array &$form, FormStateInterface return $properties; } - /** - * After build handler for Datetime elements. - */ - public static function afterBuildDateTime(array $element, FormStateInterface $form_state) { - if (isset($element['time'])) { - if (!empty($element['#date_time_min'])) { - $element['time']['#min'] = $element['#date_time_min']; - $element['time']['#attributes']['min'] = $element['#date_time_min']; - } - if (!empty($element['#date_time_max'])) { - $element['time']['#max'] = $element['#date_time_max']; - $element['time']['#attributes']['max'] = $element['#date_time_max']; - } - if (!empty($element['#date_time_step'])) { - $element['time']['#max'] = $element['#date_time_step']; - $element['time']['#attributes']['step'] = $element['#date_time_step']; - } - } - return $element; - } - } diff --git a/web/modules/webform/src/Plugin/WebformElement/Details.php b/web/modules/webform/src/Plugin/WebformElement/Details.php index b039ddfd5a..0063e7d3e0 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Details.php +++ b/web/modules/webform/src/Plugin/WebformElement/Details.php @@ -21,10 +21,27 @@ class Details extends ContainerBase { * {@inheritdoc} */ public function getDefaultProperties() { - return [ + $properties = [ + // Description/Help. 'help' => '', + 'help_title' => '', + 'description' => '', + 'more' => '', + 'more_title' => '', + // Title. + 'title_display' => '', + // Details. 'open' => FALSE, ] + parent::getDefaultProperties(); + + // Issue #2971848: [8.6.x] Details elements allow specifying attributes + // for the <summary> element. + // @todo Remove the below if/then when only 8.6.x is supported. + if (version_compare(\Drupal::VERSION, '8.6', '>=')) { + $properties['summary_attributes'] = []; + } + + return $properties; } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/Email.php b/web/modules/webform/src/Plugin/WebformElement/Email.php index 6947fec84e..b7a3444b47 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Email.php +++ b/web/modules/webform/src/Plugin/WebformElement/Email.php @@ -21,7 +21,10 @@ class Email extends TextBase { * {@inheritdoc} */ public function getDefaultProperties() { - return parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); + return [ + 'input_hide' => FALSE, + ] + parent::getDefaultProperties() + + $this->getDefaultMultipleProperties(); } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/EntityAutocomplete.php b/web/modules/webform/src/Plugin/WebformElement/EntityAutocomplete.php index 02896d7494..3aa4d18f3e 100644 --- a/web/modules/webform/src/Plugin/WebformElement/EntityAutocomplete.php +++ b/web/modules/webform/src/Plugin/WebformElement/EntityAutocomplete.php @@ -90,7 +90,6 @@ public function hasMultipleValues(array $element) { */ public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { parent::prepare($element, $webform_submission); - $element['#after_build'][] = [get_class($this), 'afterBuildEntityAutocomplete']; // Remove maxlength. $element['#maxlength'] = NULL; @@ -105,19 +104,21 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub } /** - * Form API callback. After build set the #element_validate handler. + * {@inheritdoc} */ - public static function afterBuildEntityAutocomplete(array $element, FormStateInterface $form_state) { + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); $element['#element_validate'][] = ['\Drupal\webform\Plugin\WebformElement\EntityAutocomplete', 'validateEntityAutocomplete']; - return $element; } /** * Form API callback. Remove target id property and create an array of entity ids. */ public static function validateEntityAutocomplete(array &$element, FormStateInterface $form_state) { - $name = $element['#name']; - $value = $form_state->getValue($name); + // Must use ::getValue($element['#parents']) because $element['#value'] is + // not being updated. + // @see \Drupal\Core\Entity\Element\EntityAutocomplete::validateEntityAutocomplete + $value = $form_state->getValue($element['#parents']); if (empty($value) || !is_array($value)) { return; } diff --git a/web/modules/webform/src/Plugin/WebformElement/Fieldset.php b/web/modules/webform/src/Plugin/WebformElement/Fieldset.php index ef09df5ec4..4f2c610ab2 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Fieldset.php +++ b/web/modules/webform/src/Plugin/WebformElement/Fieldset.php @@ -20,8 +20,15 @@ class Fieldset extends ContainerBase { */ public function getDefaultProperties() { return [ + // Description/Help. 'help' => '', + 'help_title' => '', + 'description' => '', + 'more' => '', + 'more_title' => '', + // Title. 'title_display' => '', + 'description_display' => '', ] + parent::getDefaultProperties(); } diff --git a/web/modules/webform/src/Plugin/WebformElement/Hidden.php b/web/modules/webform/src/Plugin/WebformElement/Hidden.php index b243be1764..171bbc0b86 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Hidden.php +++ b/web/modules/webform/src/Plugin/WebformElement/Hidden.php @@ -2,6 +2,10 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\webform\WebformInterface; + +use Drupal\Core\Form\FormStateInterface; + /** * Provides a 'hidden' element. * @@ -22,7 +26,7 @@ public function getDefaultProperties() { return [ // Element settings. 'title' => '', - 'value' => '', + 'default_value' => '', 'prepopulate' => FALSE, ]; } @@ -34,4 +38,32 @@ public function preview() { return []; } + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Hidden elements should never get a test value. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Remove the default section under the advanced tab. + unset($form['default']); + + // Add the default value textarea to the element's main settings. + $form['element']['default_value'] = [ + '#type' => 'textarea', + '#title' => $this->t('Default value'), + '#description' => $this->t('The default value of the webform element.'), + '#maxlength' => NULL, + ]; + + return $form; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/Item.php b/web/modules/webform/src/Plugin/WebformElement/Item.php index c17cf7179d..f65f12977a 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Item.php +++ b/web/modules/webform/src/Plugin/WebformElement/Item.php @@ -26,6 +26,7 @@ public function getDefaultProperties() { 'title' => '', // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -42,8 +43,8 @@ public function getDefaultProperties() { /** * {@inheritdoc} */ - public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - parent::prepare($element, $webform_submission); + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); $element['#element_validate'][] = [get_class($this), 'validateItem']; } diff --git a/web/modules/webform/src/Plugin/WebformElement/NumericBase.php b/web/modules/webform/src/Plugin/WebformElement/NumericBase.php index 01ccc554e2..96d5bae399 100644 --- a/web/modules/webform/src/Plugin/WebformElement/NumericBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/NumericBase.php @@ -61,14 +61,14 @@ public function form(array $form, FormStateInterface $form_state) { $form['number']['number_container'] = $this->getFormInlineContainer(); $form['number']['number_container']['min'] = [ '#type' => 'number', - '#title' => $this->t('Min'), + '#title' => $this->t('Minimum'), '#description' => $this->t('Specifies the minimum value.'), '#step' => 'any', '#size' => 4, ]; $form['number']['number_container']['max'] = [ '#type' => 'number', - '#title' => $this->t('Max'), + '#title' => $this->t('Maximum'), '#description' => $this->t('Specifies the maximum value.'), '#step' => 'any', '#size' => 4, diff --git a/web/modules/webform/src/Plugin/WebformElement/OptionsBase.php b/web/modules/webform/src/Plugin/WebformElement/OptionsBase.php index d0169efbba..76f8055581 100644 --- a/web/modules/webform/src/Plugin/WebformElement/OptionsBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/OptionsBase.php @@ -2,11 +2,9 @@ namespace Drupal\webform\Plugin\WebformElement; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\Markup; -use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\Plugin\WebformElementBase; @@ -19,6 +17,8 @@ */ abstract class OptionsBase extends WebformElementBase { + use TextBaseTrait; + /** * Export delta for multiple options. * @@ -39,9 +39,8 @@ abstract class OptionsBase extends WebformElementBase { public function getDefaultProperties() { $properties = parent::getDefaultProperties(); - // Issue #2836374: Wrapper attributes are not supported by composite - // elements, this includes radios, checkboxes, and buttons. - if (preg_match('/(radios|checkboxes|buttons|tableselect|tableselect_sort|table_sort)$/', $this->getPluginId())) { + // Wrapper attributes are not supported by table elements. + if (preg_match('/(tableselect|tableselect_sort|table_sort)$/', $this->getPluginId())) { unset($properties['wrapper_attributes']); } @@ -61,10 +60,10 @@ public function getDefaultProperties() { // Add other properties to elements that include the other text field. if ($this->isOptionsOther()) { $properties += [ - 'other__option_label' => $this->t('Other...'), + 'other__option_label' => $this->t('Other…'), 'other__type' => 'textfield', 'other__title' => '', - 'other__placeholder' => $this->t('Enter other...'), + 'other__placeholder' => $this->t('Enter other…'), 'other__description' => '', // Text field or textarea. 'other__size' => '', @@ -77,6 +76,14 @@ public function getDefaultProperties() { 'other__min' => '', 'other__max' => '', 'other__step' => '', + // Counter. + 'other__counter_type' => '', + 'other__counter_minimum' => '', + 'other__counter_minimum_message' => '', + 'other__counter_maximum' => '', + 'other__counter_maximum_message' => '', + // Wrapper. + 'wrapper_type' => 'fieldset', ]; } @@ -187,7 +194,7 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // Randomize options. if (isset($element['#options']) && !empty($element['#options_randomize'])) { - $element['#options'] = WebformArrayHelper::shuffle($element['#options']); + $element['#options'] = WebformElementHelper::randomize($element['#options']); } // Options description display must be set to trigger the description display. @@ -215,7 +222,7 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#options'][$default_value] = $default_value; } } - } + } } // If the element is #required and the #default_value is an empty string @@ -226,6 +233,16 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub } } + /** + * {@inheritdoc} + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + if ($this->hasMultipleValues($element)) { + $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; + } + parent::prepareElementValidateCallbacks($element, $webform_submission); + } + /** * {@inheritdoc} */ @@ -491,14 +508,13 @@ public function buildExportRecord(array $element, WebformSubmissionInterface $we * Form API callback. Remove unchecked options from value array. */ public static function validateMultipleOptions(array &$element, FormStateInterface $form_state, array &$completed_form) { - $name = $element['#name']; - $values = $form_state->getValue($name) ?: []; + $values = $element['#value'] ?: []; // Filter unchecked/unselected options whose value is 0. $values = array_filter($values, function ($value) { return $value !== 0; }); $values = array_values($values); - $form_state->setValue($name, $values); + $form_state->setValueForElement($element, $values); } /** @@ -548,6 +564,28 @@ public function getElementSelectorOptions(array $element) { } } + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + if ($this->hasMultipleValues($element) && $this->hasMultipleWrapper()) { + return []; + } + + $plugin_id = $this->getPluginId(); + $name = $element['#webform_key']; + $options = OptGroup::flattenOptions($element['#options']); + if ($this->getElementSelectorInputsOptions($element)) { + $other_type = $this->getOptionsOtherType(); + $multiple = ($this->hasMultipleValues($element) && $other_type === 'select') ? '[]' : ''; + return [":input[name=\"{$name}[$other_type]$multiple\"]" => $options]; + } + else { + $multiple = ($this->hasMultipleValues($element) && strpos($plugin_id, 'select') !== FALSE) ? '[]' : ''; + return [":input[name=\"$name$multiple\"]" => $options]; + } + } + /** * {@inheritdoc} */ @@ -556,24 +594,36 @@ public function getElementSelectorInputValue($selector, $trigger, array $element $input_name = WebformSubmissionConditionsValidator::getSelectorInputName($selector); $other_type = WebformSubmissionConditionsValidator::getInputNameAsArray($input_name, 1); $value = $this->getRawValue($element, $webform_submission); + + // Handle edge case where the other element's value has + // not been processed. + // @see https://www.drupal.org/project/webform/issues/3000202 + /** @var \Drupal\webform\Element\WebformOtherBase $class */ + $class = $this->getFormElementClassDefinition(); + $type = $class::getElementType(); + if (is_array($value) && count($value) === 2 && isset($value[$type]) && isset($value['other'])) { + $value = $class::processValue($element, $value); + } + + $options = OptGroup::flattenOptions($element['#options']); if ($other_type === 'other') { if ($this->hasMultipleValues($element)) { - $other_value = array_diff($value, array_keys($element['#options'])); + $other_value = array_diff($value, array_keys($options)); return ($other_value) ? implode(', ', $other_value) : NULL; } else { // Make sure other value is not valid option. - return ($value && !isset($element['#options'][$value])) ? $value : NULL; + return ($value && !isset($options[$value])) ? $value : NULL; } } else { if ($this->hasMultipleValues($element)) { // Return array of valid #options. - return array_intersect($value, array_keys($element['#options'])); + return array_intersect($value, array_keys($options)); } else { // Return valid #option. - return (isset($element['#options'][$value])) ? $value : NULL; + return (isset($options[$value])) ? $value : NULL; } } } @@ -588,13 +638,13 @@ public function getElementSelectorInputValue($selector, $trigger, array $element public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); - $form['default']['default_value']['#description'] = $this->t('The default value of the field identified by its key.'); + $form['default']['default_value']['#description'] .= ' ' . $this->t('The default value of the field identified by its key.'); // Issue #2836374: Wrapper attributes are not supported by composite // elements, this includes radios, checkboxes, and buttons. if (preg_match('/(radios|checkboxes|buttons)/', $this->getPluginId())) { $t_args = [ - '@name' => Unicode::strtolower($this->getPluginLabel()), + '@name' => mb_strtolower($this->getPluginLabel()), ':href' => 'https://www.drupal.org/node/2836364', ]; $form['element_attributes']['#description'] = $this->t('Please note: That the below custom element attributes will also be applied to the @name fieldset wrapper. (<a href=":href">Issue #2836374</a>)', $t_args); @@ -645,9 +695,9 @@ public function form(array $form, FormStateInterface $form_state) { $default_empty_option = $this->configFactory->get('webform.settings')->get('element.default_empty_option'); if ($default_empty_option) { $default_empty_option_required = $this->configFactory->get('webform.settings')->get('element.default_empty_option_required') ?: $this->t('- Select -'); - $form['options']['empty_option']['#description'] .= '<br />' . $this->t('Required elements default to: %required', ['%required' => $default_empty_option_required]); + $form['options']['empty_option']['#description'] .= '<br />' . $this->t('Required elements defaults to: %required', ['%required' => $default_empty_option_required]); $default_empty_option_optional = $this->configFactory->get('webform.settings')->get('element.default_empty_option_optional') ?: $this->t('- None -'); - $form['options']['empty_option']['#description'] .= '<br />' . $this->t('Optional elements default to: %optional', ['%optional' => $default_empty_option_optional]); + $form['options']['empty_option']['#description'] .= '<br />' . $this->t('Optional elements defaults to: %optional', ['%optional' => $default_empty_option_optional]); } $form['options']['empty_value'] = [ '#type' => 'textfield', @@ -676,6 +726,13 @@ public function form(array $form, FormStateInterface $form_state) { [':input[name="properties[other__type]"]' => ['value' => 'number']], ], ]; + $states_textbase = [ + 'visible' => [ + [':input[name="properties[other__type]"]' => ['value' => 'textfield']], + 'or', + [':input[name="properties[other__type]"]' => ['value' => 'textarea']], + ], + ]; $states_textarea = [ 'visible' => [ ':input[name="properties[other__type]"]' => ['value' => 'textarea'], @@ -760,7 +817,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['options_other']['other__number_container'] = $this->getFormInlineContainer(); $form['options_other']['other__number_container']['other__min'] = [ '#type' => 'number', - '#title' => $this->t('Other min'), + '#title' => $this->t('Other minimum'), '#description' => $this->t('Specifies the minimum value.'), '#step' => 'any', '#size' => 4, @@ -768,7 +825,7 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['options_other']['other__number_container']['other__max'] = [ '#type' => 'number', - '#title' => $this->t('Other max'), + '#title' => $this->t('Other maximum'), '#description' => $this->t('Specifies the maximum value.'), '#step' => 'any', '#size' => 4, @@ -783,6 +840,11 @@ public function form(array $form, FormStateInterface $form_state) { '#states' => $states_number, ]; + $form['options_other']['other__textbase_container'] = [ + '#type' => 'container', + '#states' => $states_textbase, + ] + $this->buildCounterForm('other__', 'Other count'); + // Add hide/show #format_items based on #multiple. if ($this->supportsMultipleValues() && $this->hasProperty('multiple')) { $form['display']['format_items']['#states'] = [ diff --git a/web/modules/webform/src/Plugin/WebformElement/PasswordConfirm.php b/web/modules/webform/src/Plugin/WebformElement/PasswordConfirm.php index 5e7d91302b..60d3ab2ef6 100644 --- a/web/modules/webform/src/Plugin/WebformElement/PasswordConfirm.php +++ b/web/modules/webform/src/Plugin/WebformElement/PasswordConfirm.php @@ -19,14 +19,38 @@ */ class PasswordConfirm extends Password { + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'wrapper_type' => 'fieldset', + ] + parent::getDefaultProperties(); + } + /** * {@inheritdoc} */ public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { parent::prepare($element, $webform_submission); + $element['#theme_wrappers'] = []; + } + + /** + * {@inheritdoc} + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); $element['#element_validate'][] = [get_class($this), 'validatePasswordConfirm']; } + /** + * {@inheritdoc} + */ + protected function prepareElementPreRenderCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + $element['#pre_render'] = [[get_called_class(), 'preRenderWebformCompositeFormElement']]; + } + /** * {@inheritdoc} */ @@ -60,9 +84,23 @@ public function setDefaultValue(array &$element) { * Form API callback. Convert password confirm array to single value. */ public static function validatePasswordConfirm(array &$element, FormStateInterface $form_state, array &$completed_form) { - $name = $element['#name']; - $value = $form_state->getValue($name); - $form_state->setValue($name, $value['pass1']); + $value = $element['#value']; + $form_state->setValueForElement($element, $value['pass1']); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Remove unsupported title and description display from composite elements. + if ($this->isComposite()) { + unset($form['form']['display_container']['title_display']['#options']['inline']); + unset($form['form']['display_container']['description_display']['#options']['tooltip']); + } + + return $form; } } diff --git a/web/modules/webform/src/Plugin/WebformElement/ProcessedText.php b/web/modules/webform/src/Plugin/WebformElement/ProcessedText.php index deb1ef30e3..4ce01334e9 100644 --- a/web/modules/webform/src/Plugin/WebformElement/ProcessedText.php +++ b/web/modules/webform/src/Plugin/WebformElement/ProcessedText.php @@ -38,6 +38,7 @@ public function getDefaultProperties() { return [ 'wrapper_attributes' => [], + 'label_attributes' => [], // Markup settings. 'text' => '', 'format' => $default_format , @@ -83,7 +84,7 @@ public function form(array $form, FormStateInterface $form_state) { // which closes the original modal. // @todo Remove the below workaround once this issue is resolved. if (!$form_state->getUserInput() && \Drupal::currentUser()->hasPermission('administer webform')) { - drupal_set_message($this->t('Processed text element can not be opened within a modal. Please see <a href="https://www.drupal.org/node/2741877">Issue #2741877: Nested modals don\'t work</a>.'), 'warning'); + $this->messenger()->addWarning($this->t('Processed text element can not be opened within a modal. Please see <a href="https://www.drupal.org/node/2741877">Issue #2741877: Nested modals don\'t work</a>.')); } $form = parent::form($form, $form_state); diff --git a/web/modules/webform/src/Plugin/WebformElement/Radios.php b/web/modules/webform/src/Plugin/WebformElement/Radios.php index a44a5766f6..f7e42142df 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Radios.php +++ b/web/modules/webform/src/Plugin/WebformElement/Radios.php @@ -27,6 +27,8 @@ public function getDefaultProperties() { 'options_description_display' => 'description', // iCheck settings. 'icheck' => '', + // Wrapper. + 'wrapper_type' => 'fieldset', ] + parent::getDefaultProperties(); } diff --git a/web/modules/webform/src/Plugin/WebformElement/Range.php b/web/modules/webform/src/Plugin/WebformElement/Range.php index 821802a038..8ee29bb576 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Range.php +++ b/web/modules/webform/src/Plugin/WebformElement/Range.php @@ -57,8 +57,6 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub '#max' => $this->getDefaultProperty('max'), ]; - $element['#element_validate'][] = [get_class($this), 'validateRange']; - // If no custom range output is defined then exit. if (empty($element['#output'])) { return; @@ -83,6 +81,9 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub // Create output (number) element. $output = [ '#type' => 'number', + '#title' => $element['#title'], + '#title_display' => 'invisible', + '#id' => $webform_key . '__output', '#name' => $webform_key . '__output', ]; @@ -90,7 +91,7 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $properties = ['#min', '#max', '#step', '#disabled']; $output += array_intersect_key($element, array_combine($properties, $properties)); - // Copy custom output properties to ouput element. + // Copy custom output properties to output element. foreach ($element as $key => $value) { if (strpos($key, '#output__') === 0) { $output_key = str_replace('#output__', '#', $key); @@ -149,6 +150,14 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#attached']['library'][] = 'webform/webform.element.range'; } + /** + * {@inheritdoc} + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); + $element['#element_validate'][] = [get_class($this), 'validateRange']; + } + /** * {@inheritdoc} */ @@ -223,10 +232,9 @@ public function form(array $form, FormStateInterface $form_state) { * @see \Drupal\Core\Render\Element\Range::valueCallback */ public static function validateRange(array &$element, FormStateInterface $form_state, array &$completed_form) { - $name = $element['#name']; - $value = $form_state->getValue($name); + $value = $element['#value']; $value = ($value === 0) ? '0' : (string) $value; - $form_state->setValue($name, $value); + $form_state->setValueForElement($element, $value); } } diff --git a/web/modules/webform/src/Plugin/WebformElement/Select.php b/web/modules/webform/src/Plugin/WebformElement/Select.php index 6ebf30b47d..b66c44f1b8 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Select.php +++ b/web/modules/webform/src/Plugin/WebformElement/Select.php @@ -3,7 +3,6 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\Core\Form\FormStateInterface; -use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\WebformSubmissionInterface; /** @@ -31,6 +30,7 @@ public function getDefaultProperties() { 'empty_value' => '', 'select2' => FALSE, 'chosen' => FALSE, + 'placeholder' => '', ] + parent::getDefaultProperties(); } @@ -64,13 +64,36 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub } } - if (!empty($element['#multiple'])) { - $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; + // If select2 or chosen is not available, see if we can use the alternative. + if (isset($element['#select2']) + && !$this->librariesManager->isIncluded('jquery.select2') + && $this->librariesManager->isIncluded('jquery.chosen')) { + $element['#chosen'] = TRUE; + } + elseif (isset($element['#chosen']) + && !$this->librariesManager->isIncluded('jquery.chosen') + && $this->librariesManager->isIncluded('jquery.select2')) { + $element['#select2'] = TRUE; } - parent::prepare($element, $webform_submission); + // Enhance select element using select2 or chosen. + if (isset($element['#select2']) && $this->librariesManager->isIncluded('jquery.select2')) { + $element['#attached']['library'][] = 'webform/webform.element.select2'; + $element['#attributes']['class'][] = 'js-webform-select2'; + $element['#attributes']['class'][] = 'webform-select2'; + } + elseif (isset($element['#chosen']) && $this->librariesManager->isIncluded('jquery.chosen')) { + $element['#attached']['library'][] = 'webform/webform.element.chosen'; + $element['#attributes']['class'][] = 'js-webform-chosen'; + $element['#attributes']['class'][] = 'webform-chosen'; + } - WebformElementHelper::enhanceSelect($element); + // Set placeholder as data attributes for select2 or chosen elements. + if (!empty($element['#placeholder'])) { + $element['#attributes']['data-placeholder'] = $element['#placeholder']; + } + + parent::prepare($element, $webform_submission); } /** @@ -78,6 +101,9 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + + // Select2 and/or Chosen enhancements. + // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::form $form['options']['select2'] = [ '#type' => 'checkbox', '#title' => $this->t('Select2'), @@ -102,7 +128,6 @@ public function form(array $form, FormStateInterface $form_state) { ':input[name="properties[select2]"]' => ['checked' => TRUE], ], ], - ]; if ($this->librariesManager->isExcluded('jquery.chosen')) { $form['options']['chosen']['#access'] = FALSE; @@ -111,10 +136,36 @@ public function form(array $form, FormStateInterface $form_state) { $form['options']['select_message'] = [ '#type' => 'webform_message', '#message_type' => 'warning', - '#message_message' => $this->t('Select2 and Chosen provide very similar functionality, only one can enabled.'), + '#message_message' => $this->t('Select2 and Chosen provide very similar functionality, only one should be enabled.'), '#access' => TRUE, ]; } + + // Add states to placeholder if custom library is supported and the + // select menu supports multiple values. + $placeholder_states = []; + if (!$this->librariesManager->isExcluded('jquery.select2')) { + $placeholder_states[] = [':input[name="properties[select2]"]' => ['checked' => TRUE]]; + } + if (!$this->librariesManager->isExcluded('jquery.chosen')) { + if (isset($form['form']['placeholder']['#states']['visible'])) { + $placeholder_states[] = 'or'; + } + $placeholder_states[] = [':input[name="properties[chosen]"]' => ['checked' => TRUE]]; + } + if ($placeholder_states) { + $form['form']['placeholder']['#states']['visible'] = [ + [ + ':input[name="properties[multiple][container][cardinality]"]' => ['value' => 'number'], + ':input[name="properties[multiple][container][cardinality_number]"]' => ['!value' => 1], + ], + $placeholder_states, + ]; + } + else { + $form['form']['placeholder']['#access'] = FALSE; + } + return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/Table.php b/web/modules/webform/src/Plugin/WebformElement/Table.php index bc5e6d1308..2cb04f008e 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Table.php +++ b/web/modules/webform/src/Plugin/WebformElement/Table.php @@ -6,7 +6,6 @@ use Drupal\Core\Render\Element; use Drupal\webform\Plugin\WebformElementBase; use Drupal\webform\WebformInterface; -use Drupal\Component\Utility\Unicode; use Drupal\webform\WebformSubmissionInterface; /** @@ -158,7 +157,7 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we // Add divider between (optional) header. if (!empty($element['#header'])) { $lines = explode(PHP_EOL, trim($html)); - $lines[0] .= PHP_EOL . str_repeat('-', Unicode::strlen($lines[0])); + $lines[0] .= PHP_EOL . str_repeat('-', mb_strlen($lines[0])); $html = implode(PHP_EOL, $lines); } diff --git a/web/modules/webform/src/Plugin/WebformElement/Telephone.php b/web/modules/webform/src/Plugin/WebformElement/Telephone.php index a1ee17ba17..2c02ad15aa 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Telephone.php +++ b/web/modules/webform/src/Plugin/WebformElement/Telephone.php @@ -2,6 +2,7 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\webform\Element\WebformMessage as WebformMessageElement; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Locale\CountryManager; use Drupal\webform\WebformInterface; @@ -12,10 +13,11 @@ * * @WebformElement( * id = "tel", - * api = "https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!Element!Tel.php/class/Tel", - * label = @Translation("Telephone"), - * description = @Translation("Provides a form element for entering a telephone number."), - * category = @Translation("Advanced elements"), + * api = + * "https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!Element!Tel.php/class/Tel", + * label = @Translation("Telephone"), description = @Translation("Provides a + * form element for entering a telephone number."), category = + * @Translation("Advanced elements"), * ) */ class Telephone extends TextBase { @@ -24,11 +26,23 @@ class Telephone extends TextBase { * {@inheritdoc} */ public function getDefaultProperties() { - return [ - 'multiple' => FALSE, - 'international' => FALSE, - 'international_initial_country' => '', - ] + parent::getDefaultProperties(); + $properties = [ + 'input_hide' => FALSE, + 'multiple' => FALSE, + 'international' => FALSE, + 'international_initial_country' => '', + ] + parent::getDefaultProperties(); + + // Add support for telephone_validation.module. + if (\Drupal::moduleHandler()->moduleExists('telephone_validation')) { + $properties += [ + 'telephone_validation_format' => '', + 'telephone_validation_country' => '', + 'telephone_validation_countries' => [], + ]; + } + + return $properties; } /** @@ -45,6 +59,41 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub if (!empty($element['#international_initial_country'])) { $element['#attributes']['data-webform-telephone-international-initial-country'] = $element['#international_initial_country']; } + + // The utilsScript is fetched when the page has finished loading to + // prevent blocking. + // @see https://github.com/jackocnr/intl-tel-input + $utils_script = '/libraries/jquery.intl-tel-input/build/js/utils.js'; + // Load utils.js from CDN defined in webform.libraries.yml. + if (!file_exists(DRUPAL_ROOT . $utils_script)) { + /** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */ + $library_discovery = \Drupal::service('library.discovery'); + $intl_tel_input_library = $library_discovery->getLibraryByName('webform', 'libraries.jquery.intl-tel-input'); + $cdn = reset($intl_tel_input_library['cdn']); + $utils_script = $cdn . 'build/js/utils.js'; + } + $element['#attached']['drupalSettings']['webform']['intlTelInput']['utilsScript'] = $utils_script; + } + + // Add support for telephone_validation.module. + if (\Drupal::moduleHandler()->moduleExists('telephone_validation')) { + $format = $this->getElementProperty($element, 'telephone_validation_format'); + if ($format === \libphonenumber\PhoneNumberFormat::NATIONAL) { + $country = (array) $this->getElementProperty($element, 'telephone_validation_country'); + } + else { + $country = $this->getElementProperty($element, 'telephone_validation_countries'); + } + if ($format !== '') { + $element['#element_validate'][] = [ + 'Drupal\telephone_validation\Render\Element\TelephoneValidation', + 'validateTel', + ]; + $element['#element_validate_settings'] = [ + 'format' => $format, + 'country' => $country, + ]; + } } } @@ -68,8 +117,8 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'select', '#empty_option' => $this->t('- None -'), '#options' => [ - 'auto' => $this->t('Auto detect'), - ] + CountryManager::getStandardList(), + 'auto' => $this->t('Auto detect'), + ] + CountryManager::getStandardList(), '#states' => [ 'visible' => [':input[name="properties[international]"]' => ['checked' => TRUE]], ], @@ -79,6 +128,62 @@ public function form(array $form, FormStateInterface $form_state) { $form['telephone']['international']['#access'] = FALSE; $form['telephone']['international_initial_country']['#access'] = FALSE; } + + // Add support for telephone_validation.module. + if (\Drupal::moduleHandler()->moduleExists('telephone_validation')) { + $form['telephone']['telephone_validation_format'] = [ + '#type' => 'select', + '#title' => $this->t('Valid format'), + '#description' => $this->t('For international telephone numbers we suggest using <a href=":href">E164</a> format.', [':href' => 'https://en.wikipedia.org/wiki/E.164']), + '#empty_option' => $this->t('- None -'), + '#options' => [ + \libphonenumber\PhoneNumberFormat::E164 => $this->t('E164'), + \libphonenumber\PhoneNumberFormat::NATIONAL => $this->t('National'), + ], + ]; + $form['telephone']['telephone_validation_country'] = [ + '#type' => 'select', + '#title' => $this->t('Valid country'), + '#options' => \Drupal::service('telephone_validation.validator') + ->getCountryList(), + '#states' => [ + 'visible' => [ + ':input[name="properties[telephone_validation_format]"]' => ['value' => \libphonenumber\PhoneNumberFormat::NATIONAL], + ], + 'required' => [ + ':input[name="properties[telephone_validation_format]"]' => ['value' => \libphonenumber\PhoneNumberFormat::NATIONAL], + ], + ], + ]; + $form['telephone']['telephone_validation_countries'] = [ + '#type' => 'select', + '#title' => $this->t('Valid countries'), + '#description' => t('If no country selected all countries are valid.'), + '#options' => \Drupal::service('telephone_validation.validator') + ->getCountryList(), + '#select2' => TRUE, + '#multiple' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="properties[telephone_validation_format]"]' => ['value' => \libphonenumber\PhoneNumberFormat::E164], + ], + ], + ]; + $this->elementManager->processElement($form['telephone']['telephone_validation_countries']); + } + elseif (\Drupal::currentUser()->hasPermission('administer modules')) { + $t_args = [':href' => 'https://www.drupal.org/project/telephone_validation']; + $form['telephone']['telephone_validation_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_message' => $this->t('Install the <a href=":href">Telephone validation</a> module which provides international phone number validation.', $t_args), + '#message_id' => 'webform.telephone_validation_message', + '#message_close' => TRUE, + '#message_storage' => WebformMessageElement::STORAGE_STATE, + '#access' => TRUE, + ]; + } + return $form; } @@ -95,17 +200,6 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we $format = $this->getItemFormat($element); switch ($format) { case 'link': - /**********************************************************************/ - // Issue #2484693: Telephone Link field formatter breaks Drupal with 5 - // digits or less in the number - // return [ - // '#type' => 'link', - // '#title' => $value, - // '#url' => \Drupal::pathValidator()->getUrlIfValid('tel:' . $value), - // ]; - // Workaround: Manually build a static HTML link. - /**********************************************************************/ - $t_args = [':tel' => 'tel:' . $value, '@tel' => $value]; return t('<a href=":tel">@tel</a>', $t_args); @@ -126,8 +220,8 @@ public function getItemDefaultFormat() { */ public function getItemFormats() { return parent::getItemFormats() + [ - 'link' => $this->t('Link'), - ]; + 'link' => $this->t('Link'), + ]; } /** @@ -135,8 +229,8 @@ public function getItemFormats() { */ public function preview() { return parent::preview() + [ - '#international' => TRUE, - ]; + '#international' => TRUE, + ]; } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/TextBase.php b/web/modules/webform/src/Plugin/WebformElement/TextBase.php index 5bfff9b95d..f006e649f8 100644 --- a/web/modules/webform/src/Plugin/WebformElement/TextBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/TextBase.php @@ -2,9 +2,9 @@ namespace Drupal\webform\Plugin\WebformElement; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Plugin\WebformElementBase; +use Drupal\webform\Utility\WebformTextHelper; use Drupal\webform\WebformSubmissionInterface; /** @@ -12,6 +12,8 @@ */ abstract class TextBase extends WebformElementBase { + use TextBaseTrait; + /** * {@inheritdoc} */ @@ -24,6 +26,7 @@ public function getDefaultProperties() { 'placeholder' => '', 'autocomplete' => 'on', 'pattern' => '', + 'pattern_error' => '', ] + parent::getDefaultProperties(); } @@ -31,7 +34,7 @@ public function getDefaultProperties() { * {@inheritdoc} */ public function getTranslatableProperties() { - return array_merge(parent::getTranslatableProperties(), ['counter_message']); + return array_merge(parent::getTranslatableProperties(), ['counter_minimum_message', 'counter_maximum_message', 'pattern_error']); } /** @@ -41,16 +44,32 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub parent::prepare($element, $webform_submission); // Counter. - if (!empty($element['#counter_type']) && !empty($element['#counter_maximum']) && $this->librariesManager->isIncluded('jquery.word-and-character-counter')) { + if (!empty($element['#counter_type']) + && (!empty($element['#counter_minimum']) || !empty($element['#counter_maximum'])) + && $this->librariesManager->isIncluded('jquery.textcounter')) { + // Apply character min/max to min/max length. if ($element['#counter_type'] == 'character') { - $element['#maxlength'] = $element['#counter_maximum']; + if (!empty($element['#counter_minimum'])) { + $element['#minlength'] = $element['#counter_minimum']; + } + if (!empty($element['#counter_maximum'])) { + $element['#maxlength'] = $element['#counter_maximum']; + } } - $element['#attributes']['data-counter-type'] = $element['#counter_type']; - $element['#attributes']['data-counter-limit'] = $element['#counter_maximum']; - if (!empty($element['#counter_message'])) { - $element['#attributes']['data-counter-message'] = $element['#counter_message']; + // Set 'data-counter-*' attributes using '#counter_*' properties. + $data_attributes = [ + 'counter_type', + 'counter_minimum', + 'counter_minimum_message', + 'counter_maximum', + 'counter_maximum_message', + ]; + foreach ($data_attributes as $data_attribute) { + if (!empty($element['#' . $data_attribute])) { + $element['#attributes']['data-' . str_replace('_', '-', $data_attribute)] = $element['#' . $data_attribute]; + } } $element['#attributes']['class'][] = 'js-webform-counter'; @@ -71,10 +90,38 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub else { $element['#attributes']['data-inputmask-mask'] = $input_mask; } + // Set input mask #pattern. + $input_masks = $this->getInputMasks(); + if (isset($input_masks[$input_mask]) + && isset($input_masks[$input_mask]['pattern']) && + empty($element['#pattern'])) { + $element['#pattern'] = $input_masks[$input_mask]['pattern']; + } $element['#attributes']['class'][] = 'js-webform-input-mask'; $element['#attached']['library'][] = 'webform/webform.element.inputmask'; } + + // Input hiding. + if (!empty($element['#input_hide'])) { + $element['#attributes']['class'][] = 'js-webform-input-hide'; + $element['#attached']['library'][] = 'webform/webform.element.inputhide'; + } + + // Pattern validation. + // This override core's pattern validation to support unicode + // and a custom error message. + if (isset($element['#pattern'])) { + $element['#attributes']['pattern'] = $element['#pattern']; + $element['#element_validate'][] = [get_called_class(), 'validatePattern']; + + // Set pattern error message using #pattern_error. + // @see Drupal.behaviors.webformRequiredError + // @see webform.form.js + if (!empty($element['#pattern_error'])) { + $element['#attributes']['data-webform-pattern-error'] = $element['#pattern_error']; + } + } } /** @@ -88,33 +135,24 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_select_other', '#title' => $this->t('Input masks'), '#description' => $this->t('An <a href=":href">inputmask</a> helps the user with the element by ensuring a predefined format.', [':href' => 'https://github.com/RobinHerbots/jquery.inputmask']), - '#other__option_label' => $this->t('Custom...'), - '#other__placeholder' => $this->t('Enter input mask...'), + '#other__option_label' => $this->t('Custom…'), + '#other__placeholder' => $this->t('Enter input mask…'), '#other__description' => $this->t('(9 = numeric; a = alphabetical; * = alphanumeric)'), '#empty_option' => $this->t('- None -'), - '#options' => [ - 'Basic' => [ - "'alias': 'currency'" => $this->t('Currency - @format', ['@format' => '$ 9.99']), - "'alias': 'mm/dd/yyyy'" => $this->t('Date - @format', ['@format' => 'mm/dd/yyyy']), - "'alias': 'decimal'" => $this->t('Decimal - @format', ['@format' => '1.234']), - "'alias': 'email'" => $this->t('Email - @format', ['@format' => 'example@example.com']), - "'alias': 'percentage'" => $this->t('Percentage - @format', ['@format' => '99%']), - '(999) 999-9999' => $this->t('Phone - @format', ['@format' => '(999) 999-9999']), - '99999[-9999]' => $this->t('Zip code - @format', ['@format' => '99999[-9999]']), - ], - 'Advanced' => [ - "'alias': 'ip'" => $this->t('IP address - @format', ['@format' => '255.255.255.255']), - '[9-]AAA-999' => $this->t('License plate - @format', ['@format' => '[9-]AAA-999']), - "'alias': 'mac'" => $this->t('MAC addresses - @format', ['@format' => '99-99-99-99-99-99']), - '999-99-9999' => $this->t('SSN - @format', ['@format' => '999-99-9999']), - "'alias': 'vin'" => 'VIN (Vehicle identification number)', - ], - ], + '#options' => $this->getInputMaskOptions(), ]; if ($this->librariesManager->isExcluded('jquery.inputmask')) { $form['form']['input_mask']['#access'] = FALSE; } + // Input hiding. + $form['form']['input_hide'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Input hiding'), + '#description' => $this->t('Hide the input of the element when the input is not being focused.'), + '#return_value' => TRUE, + ]; + // Pattern. $form['validation']['pattern'] = [ '#type' => 'webform_checkbox_value', @@ -122,46 +160,19 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('A <a href=":href">regular expression</a> that the element\'s value is checked against.', [':href' => 'http://www.w3schools.com/js/js_regexp.asp']), '#value__title' => $this->t('Pattern regular expression'), ]; - - // Counter. - $form['validation']['counter_container'] = $this->getFormInlineContainer(); - $form['validation']['counter_container']['counter_type'] = [ - '#type' => 'select', - '#title' => $this->t('Count'), - '#description' => $this->t('Limit entered value to a maximum number of characters or words.'), - '#empty_option' => $this->t('- None -'), - '#options' => [ - 'character' => $this->t('Characters'), - 'word' => $this->t('Words'), - ], - ]; - $form['validation']['counter_container']['counter_maximum'] = [ - '#type' => 'number', - '#title' => $this->t('Count maximum'), - '#min' => 1, - '#states' => [ - 'invisible' => [ - ':input[name="properties[counter_type]"]' => ['value' => ''], - ], - 'optional' => [ - ':input[name="properties[counter_type]"]' => ['value' => ''], - ], - ], - ]; - $form['validation']['counter_message'] = [ + $form['validation']['pattern_error'] = [ '#type' => 'textfield', - '#title' => $this->t('Count message'), - '#description' => $this->t('Defaults to: %value', ['%value' => $this->t('X characters/word(s) left')]), + '#title' => $this->t('Pattern message'), + '#description' => $this->t('If set, this message will be used when a pattern is not matched, instead of the default "@message" message.', ['@message' => t('%name field is not in the right format.')]), '#states' => [ - 'invisible' => [ - ':input[name="properties[counter_type]"]' => ['value' => ''], + 'visible' => [ + ':input[name="properties[pattern][checkbox]"]' => ['checked' => TRUE], ], ], ]; - if ($this->librariesManager->isExcluded('jquery.word-and-character-counter')) { - $form['validation']['counter_container']['#access'] = FALSE; - $form['validation']['counter_message']['#access'] = FALSE; - } + + // Counter. + $form['validation'] += $this->buildCounterForm(); if (isset($form['form']['maxlength'])) { $form['form']['maxlength']['#description'] .= ' ' . $this->t('If character counter is enabled, maxlength will automatically be set to the count maximum.'); @@ -176,41 +187,68 @@ public function form(array $form, FormStateInterface $form_state) { } /** - * Form API callback. Validate (word/charcter) counter. + * Form API callback. Validate (word/character) counter. */ public static function validateCounter(array &$element, FormStateInterface $form_state) { - $name = $element['#name']; - $value = $form_state->getValue($name); - $type = $element['#counter_type']; - $max = $element['#counter_maximum']; - - // Validate character count. - if ($type == 'character') { - $length = Unicode::strlen($value); - if ($length <= $max) { - return; - } - } - // Validate word count. - elseif ($type == 'word') { - $length = str_word_count($value); - if ($length <= $max) { - return; - } - } - else { + $value = $element['#value']; + if ($value === '') { return; } + $type = $element['#counter_type']; + $max = (!empty($element['#counter_maximum'])) ? $element['#counter_maximum'] : NULL; + $min = (!empty($element['#counter_minimum'])) ? $element['#counter_minimum'] : NULL; + // Display error. // @see \Drupal\Core\Form\FormValidator::performRequiredValidation $t_args = [ + '@type' => ($type == 'character') ? t('characters') : t('words'), '@name' => $element['#title'], '%max' => $max, - '@type' => ($type == 'character') ? t('characters') : t('words'), - '%length' => $length, + '%min' => $min, ]; - $form_state->setError($element, t('@name cannot be longer than %max @type but is currently %length @type long.', $t_args)); + + // Get character/word count. + if ($type === 'character') { + $length = mb_strlen($value); + $t_args['%length'] = $length; + } + // Validate word count. + elseif ($type === 'word') { + $length = WebformTextHelper::wordCount($value); + $t_args['%length'] = $length; + } + + // Validate character/word count. + if ($max && $length > $max) { + $form_state->setError($element, t('@name cannot be longer than %max @type but is currently %length @type long.', $t_args)); + } + elseif ($min && $length < $min) { + $form_state->setError($element, t('@name must be longer than %min @type but is currently %length @type long.', $t_args)); + } + } + + /** + * Form API callback. Validate unicode pattern and display a custom error. + * + * @see https://www.drupal.org/project/drupal/issues/2633550 + */ + public static function validatePattern(&$element, FormStateInterface $form_state, &$complete_form) { + if ($element['#value'] !== '') { + // PHP: Convert JavaScript-escaped Unicode characters to PCRE + // escape sequence format. + // @see https://bytefreaks.net/programming-2/php-programming-2/php-convert-javascript-escaped-unicode-characters-to-html-hex-references˚ + $pcre_pattern = preg_replace('/\\\\u([a-fA-F0-9]{4})/', '\\x{\\1}', $element['#pattern']); + $pattern = '{^(?:' . $pcre_pattern . ')$}u'; + if (!preg_match($pattern, $element['#value'])) { + if (!empty($element['#pattern_error'])) { + $form_state->setError($element, $element['#pattern_error']); + } + else { + $form_state->setError($element, t('%name field is not in the right format.', ['%name' => $element['#title']])); + } + } + } } /** @@ -225,16 +263,119 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form // @see http://stackoverflow.com/questions/4440626/how-can-i-validate-regex if (!empty($properties['#pattern'])) { set_error_handler('_webform_entity_element_validate_rendering_error_handler'); - if (preg_match('{^(?:' . $properties['#pattern'] . ')$}', NULL) === FALSE) { + + // PHP: Convert JavaScript-escaped Unicode characters to PCRE escape + // sequence format. + // @see https://bytefreaks.net/programming-2/php-programming-2/php-convert-javascript-escaped-unicode-characters-to-html-hex-references + $pcre_pattern = preg_replace('/\\\\u([a-fA-F0-9]{4})/', '\\x{\\1}', $properties['#pattern']); + + if (preg_match('{^(?:' . $pcre_pattern . ')$}u', NULL) === FALSE) { $form_state->setErrorByName('pattern', t('Pattern %pattern is not a valid regular expression.', ['%pattern' => $properties['#pattern']])); } + set_error_handler('_drupal_error_handler'); } // Validate #counter_maximum. - if (!empty($properties['#counter_type']) && empty($properties['#counter_maximum'])) { - $form_state->setErrorByName('counter_maximum', t('Counter maximum is required.')); + if (!empty($properties['#counter_type']) && empty($properties['#counter_maximum']) && empty($properties['#counter_minimum'])) { + $form_state->setErrorByName('counter_minimum', t('Counter minimum or maximum is required.')); + $form_state->setErrorByName('counter_maximum', t('Counter minimum or maximum is required.')); + } + } + + /****************************************************************************/ + // Input masks. + /****************************************************************************/ + + /** + * Get input masks. + * + * @return array + * An associative array keyed my input mask contain input mask title, + * example, and patterh. + */ + protected function getInputMasks() { + return [ + "'alias': 'currency'" => [ + 'title' => $this->t('Currency'), + 'example' => '$ 9.99', + 'pattern' => '^\$ [0-9]{1,3}(,[0-9]{3})*.\d\d$', + ], + "'alias': 'datetime'" => [ + 'title' => $this->t('Date'), + 'example' => '2007-06-09\'T\'17:46:21', + ], + "'alias': 'decimal'" => [ + 'title' => $this->t('Decimal'), + 'example' => '1.234', + 'pattern' => '^\d+(\.\d+)?$', + ], + "'alias': 'email'" => [ + 'title' => $this->t('Email'), + 'example' => 'example@example.com', + ], + "'alias': 'ip'" => [ + 'title' => $this->t('IP address'), + 'example' => '255.255.255.255', + 'pattern' => '^\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d$', + ], + '[9-]AAA-999' => [ + 'title' => $this->t('License plate'), + 'example' => '[9-]AAA-999', + ], + "'alias': 'mac'" => [ + 'title' => $this->t('MAC address'), + 'example' => '99-99-99-99-99-99', + 'pattern' => '^\d\d-\d\d-\d\d-\d\d-\d\d-\d\d$', + ], + "'alias': 'percentage'" => [ + 'title' => $this->t('Percentage'), + 'example' => '99 %', + 'pattern' => '^\d+ %$', + ], + '(999) 999-9999' => [ + 'title' => $this->t('Phone'), + 'example' => '(999) 999-9999', + 'pattern' => '^\(\d\d\d\) \d\d\d-\d\d\d\d$', + ], + '999-99-9999' => [ + 'title' => $this->t('Social Security Number (SSN)'), + 'example' => '999-99-9999', + 'pattern' => '^\d\d\d-\d\d-\d\d\d\d$', + ], + "'alias': 'vin'" => [ + 'title' => $this->t('Vehicle identification number (VIN)'), + 'example' => 'JA3AY11A82U020534', + ], + '99999[-9999]' => [ + 'title' => $this->t('ZIP Code'), + 'example' => '99999[-9999]', + 'pattern' => '^\d\d\d\d\d(-\d\d\d\d)?$', + ], + "'casing': 'upper'" => [ + 'title' => $this->t('Uppercase'), + 'example' => 'UPPERCASE', + ], + "'casing': 'lower'" => [ + 'title' => $this->t('Lowercase '), + 'example' => 'lowercase', + ], + ]; + } + + /** + * Get input masks as select menu options. + * + * @return array + * An associative array of options. + */ + protected function getInputMaskOptions() { + $input_masks = $this->getInputMasks(); + $options = []; + foreach ($input_masks as $input_mask => $settings) { + $options[$input_mask] = $settings['title'] . (!empty($settings['example']) ? ' - ' . $settings['example'] : ''); } + return $options; } } diff --git a/web/modules/webform/src/Plugin/WebformElement/TextBaseTrait.php b/web/modules/webform/src/Plugin/WebformElement/TextBaseTrait.php new file mode 100644 index 0000000000..3e8b093b61 --- /dev/null +++ b/web/modules/webform/src/Plugin/WebformElement/TextBaseTrait.php @@ -0,0 +1,105 @@ +<?php + +namespace Drupal\webform\Plugin\WebformElement; + +/** + * Text base trait contains methods that are applicable to any text elements. + */ +trait TextBaseTrait { + + /** + * Build counter widget used by text elements and other element. + * + * @param string $name + * Property name prefix. + * @param string $title + * Property title prefix. + * + * @return array + * A renderable array containing a counter configuration form. + * + * @see \Drupal\webform\Plugin\WebformElement\TextBase::form + * @see \Drupal\webform\Plugin\WebformElement\OptionsBase::form + */ + public function buildCounterForm($name = '', $title = NULL) { + if ($title === NULL) { + $title = t('Counter'); + } + $t_args = ['@title' => $title]; + + $build[$name . 'counter_type'] = [ + '#type' => 'select', + '#title' => $title, + '#description' => $this->t('Limit entered value to a maximum number of characters or words.'), + '#empty_option' => $this->t('- None -'), + '#options' => [ + 'character' => $this->t('Characters'), + 'word' => $this->t('Words'), + ], + ]; + $build['counter_container'] = $this->getFormInlineContainer(); + $build['counter_container']['#states'] = [ + 'invisible' => [ + ':input[name="properties[' . $name . 'counter_type]"]' => ['value' => ''], + ], + ]; + $build['counter_container'][$name . 'counter_minimum'] = [ + '#type' => 'number', + '#title' => $this->t('@title minimum', $t_args), + '#min' => 1, + '#states' => [ + 'required' => [ + ':input[name="properties[' . $name . 'counter_type]"]' => ['!value' => ''], + ':input[name="properties[' . $name . 'counter_maximum]"]' => ['value' => ''], + ], + ], + ]; + $build['counter_container'][$name . 'counter_maximum'] = [ + '#type' => 'number', + '#title' => $this->t('@title maximum', $t_args), + '#min' => 1, + '#states' => [ + 'required' => [ + ':input[name="properties[' . $name . 'counter_type]"]' => ['!value' => ''], + ':input[name="properties[' . $name . 'counter_minimum]"]' => ['value' => ''], + ], + ], + ]; + $build['counter_message_container'] = [ + '#type' => 'container', + '#states' => [ + 'invisible' => [ + ':input[name="properties[' . $name . 'counter_type]"]' => ['value' => ''], + ], + ], + ]; + $build['counter_message_container'][$name . 'counter_minimum_message'] = [ + '#type' => 'textfield', + '#title' => $this->t('@title minimum message', $t_args), + '#description' => $this->t('Defaults to: %value', ['%value' => $this->t('%d characters/word(s) entered')]), + '#states' => [ + 'visible' => [ + ':input[name="properties[' . $name . 'counter_minimum]"]' => ['!value' => ''], + ':input[name="properties[' . $name . 'counter_maximum]"]' => ['value' => ''], + ], + ], + ]; + $build['counter_message_container'][$name . 'counter_maximum_message'] = [ + '#type' => 'textfield', + '#title' => $this->t('@title maximum message', $t_args), + '#description' => $this->t('Defaults to: %value', ['%value' => $this->t('%d characters/word(s) remaining')]), + '#states' => [ + 'visible' => [ + ':input[name="properties[' . $name . 'counter_maximum]"]' => ['!value' => ''], + ], + ], + ]; + if ($this->librariesManager->isExcluded('jquery.textcounter')) { + $build[$name . 'counter_type']['#access'] = FALSE; + $build[$name . 'counter_container']['#access'] = FALSE; + $build[$name . 'counter_message_container']['#access'] = FALSE; + } + return $build; + } + +} diff --git a/web/modules/webform/src/Plugin/WebformElement/TextField.php b/web/modules/webform/src/Plugin/WebformElement/TextField.php index cecb900dec..7618bfcd5a 100644 --- a/web/modules/webform/src/Plugin/WebformElement/TextField.php +++ b/web/modules/webform/src/Plugin/WebformElement/TextField.php @@ -24,10 +24,13 @@ public function getDefaultProperties() { return [ // Form display. 'input_mask' => '', + 'input_hide' => FALSE, // Form validation. 'counter_type' => '', + 'counter_minimum' => '', + 'counter_minimum_message' => '', 'counter_maximum' => '', - 'counter_message' => '', + 'counter_maximum_message' => '', ] + parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); } @@ -35,7 +38,9 @@ public function getDefaultProperties() { * {@inheritdoc} */ public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - $element['#maxlength'] = (!isset($element['#maxlength'])) ? 255 : $element['#maxlength']; + if (!array_key_exists('#maxlength', $element)) { + $element['#maxlength'] = 255; + } parent::prepare($element, $webform_submission); } diff --git a/web/modules/webform/src/Plugin/WebformElement/TextFormat.php b/web/modules/webform/src/Plugin/WebformElement/TextFormat.php index a8f6fc8a15..fcaee819c0 100644 --- a/web/modules/webform/src/Plugin/WebformElement/TextFormat.php +++ b/web/modules/webform/src/Plugin/WebformElement/TextFormat.php @@ -242,10 +242,54 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form } /** - * Get composite element. + * {@inheritdoc} + */ + public function postSave(array &$element, WebformSubmissionInterface $webform_submission, $update = TRUE) { + $webform = $webform_submission->getWebform(); + if ($webform->isResultsDisabled()) { + return; + } + + // Get current value and original value for this element. + $key = $element['#webform_key']; + + $data = $webform_submission->getData(); + $value = (isset($data[$key]) && isset($data[$key]['value'])) ? $data[$key]['value'] : ''; + $uuids = _webform_parse_file_uuids($value); + + if ($update) { + $original_data = $webform_submission->getOriginalData(); + $original_value = isset($original_data[$key]) ? $original_data[$key]['value'] : ''; + $original_uuids = _webform_parse_file_uuids($original_value); + + // Detect file usages that should be incremented. + $added_files = array_diff($uuids, $original_uuids); + _webform_record_file_usage($added_files, $webform_submission->getEntityTypeId(), $webform_submission->id()); + + // Detect file usages that should be decremented. + $removed_files = array_diff($original_uuids, $uuids); + _webform_delete_file_usage($removed_files, $webform_submission->getEntityTypeId(), $webform_submission->id(), 1); + } + else { + _webform_record_file_usage($uuids, $webform_submission->getEntityTypeId(), $webform_submission->id()); + } + } + + /** + * {@inheritdoc} + */ + public function postDelete(array &$element, WebformSubmissionInterface $webform_submission) { + $key = $element['#webform_key']; + $value = $webform_submission->getElementData($key); + $uuids = _webform_parse_file_uuids($value['value']); + _webform_delete_file_usage($uuids, $webform_submission->getEntityTypeId(), $webform_submission->id(), 0); + } + + /** + * Check if composite element exists. * - * @return array - * A composite sub-elements. + * @return bool + * TRUE if composite element exists. */ public function hasCompositeElement(array $element, $key) { $elements = $this->getCompositeElements(); diff --git a/web/modules/webform/src/Plugin/WebformElement/Textarea.php b/web/modules/webform/src/Plugin/WebformElement/Textarea.php index 35d92aa32f..1eb1bce8c1 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Textarea.php +++ b/web/modules/webform/src/Plugin/WebformElement/Textarea.php @@ -29,6 +29,7 @@ public function getDefaultProperties() { 'default_value' => '', // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -50,10 +51,13 @@ public function getDefaultProperties() { 'unique_entity' => FALSE, 'unique_error' => '', 'counter_type' => '', + 'counter_minimum' => '', + 'counter_minimum_message' => '', 'counter_maximum' => '', - 'counter_message' => '', + 'counter_maximum_message' => '', // Attributes. 'wrapper_attributes' => [], + 'label_attributes' => [], 'attributes' => [], // Submission display. 'format' => $this->getItemDefaultFormat(), @@ -62,23 +66,10 @@ public function getDefaultProperties() { 'format_items' => $this->getItemsDefaultFormat(), 'format_items_html' => '', 'format_items_text' => '', + 'format_attributes' => [], ] + parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); } - /** - * {@inheritdoc} - */ - public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - parent::prepare($element, $webform_submission); - - // @todo Remove once Drupal 8.4.x+ is a dependency. - // Textarea Form API element now supports #maxlength attribute - // @see https://www.drupal.org/node/2887280 - if (!empty($element['#maxlength'])) { - $element['#attributes']['maxlength'] = $element['#maxlength']; - } - } - /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/Url.php b/web/modules/webform/src/Plugin/WebformElement/Url.php index 97f955ba81..d22e943d4e 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Url.php +++ b/web/modules/webform/src/Plugin/WebformElement/Url.php @@ -21,7 +21,10 @@ class Url extends TextBase { * {@inheritdoc} */ public function getDefaultProperties() { - return parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); + return [ + 'input_hide' => FALSE, + ] + parent::getDefaultProperties() + + $this->getDefaultMultipleProperties(); } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/Value.php b/web/modules/webform/src/Plugin/WebformElement/Value.php index 3eaa177d43..fbc84f30f4 100644 --- a/web/modules/webform/src/Plugin/WebformElement/Value.php +++ b/web/modules/webform/src/Plugin/WebformElement/Value.php @@ -2,6 +2,8 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\webform\WebformInterface; + /** * Provides a 'value' element. * @@ -40,4 +42,12 @@ public function getElementSelectorOptions(array $element) { return []; } + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Value elements should never get a test value. + return NULL; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformActions.php b/web/modules/webform/src/Plugin/WebformElement/WebformActions.php index 27a867509c..b604b32f6c 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformActions.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformActions.php @@ -29,9 +29,7 @@ public function getDefaultProperties() { 'title' => '', // Attributes. 'attributes' => [], - // Conditional logic. - 'states' => [], - ]; + ] + $this->getDefaultBaseProperties(); foreach (WebformActionsElement::$buttons as $button) { $properties[$button . '_hide'] = FALSE; $properties[$button . '__label'] = ''; @@ -226,11 +224,17 @@ public function form(array $form, FormStateInterface $form_state) { public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); - /** @var \Drupal\webform\WebformInterface $webform */ - $webform = $form_state->getFormObject()->getWebform(); + /** @var \Drupal\webform_ui\Form\WebformUiElementEditForm $form_object */ + $form_object = $form_state->getFormObject(); + + if (!$form_object->getWebform()->hasActions()) { + $form['element']['title']['#default_value'] = (string) $this->t('Submit button(s)'); + } - if (!$webform->hasActions()) { - $form['element']['title']['#default_value'] = $this->t('Submit button(s)'); + // Hide element settings for default 'actions' to prevent UX confusion. + $key = $form_object->getKey() ?: $form_object->getDefaultKey(); + if ($key === 'actions') { + $form['element']['#access'] = FALSE; } return $form; diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformAddress.php b/web/modules/webform/src/Plugin/WebformElement/WebformAddress.php index 0b0d37b951..d08d9bfc3a 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformAddress.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformAddress.php @@ -19,6 +19,13 @@ */ class WebformAddress extends WebformCompositeBase { + /** + * {@inheritdoc} + */ + public function getPluginLabel() { + return \Drupal::moduleHandler()->moduleExists('address') ? $this->t('Basic address') : parent::getPluginLabel(); + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformCheckboxesOther.php b/web/modules/webform/src/Plugin/WebformElement/WebformCheckboxesOther.php index bcfac7d912..aa82989568 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformCheckboxesOther.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformCheckboxesOther.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\webform\WebformSubmissionInterface; +use Drupal\webform\WebformSubmissionConditionsValidator; /** * Provides a 'checkboxes_other' element. @@ -16,14 +17,6 @@ */ class WebformCheckboxesOther extends Checkboxes implements WebformOtherInterface { - /** - * {@inheritdoc} - */ - public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; - parent::prepare($element, $webform_submission); - } - /** * {@inheritdoc} */ @@ -39,4 +32,26 @@ public function getElementSelectorOptions(array $element) { return [$title => $selectors]; } + /** + * {@inheritdoc} + */ + public function getElementSelectorInputValue($selector, $trigger, array $element, WebformSubmissionInterface $webform_submission) { + $input_name = WebformSubmissionConditionsValidator::getSelectorInputName($selector); + $other_type = WebformSubmissionConditionsValidator::getInputNameAsArray($input_name, 1); + $value = $this->getRawValue($element, $webform_submission) ?: []; + if ($other_type === 'other') { + $other_value = array_diff($value, array_keys($element['#options'])); + return ($other_value) ? implode(', ', $other_value) : NULL; + } + else { + $option_value = WebformSubmissionConditionsValidator::getInputNameAsArray($input_name, 2); + if (in_array($option_value, $value, TRUE)) { + return (in_array($trigger, ['checked', 'unchecked'])) ? TRUE : $value; + } + else { + return (in_array($trigger, ['checked', 'unchecked'])) ? FALSE : NULL; + } + } + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformCodeMirror.php b/web/modules/webform/src/Plugin/WebformElement/WebformCodeMirror.php index 5767c7aa75..51218c6019 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformCodeMirror.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformCodeMirror.php @@ -28,6 +28,7 @@ public function getDefaultProperties() { // Codemirror settings. 'placeholder' => '', 'mode' => 'text', + 'wrap' => TRUE, ] + parent::getDefaultProperties(); } @@ -130,6 +131,11 @@ public function form(array $form, FormStateInterface $form_state) { ], '#required' => TRUE, ]; + $form['codemirror']['wrap'] = [ + '#title' => $this->t('Wrap long lines of text'), + '#type' => 'checkbox', + '#return_value' => TRUE, + ]; return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformCompositeBase.php b/web/modules/webform/src/Plugin/WebformElement/WebformCompositeBase.php index b867c29231..3eef7b76a0 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformCompositeBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformCompositeBase.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\Element as RenderElement; use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -37,6 +38,13 @@ abstract class WebformCompositeBase extends WebformElementBase { */ protected $initializedCompositeElement; + /** + * Track managed file elements. + * + * @var array + */ + protected $elementsManagedFiles; + /****************************************************************************/ // Property methods. /****************************************************************************/ @@ -46,32 +54,17 @@ abstract class WebformCompositeBase extends WebformElementBase { */ public function getDefaultProperties() { $properties = [ - 'title' => '', 'default_value' => [], - // Description/Help. - 'help' => '', - 'description' => '', - 'more' => '', - 'more_title' => '', - // Form display. - 'description_display' => '', 'title_display' => 'invisible', 'disabled' => FALSE, - // Form validation. - 'required' => FALSE, - 'required_error' => '', - // Flex box. 'flexbox' => '', - // Attributes. - 'wrapper_attributes' => [], - // Submission display. - 'format' => $this->getItemDefaultFormat(), - 'format_html' => '', - 'format_text' => '', - 'format_items' => $this->getItemsDefaultFormat(), - 'format_items_html' => '', - 'format_items_text' => '', + // Enhancements. + 'select2' => FALSE, + 'chosen' => FALSE, + // Wrapper. + 'wrapper_type' => 'fieldset', ] + parent::getDefaultProperties() + $this->getDefaultMultipleProperties(); + unset($properties['required_error']); $composite_elements = $this->getCompositeElements(); foreach ($composite_elements as $composite_key => $composite_element) { @@ -88,6 +81,7 @@ public function getDefaultProperties() { } } if (isset($properties[$composite_key . '__type'])) { + $properties[$composite_key . '__title_display'] = ''; $properties[$composite_key . '__description'] = ''; $properties[$composite_key . '__help'] = ''; $properties[$composite_key . '__required'] = FALSE; @@ -108,6 +102,13 @@ protected function getDefaultMultipleProperties() { ] + parent::getDefaultMultipleProperties(); } + /** + * {@inheritdoc} + */ + public function hasManagedFiles(array $element) { + return ($this->getManagedFiles($element)) ? TRUE : FALSE; + } + /****************************************************************************/ // Element relationship methods. /****************************************************************************/ @@ -267,25 +268,34 @@ public function getElementSelectorOptions(array $element) { $has_access = (!isset($composite_elements['#access']) || $composite_elements['#access']); if ($has_access && isset($composite_element['#type'])) { $element_plugin = $this->elementManager->getElementInstance($composite_element); - $composite_title = (isset($composite_element['#title'])) ? $composite_element['#title'] : $composite_key; + $composite_element['#webform_key'] = "{$name}[{$composite_key}]"; + $selectors += OptGroup::flattenOptions($element_plugin->getElementSelectorOptions($composite_element)); + } + } + return [$title => $selectors]; + } - switch ($composite_element['#type']) { - case 'label': - case 'webform_message': - break; + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + if ($this->hasMultipleValues($element)) { + return []; + } - case 'webform_select_other': - $selectors[":input[name=\"{$name}[{$composite_key}][select]\"]"] = $composite_title . ' [' . $this->t('Select') . ']'; - $selectors[":input[name=\"{$name}[{$composite_key}][other]\"]"] = $composite_title . ' [' . $this->t('Textfield') . ']'; - break; + $name = $element['#webform_key']; - default: - $selectors[":input[name=\"{$name}[{$composite_key}]\"]"] = $composite_title . ' [' . $element_plugin->getPluginLabel() . ']'; - break; - } + $source_values = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach ($composite_elements as $composite_key => $composite_element) { + $has_access = (!isset($composite_elements['#access']) || $composite_elements['#access']); + if ($has_access && isset($composite_element['#type'])) { + $element_plugin = $this->elementManager->getElementInstance($composite_element); + $composite_element['#webform_key'] = "{$name}[{$composite_key}]"; + $source_values += $element_plugin->getElementSelectorSourceValues($composite_element); } } - return [$title => $selectors]; + return $source_values; } /****************************************************************************/ @@ -319,7 +329,7 @@ public function formatText(array $element, WebformSubmissionInterface $webform_s /** * {@inheritdoc} */ - protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { + protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = [], array $context = []) { $name = strtolower($type); // Parse element.composite_key from template and add composite element @@ -330,17 +340,15 @@ protected function formatCustomItem($type, array &$element, WebformSubmissionInt $composite_keys = array_unique($matches[1]); $item_function = 'format' . $type; - $options['context'] = [ - 'element' => [], - ]; + $context['element'] = []; foreach ($composite_keys as $composite_key) { if (isset($composite_elements[$composite_key])) { - $options['context']['element'][$composite_key] = $this->$item_function(['#format' => NULL] + $element, $webform_submission, ['composite_key' => $composite_key] + $options); + $context['element'][$composite_key] = $this->$item_function(['#format' => NULL] + $element, $webform_submission, ['composite_key' => $composite_key] + $options); } } } - return parent::formatCustomItem($type, $element, $webform_submission, $options); + return parent::formatCustomItem($type, $element, $webform_submission, $options, $context); } /** @@ -352,6 +360,12 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we } $format = $this->getItemFormat($element); + + // Handle custom composite html items. + if ($format === 'custom' && !empty($element['#format_html'])) { + return $this->formatCustomItem('html', $element, $webform_submission, $options); + } + switch ($format) { case 'list': case 'raw': @@ -363,6 +377,9 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we default: $lines = $this->formatHtmlItemValue($element, $webform_submission, $options); + if (empty($lines)) { + return ''; + } foreach ($lines as $key => $line) { if (is_string($line)) { $lines[$key] = ['#markup' => $line]; @@ -435,7 +452,7 @@ protected function formatHtmlItems(array &$element, WebformSubmissionInterface $ protected function formatTextItems(array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { $format = $this->getItemsFormat($element); if ($format === 'table') { - $element['#format_items'] = 'hr'; + $element['#format_items'] = 'hr'; } return parent::formatTextItems($element, $webform_submission, $options); } @@ -449,6 +466,12 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we } $format = $this->getItemFormat($element); + + // Handle custom composite text items. + if ($format === 'custom' && !empty($element['#format_text'])) { + return $this->formatCustomItem('text', $element, $webform_submission, $options); + } + switch ($format) { case 'list': case 'raw': @@ -765,6 +788,11 @@ public function getTestValues(array $element, WebformInterface $webform, array $ $values = []; for ($i = 1; $i <= 3; $i++) { + // Add delta to $options to allow multiple unique managed test files + // to be created. + // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::getTestValues + $options['delta'] = $i; + $value = []; foreach (RenderElement::children($composite_elements) as $composite_key) { $value[$composite_key] = $generate->getTestValue($webform, $composite_key, $composite_elements[$composite_key], $options); @@ -798,7 +826,8 @@ public function form(array $form, FormStateInterface $form_state) { $form['default']['default_value']['#description'] = $this->t("The default value of the composite webform element as YAML."); // Update #required label. - $form['validation']['required_container']['required']['#description'] .= '<br /><br />' . $this->t("Checking this option only displays the required indicator next to this element's label. Please chose which elements should be required."); + $form['validation']['required_container']['required']['#title'] .= ' <em>' . $this->t('(Display purposes only)') . '</em>'; + $form['validation']['required_container']['required']['#description'] = $this->t('If checked, adds required indicator to the title, if visible. To enforce individual fields, also tick "Required" under the @name settings above.', ['@name' => $this->getPluginLabel()]); // Update '#multiple__header_label'. $form['element']['multiple__header_container']['multiple__header_label']['#states']['visible'][':input[name="properties[multiple__header]"]'] = ['checked' => FALSE]; @@ -820,12 +849,6 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; - $item_pattern = &$form['display']['item']['patterns']['#value']['items']['#items']; - $composite_elements = $this->getCompositeElements(); - foreach ($composite_elements as $composite_key => $composite_element) { - $item_pattern[] = "{{ element.$composite_key }}"; - } - // Hide single item display when multiple item display is set to 'table'. $form['display']['item']['#states']['invisible'] = [ ':input[name="properties[format_items]"]' => ['value' => 'table'], @@ -833,6 +856,45 @@ public function form(array $form, FormStateInterface $form_state) { $form['#attached']['library'][] = 'webform/webform.admin.composite'; + // Select2 and/or Chosen enhancements. + // @see \Drupal\webform\Plugin\WebformElement\Select::form + $form['composite']['select2'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Select2'), + '#description' => $this->t('Replace select element with jQuery <a href=":href">Select2</a> box.', [':href' => 'https://select2.github.io/']), + '#return_value' => TRUE, + '#states' => [ + 'disabled' => [ + ':input[name="properties[chosen]"]' => ['checked' => TRUE], + ], + ], + ]; + if ($this->librariesManager->isExcluded('jquery.select2')) { + $form['composite']['select2']['#access'] = FALSE; + } + $form['composite']['chosen'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Chosen'), + '#description' => $this->t('Replace select element with jQuery <a href=":href">Chosen</a> box.', [':href' => 'https://harvesthq.github.io/chosen/']), + '#return_value' => TRUE, + '#states' => [ + 'disabled' => [ + ':input[name="properties[select2]"]' => ['checked' => TRUE], + ], + ], + ]; + if ($this->librariesManager->isExcluded('jquery.chosen')) { + $form['composite']['chosen']['#access'] = FALSE; + } + if ($this->librariesManager->isIncluded('jquery.select2') && $this->librariesManager->isIncluded('jquery.chosen')) { + $form['composite']['select_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Select2 and Chosen provide very similar functionality, only one should be enabled.'), + '#access' => TRUE, + ]; + } + return $form; } @@ -843,12 +905,42 @@ public function form(array $form, FormStateInterface $form_state) { * A renderable array container the composite elements settings table. */ protected function buildCompositeElementsTable() { + $labels_help = [ + 'help' => [ + '#type' => 'webform_help', + '#help' => '<b>' . t('Key') . ':</b> ' . t('The machine-readable name.') . + '<hr/><b>' . t('Title') . ':</b> ' . t('This is used as a descriptive label when displaying this webform element.') . + '<hr/><b>' . t('Placeholder') . ':</b> ' . t('The placeholder will be shown in the element until the user starts entering a value.') . + '<hr/><b>' . t('Description') . ':</b> ' . t('A short description of the element used as help for the user when he/she uses the webform.') . + '<hr/><b>' . t('Help text') . ':</b> ' . t('A tooltip displayed after the title.') . + '<hr/><b>' . t('Title display') . ':</b> ' . t('A tooltip displayed after the title.'), + '#help_title' => $this->t('Labels'), + ], + ]; + $settings_help = [ + 'help' => [ + '#type' => 'webform_help', + '#help' => '<b>' . t('Required') . ':</b> ' . t('Check this option if the user must enter a value.') . + '<hr/><b>' . t('Type') . ':</b> ' . t('The type of element to be displayed.') . + '<hr/><b>' . t('Options') . ':</b> ' . t('Please select predefined options.'), + '#help_title' => $this->t('Settings'), + ], + ]; + $header = [ - 'key' => $this->t('Key'), - 'title_placeholder_help_description' => $this->t('Title / Placeholder / Help / Description'), - 'type_options' => $this->t('Type/Options'), - 'required' => $this->t('Required'), 'visible' => $this->t('Visible'), + 'labels' => [ + 'data' => [ + ['title' => ['#markup' => $this->t('Labels')]], + $labels_help, + ], + ], + 'settings' => [ + 'data' => [ + ['title' => ['#markup' => $this->t('Settings')]], + $settings_help, + ], + ], ]; $rows = []; @@ -867,44 +959,75 @@ protected function buildCompositeElementsTable() { $row = []; - // Key. - $row[$composite_key . '__key'] = [ - '#markup' => $composite_key, - '#access' => TRUE, + // Access. + $row[$composite_key . '__access'] = [ + '#title' => $this->t('@title visible', $t_args), + '#title_display' => 'invisible', + '#type' => 'checkbox', + '#return_value' => TRUE, ]; - // Title, placeholder, help, description. + // Key, title, placeholder, help, description, and title display. if ($type) { - $row['title_placeholder_help_description'] = [ + $row['labels'] = [ 'data' => [ + $composite_key . '__key' => [ + '#markup' => $composite_key, + '#suffix' => '<hr/>', + '#access' => TRUE, + ], $composite_key . '__title' => [ '#type' => 'textfield', '#title' => $this->t('@title title', $t_args), '#title_display' => 'invisible', - '#placeholder' => $this->t('Enter title...'), + '#description' => t('This is used as a descriptive label when displaying this webform element.'), + '#description_display' => 'invisible', + '#placeholder' => $this->t('Enter title…'), + '#required' => TRUE, '#states' => $state_disabled, ], $composite_key . '__placeholder' => [ '#type' => 'textfield', '#title' => $this->t('@title placeholder', $t_args), '#title_display' => 'invisible', - '#placeholder' => $this->t('Enter placeholder...'), + '#description' => t('The placeholder will be shown in the element until the user starts entering a value.'), + '#description_display' => 'invisible', + '#placeholder' => $this->t('Enter placeholder…'), '#states' => $state_disabled, ], $composite_key . '__help' => [ '#type' => 'textarea', '#title' => $this->t('@title help text', $t_args), '#title_display' => 'invisible', + '#description' => t('A short description of the element used as help for the user when he/she uses the webform.'), + '#description_display' => 'invisible', '#rows' => 2, - '#placeholder' => $this->t('Enter help text...'), + '#placeholder' => $this->t('Enter help text…'), '#states' => $state_disabled, ], $composite_key . '__description' => [ '#type' => 'textarea', '#title' => $this->t('@title description', $t_args), '#title_display' => 'invisible', + '#description' => t('A tooltip displayed after the title.'), + '#description_display' => 'invisible', '#rows' => 2, - '#placeholder' => $this->t('Enter description...'), + '#placeholder' => $this->t('Enter description…'), + '#states' => $state_disabled, + ], + $composite_key . '__title_display' => [ + '#type' => 'select', + '#title' => $this->t('@title title display', $t_args), + '#title_display' => 'invisible', + '#description' => t('A tooltip displayed after the title.'), + '#description_display' => 'invisible', + '#options' => [ + 'before' => $this->t('Before'), + 'after' => $this->t('After'), + 'inline' => $this->t('Inline'), + 'invisible' => $this->t('Invisible'), + ], + '#empty_option' => $this->t('Select title display… '), '#states' => $state_disabled, ], ], @@ -916,10 +1039,29 @@ protected function buildCompositeElementsTable() { // Type and options. // Using if/else instead of switch/case because of complex conditions. - $row['type_options'] = []; + $row['settings'] = []; + + // Required. + if ($type) { + $row['settings']['data'][$composite_key . '__required'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Required'), + '#description' => t('Check this option if the user must enter a value.'), + '#description_display' => 'invisible', + '#return_value' => TRUE, + '#states' => $state_disabled, + '#wrapper_attributes' => ['style' => 'white-space: nowrap'], + '#suffix' => '<hr/>', + ]; + } + if ($type == 'tel') { - $row['type_options']['data'][$composite_key . '__type'] = [ + $row['settings']['data'][$composite_key . '__type'] = [ '#type' => 'select', + '#title' => $this->t('@title type', $t_args), + '#title_display' => 'invisible', + '#description' => t('The type of element to be displayed.'), + '#description_display' => 'invisible', '#required' => TRUE, '#options' => [ 'tel' => $this->t('Telephone'), @@ -935,7 +1077,7 @@ protected function buildCompositeElementsTable() { // Get type options. switch ($base_type) { case 'radios': - $type_options = [ + $settings = [ 'radios' => $this->t('Radios'), 'webform_radios_other' => $this->t('Radios other'), 'textfield' => $this->t('Text field'), @@ -944,7 +1086,7 @@ protected function buildCompositeElementsTable() { case 'select': default: - $type_options = [ + $settings = [ 'select' => $this->t('Select'), 'webform_select_other' => $this->t('Select other'), 'textfield' => $this->t('Text field'), @@ -952,15 +1094,23 @@ protected function buildCompositeElementsTable() { break; } - $row['type_options']['data'][$composite_key . '__type'] = [ + $row['settings']['data'][$composite_key . '__type'] = [ '#type' => 'select', + '#title' => $this->t('@title type', $t_args), + '#title_display' => 'invisible', + '#description' => t('The type of element to be displayed.'), + '#description_display' => 'invisible', '#required' => TRUE, - '#options' => $type_options, + '#options' => $settings, '#states' => $state_disabled, ]; if ($composite_options = $this->getCompositeElementOptions($composite_key)) { - $row['type_options']['data'][$composite_key . '__options'] = [ + $row['settings']['data'][$composite_key . '__options'] = [ '#type' => 'select', + '#title' => $this->t('@title options', $t_args), + '#title_display' => 'invisible', + '#description' => t('Please select predefined options.'), + '#description_display' => 'invisible', '#options' => $composite_options, '#required' => TRUE, '#attributes' => ['style' => 'width: 100%;'], @@ -974,39 +1124,23 @@ protected function buildCompositeElementsTable() { ]; } else { - $row['type_options']['data'][$composite_key . '__options'] = [ + $row['settings']['data'][$composite_key . '__options'] = [ '#type' => 'value', ]; } } else { - $row['type_options']['data'][$composite_key . '__type'] = [ + $row['settings']['data'][$composite_key . '__type'] = [ '#type' => 'textfield', '#access' => FALSE, ]; - $row['type_options']['data']['markup'] = [ + $row['settings']['data']['markup'] = [ + '#type' => 'container', '#markup' => $this->elementManager->getElementInstance($composite_element)->getPluginLabel(), '#access' => TRUE, ]; } - // Required. - if ($type) { - $row[$composite_key . '__required'] = [ - '#type' => 'checkbox', - '#return_value' => TRUE, - ]; - } - else { - $row[$composite_key . '__required'] = ['data' => ['']]; - } - - // Access. - $row[$composite_key . '__access'] = [ - '#type' => 'checkbox', - '#return_value' => TRUE, - ]; - $rows[$composite_key] = $row; } @@ -1023,14 +1157,9 @@ public function getConfigurationFormProperties(array &$form, FormStateInterface $properties = parent::getConfigurationFormProperties($form, $form_state); foreach ($properties as $key => $value) { // Convert composite element access and required to boolean value. - if (strpos($key, '__access') || strpos($key, '__required')) { + if (preg_match('/__(access|required)$/', $key)) { $properties[$key] = (boolean) $value; } - // If the entire element is required remove required property for - // composite elements. - if (!empty($properties['required']) && strpos($key, '__required')) { - unset($properties[$key]); - } } return $properties; } @@ -1046,9 +1175,38 @@ public function getConfigurationFormProperties(array &$form, FormStateInterface * A composite element. */ public function initializeCompositeElements(array &$element) { - // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::getInitializedCompositeElement + /** @var \Drupal\webform\Element\WebformCompositeInterface $class */ $class = $this->getFormElementClassDefinition(); $element['#webform_composite_elements'] = $class::initializeCompositeElements($element); + $this->initializeCompositeElementsRecursive($element, $element['#webform_composite_elements']); + } + + /** + * Initialize a composite's elements recursively. + * + * @param array $element + * A render array for the current element. + * @param array $composite_elements + * A render array containing a composite's elements. + */ + protected function initializeCompositeElementsRecursive(array &$element, array &$composite_elements) { + foreach ($composite_elements as $composite_key => &$composite_element) { + if (Element::property($composite_key)) { + continue; + } + + // Set composite id, key, and parent key. + // @see \Drupal\webform\Entity\Webform::initElementsRecursive + if (isset($element['#webform_id'])) { + $composite_element['#webform_composite_id'] = $element['#webform_id'] . '--' . $composite_key; + } + if (isset($element['#webform_key'])) { + $composite_element['#webform_composite_key'] = $element['#webform_key'] . '__' . $composite_key; + $composite_element['#webform_composite_parent_key'] = $element['#webform_key']; + } + + $this->initializeCompositeElementsRecursive($element, $composite_element); + } } /** @@ -1106,6 +1264,107 @@ protected function getCompositeElementOptions($composite_key) { return $options; } + /****************************************************************************/ + // Composite managed file methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function postSave(array &$element, WebformSubmissionInterface $webform_submission, $update = TRUE) { + $webform = $webform_submission->getWebform(); + if ($webform->isResultsDisabled() || !$this->hasManagedFiles($element)) { + return; + } + + $original_data = $webform_submission->getOriginalData(); + $data = $webform_submission->getData(); + + $composite_elements_managed_files = $this->getManagedFiles($element); + foreach ($composite_elements_managed_files as $composite_key) { + $original_fids = $this->getManagedFileIdsFromData($element, $original_data, $composite_key); + $fids = $this->getManagedFileIdsFromData($element, $data, $composite_key); + + // Delete the old file uploads. + $delete_fids = array_diff($original_fids, $fids); + WebformManagedFileBase::deleteFiles($webform_submission, $delete_fids); + + // Add new files. + if ($fids) { + $composite_element = $this->getInitializedCompositeElement($element, $composite_key); + /** @var \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase $composite_element_plugin */ + $composite_element_plugin = $this->elementManager->getElementInstance($composite_element); + $composite_element_plugin->addFiles($composite_element, $webform_submission, $fids); + } + } + } + + /** + * Get composite element's file ids from data array. + * + * @param array $element + * A composite element. + * @param array $data + * A submission data array. + * @param string $composite_key + * The composite sub-element key. + * + * @return array + * An array of file ids. + */ + protected function getManagedFileIdsFromData(array $element, array $data, $composite_key) { + $element_key = $element['#webform_key']; + + if (empty($data[$element_key])) { + return []; + } + + $fids = []; + $items = ($this->hasMultipleValues($element)) ? $data[$element_key] : [$data[$element_key]]; + foreach ($items as $item) { + if (!empty($item[$composite_key])) { + $fids[] = $item[$composite_key]; + } + } + return $fids; + } + + /** + * {@inheritdoc} + */ + public function postDelete(array &$element, WebformSubmissionInterface $webform_submission) { + // Uploaded files are deleted via the webform submission. + // This ensures that all files associated with a submission are deleted. + // @see \Drupal\webform\WebformSubmissionStorage::delete + } + + /** + * Get composite's managed file elements. + * + * @param array $element + * A composite element. + * + * @return array + * An array of managed file element keys. + */ + protected function getManagedFiles(array $element) { + if (isset($this->elementsManagedFiles)) { + return $this->elementsManagedFiles; + } + + $composite_elements = WebformElementHelper::getFlattened( + $this->getInitializedCompositeElement($element) + ); + + foreach ($composite_elements as $composite_key => $composite_element) { + $composite_element_plugin = $this->elementManager->getElementInstance($composite_element); + if ($composite_element_plugin instanceof WebformManagedFileBase) { + $this->elementsManagedFiles[$composite_key] = $composite_key; + } + } + return $this->elementsManagedFiles; + } + /****************************************************************************/ // Composite helper methods. /****************************************************************************/ @@ -1130,9 +1389,8 @@ public static function isSupportedElementType($type) { || $element_plugin->isComposite() || $element_plugin->isContainer($element) || $element_plugin->hasMultipleValues($element) - || $element_plugin instanceof WebformElementEntityReferenceInterface - || $element_plugin instanceof WebformComputedBase - || $element_plugin instanceof WebformManagedFileBase) { + || ($element_plugin instanceof WebformElementEntityReferenceInterface && !($element_plugin instanceof WebformManagedFileBase)) + || $element_plugin instanceof WebformComputedBase) { return FALSE; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformComputedBase.php b/web/modules/webform/src/Plugin/WebformElement/WebformComputedBase.php index 064d29e6ff..5700621f59 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformComputedBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformComputedBase.php @@ -4,14 +4,15 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Mail\MailFormatHelper; -use Drupal\Core\Render\Element; +use Drupal\webform\Element\WebformComputedTwig as WebformComputedTwigElement; use Drupal\webform\Element\WebformComputedBase as WebformComputedBaseElement; use Drupal\webform\Plugin\WebformElementBase; use Drupal\webform\Plugin\WebformElementDisplayOnInterface; +use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; /** - * Provides a base clase for 'webform_computed' elements. + * Provides a base class for 'webform_computed' elements. */ abstract class WebformComputedBase extends WebformElementBase implements WebformElementDisplayOnInterface { @@ -28,6 +29,7 @@ public function getDefaultProperties() { 'display_on' => static::DISPLAY_ON_BOTH, // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -35,11 +37,14 @@ public function getDefaultProperties() { 'title_display' => '', 'description_display' => '', // Computed values. - 'value' => '', + 'template' => '', 'mode' => WebformComputedBaseElement::MODE_AUTO, + 'hide_empty' => FALSE, 'store' => FALSE, + 'ajax' => FALSE, // Attributes. 'wrapper_attributes' => [], + 'label_attributes' => [], ] + $this->getDefaultBaseProperties(); } @@ -68,19 +73,14 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub if (!$this->isDisplayOn($element, static::DISPLAY_ON_FORM)) { $element['#access'] = FALSE; } - - $element['#element_validate'][] = [get_class($this), 'validateWebformComputed']; } /** * {@inheritdoc} */ - public function replaceTokens(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - foreach ($element as $key => $value) { - if (!Element::child($key) && !in_array($key, ['#value'])) { - $element[$key] = $this->tokenManager->replace($value, $webform_submission); - } - } + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepareElementValidateCallbacks($element, $webform_submission); + $element['#element_validate'][] = [get_class($this), 'validateWebformComputed']; } /** @@ -145,7 +145,7 @@ public function preview() { return [ '#type' => $this->getTypeName(), '#title' => $this->getPluginLabel(), - '#value' => $this->t('This is a @label value.', ['@label' => $this->getPluginLabel()]), + '#template' => $this->t('This is a @label value.', ['@label' => $this->getPluginLabel()]), ]; } @@ -155,9 +155,6 @@ public function preview() { public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); - // Remove value element so that it appears under computed fieldset. - unset($form['element']['value']); - $form['computed'] = [ '#type' => 'fieldset', '#title' => $this->t('Computed settings'), @@ -185,18 +182,39 @@ public function form(array $form, FormStateInterface $form_state) { WebformComputedBaseElement::MODE_TEXT => t('Plain text'), ], ]; - $form['computed']['value'] = [ + $form['computed']['template'] = [ '#type' => 'webform_codemirror', '#mode' => 'text', '#title' => $this->t('Computed value/markup'), ]; + $form['computed']['whitespace'] = [ + '#type' => 'select', + '#title' => $this->t('Remove whitespace around the'), + '#empty_option' => $this->t('- None -'), + '#options' => [ + WebformComputedTwigElement::WHITESPACE_TRIM => $this->t('computed value'), + WebformComputedTwigElement::WHITESPACE_SPACELESS => $this->t('computed value and between HTML tags'), + ], + ]; + $form['computed']['hide_empty'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + '#title' => $this->t('Hide empty'), + '#description' => $this->t('If checked the computed elements will be hidden from display when the value is an empty string.'), + ]; $form['computed']['store'] = [ '#type' => 'checkbox', '#return_value' => TRUE, '#title' => $this->t('Store value in the database'), '#description' => $this->t('The value will be stored in the database. As a result, it will only be recalculated when the submission is updated. This option is required when accessing the computed element through Views.'), ]; - $form['computed']['tokens'] = ['#access' => TRUE, '#weight' => 10] + $this->tokenManager->buildTreeLink(); + $form['computed']['ajax'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + '#title' => $this->t('Automatically update the computed value using Ajax'), + '#description' => $this->t('If checked, any element used within the computed value/markup will trigger any automatic update.'), + ]; + $form['computed']['tokens'] = ['#access' => TRUE, '#weight' => 10] + $this->tokenManager->buildTreeElement(); return $form; } @@ -219,7 +237,7 @@ public function preSave(array &$element, WebformSubmissionInterface $webform_sub $key = $element['#webform_key']; $data = $webform_submission->getData(); if (!empty($element['#store'])) { - $data[$key] = $this->processValue($element, $webform_submission); + $data[$key] = (string) $this->processValue($element, $webform_submission); } else { // Always unset the value. @@ -228,6 +246,42 @@ public function preSave(array &$element, WebformSubmissionInterface $webform_sub $webform_submission->setData($data); } + /** + * {@inheritdoc} + */ + public function postSave(array &$element, WebformSubmissionInterface $webform_submission, $update = TRUE) { + if ($update || empty($element['#store']) || $webform_submission->getWebform()->getSetting('results_disabled')) { + return; + } + + // Recalculate the stored computed value to account new a submission's + // generated sid and serial. + $key = $element['#webform_key']; + $value = (string) $this->processValue($element, $webform_submission); + + // Update the submission's value. + $webform_submission->setElementData($key, $value); + + // The below database update is a one-off solution because there is + // currently no other instances where a single element's value + // needs to be updated. + // @see \Drupal\webform\WebformSubmissionStorage::saveData + $fields = [ + 'webform_id' => $webform_submission->getWebform()->id(), + 'sid' => $webform_submission->id(), + 'name' => $key, + 'property' => '', + 'delta' => 0, + 'value' => $value, + ]; + \Drupal::database()->update('webform_submission_data') + ->fields($fields) + ->condition('webform_id', $fields['webform_id']) + ->condition('sid', $fields['sid']) + ->condition('name', $fields['name']) + ->execute(); + } + /** * Get computed element markup. * @@ -257,4 +311,19 @@ protected function processValue(array $element, WebformSubmissionInterface $webf return $class::processValue($element, $webform_submission); } + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Computed elements should never get a test value. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorInputValue($selector, $trigger, array $element, WebformSubmissionInterface $webform_submission) { + return (string) $this->processValue($element, $webform_submission); + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformComputedTwig.php b/web/modules/webform/src/Plugin/WebformElement/WebformComputedTwig.php index bcb5934d7a..a1aa8caa9e 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformComputedTwig.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformComputedTwig.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Plugin\WebformElement; -use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Twig\TwigExtension; @@ -19,6 +18,15 @@ */ class WebformComputedTwig extends WebformComputedBase { + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'whitespace' => '', + ] + parent::getDefaultProperties(); + } + /** * {@inheritdoc} */ @@ -26,7 +34,7 @@ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); $form['computed']['help'] = TwigExtension::buildTwigHelp(); - $form['computed']['value']['#mode'] = 'twig'; + $form['computed']['template']['#mode'] = 'twig'; // Set #access so that help is always visible. WebformElementHelper::setPropertyRecursive($form['computed']['help'], '#access', TRUE); @@ -34,27 +42,4 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } - /** - * {@inheritdoc} - */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { - parent::validateConfigurationForm($form, $form_state); - - // Validate Twig markup with no context. - try { - $build = [ - '#type' => 'inline_template', - '#template' => $form_state->getValue('value'), - '#context' => [], - ]; - \Drupal::service('renderer')->renderPlain($build); - } - catch (\Exception $exception) { - $form_state->setErrorByName('markup', [ - 'message' => ['#markup' => $this->t('Failed to render computed Twig value due to error.'), '#suffix' => '<br /><br />'], - 'error' => ['#markup' => Html::escape($exception->getMessage()), '#prefix' => '<pre>', '#suffix' => '</pre>'], - ]); - } - } - } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformCustomComposite.php b/web/modules/webform/src/Plugin/WebformElement/WebformCustomComposite.php index 50b64e4ffa..35ef76a1ef 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformCustomComposite.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformCustomComposite.php @@ -3,7 +3,6 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\Core\Form\FormStateInterface; -use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; /** @@ -120,7 +119,7 @@ protected function buildCompositeElementsTable() { return [ '#type' => 'webform_element_composite', '#title' => $this->t('Elements'), - '#title_display' => $this->t('Invisible'), + '#title_display' => 'invisible', ]; } @@ -154,36 +153,6 @@ public function preview() { ]; } - /****************************************************************************/ - // Test methods. - /****************************************************************************/ - - /** - * {@inheritdoc} - */ - public function getTestValues(array $element, WebformInterface $webform, array $options = []) { - /** @var \Drupal\webform\WebformSubmissionGenerateInterface $generate */ - $generate = \Drupal::service('webform_submission.generate'); - - $composite_elements = $element['#element']; - - // Initialize, prepare, and populate composite sub-element. - foreach ($composite_elements as $composite_key => $composite_element) { - $this->elementManager->initializeElement($composite_element); - $composite_elements[$composite_key] = $composite_element; - } - - $values = []; - for ($i = 1; $i <= 3; $i++) { - $value = []; - foreach ($composite_elements as $composite_key => $composite_element) { - $value[$composite_key] = $generate->getTestValue($webform, $composite_key, $composite_element, $options); - } - $values[] = $value; - } - return $values; - } - /****************************************************************************/ // Composite element methods. /****************************************************************************/ @@ -194,10 +163,10 @@ public function getTestValues(array $element, WebformInterface $webform, array $ public function initializeCompositeElements(array &$element) { $element['#webform_composite_elements'] = []; foreach ($element['#element'] as $composite_key => $composite_element) { - // Initialize composite sub-element. $this->elementManager->initializeElement($composite_element); $element['#webform_composite_elements'][$composite_key] = $composite_element; } + $this->initializeCompositeElementsRecursive($element, $element['#webform_composite_elements']); } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformElement.php b/web/modules/webform/src/Plugin/WebformElement/WebformElement.php index e5181722ae..bde9844d48 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformElement.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformElement.php @@ -4,7 +4,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Plugin\WebformElementBase; -use Drupal\Core\Url as UrlGenerator; /** * Provides a 'generic' element. Used as a fallback. @@ -55,18 +54,18 @@ public function form(array $form, FormStateInterface $form_state) { */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { $form = parent::buildConfigurationForm($form, $form_state); + $form['custom']['#type'] = 'fieldset'; $form['custom']['#title'] = $this->t('Element settings'); + $form['custom']['#weight'] = 100; $form['custom']['custom']['#title'] = $this->t('Properties'); + return $form; + } - // Add link to theme API documentation. - $theme = (isset($this->configuration['#theme'])) ? $this->configuration['#theme'] : ''; - if (function_exists('template_preprocess_' . $theme)) { - $t_args = [ - ':href' => UrlGenerator::fromUri('https://api.drupal.org/api/drupal/core!includes!theme.inc/function/template_preprocess_' . $theme)->toString(), - '%label' => $theme, - ]; - $form['custom']['#description'] = $this->t('Read the the %label template\'s <a href=":href">API documentation</a>.', $t_args); - } + /** + * {@inheritdoc} + */ + protected function buildConfigurationFormTabs(array $form, FormStateInterface $form_state) { + // Generic elements do not need use tabs. return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformEmailConfirm.php b/web/modules/webform/src/Plugin/WebformElement/WebformEmailConfirm.php index c91d8ba516..d8deaf2581 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformEmailConfirm.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformEmailConfirm.php @@ -28,6 +28,8 @@ public function getDefaultProperties() { 'confirm__title' => '', 'confirm__description' => '', 'confirm__placeholder' => '', + // Wrapper. + 'wrapper_type' => 'fieldset', ]; unset( $properties['multiple'], @@ -68,6 +70,13 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'textfield', '#title' => $this->t('Email confirm placeholder'), ]; + + // Remove unsupported title and description display from composite elements. + if ($this->isComposite()) { + unset($form['form']['display_container']['title_display']['#options']['inline']); + unset($form['form']['display_container']['description_display']['#options']['tooltip']); + } + return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformEntityOptionsTrait.php b/web/modules/webform/src/Plugin/WebformElement/WebformEntityOptionsTrait.php index 13adfa36e8..987244746d 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformEntityOptionsTrait.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformEntityOptionsTrait.php @@ -39,4 +39,12 @@ protected function getElementSelectorInputsOptions(array $element) { return parent::getElementSelectorInputsOptions($element); } + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + $this->setOptions($element); + return parent::getElementSelectorSourceValues($element); + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformEntityReferenceTrait.php b/web/modules/webform/src/Plugin/WebformElement/WebformEntityReferenceTrait.php index ad2a998c63..a54de5d5c5 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformEntityReferenceTrait.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformEntityReferenceTrait.php @@ -6,6 +6,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Url as UrlGenerator; use Drupal\webform\Element\WebformEntityTrait; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; @@ -40,7 +41,8 @@ public function getRelatedTypes(array $element) { */ protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { $entity = $this->getTargetEntity($element, $webform_submission, $options); - if (!$entity) { + + if (empty($entity)) { return ''; } @@ -55,11 +57,32 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we return $this->formatTextItem($element, $webform_submission, $options); case 'link': - return [ - '#type' => 'link', - '#title' => $entity->label(), - '#url' => $entity->toUrl()->setAbsolute(TRUE), - ]; + if ($entity->hasLinkTemplate('canonical')) { + return [ + '#type' => 'link', + '#title' => $entity->label(), + '#url' => $entity->toUrl()->setAbsolute(TRUE), + ]; + } + else { + switch ($entity->getEntityTypeId()) { + case 'file': + /** @var \Drupal\file\FileInterface $entity */ + if ($entity->access('download')) { + return [ + '#type' => 'link', + '#title' => $entity->label(), + '#url' => UrlGenerator::fromUri(file_create_url($entity->getFileUri())), + ]; + } + else { + return $this->formatTextItem($element, $webform_submission, $options); + } + + default: + return $this->formatTextItem($element, $webform_submission, $options); + } + } default: return \Drupal::entityTypeManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, $format); @@ -112,10 +135,10 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we * {@inheritdoc} */ public function getTestValues(array $element, WebformInterface $webform, array $options = []) { - $this->setOptions($element); - $target_type = $this->getTargetType($element); + $this->setOptions($element, ['limit' => 10, 'random' => TRUE]); // Exclude 'anonymous' user. - if ($target_type == 'user') { + $target_type = $this->getTargetType($element); + if ($target_type === 'user') { unset($element['#options'][0]); } return array_keys($element['#options']); @@ -322,9 +345,11 @@ public function buildExportRecord(array $element, WebformSubmissionInterface $we * * @param array $element * An element. + * @param array $settings + * An array of settings used to limit and randomize options. */ - protected function setOptions(array &$element) { - WebformEntityTrait::setOptions($element); + protected function setOptions(array &$element, array $settings = []) { + WebformEntityTrait::setOptions($element, $settings); } /** @@ -535,16 +560,16 @@ public function form(array $form, FormStateInterface $form_state) { '#submit' => [[get_called_class(), 'submitEntityReferenceCallback']], // Refresh the entity reference details container. '#ajax' => [ - 'callback' => [get_called_class(), 'entityReferenceAjaxCallback'], + 'callback' => [get_called_class(), 'ajaxEntityReferenceCallback'], 'wrapper' => 'webform-entity-reference-selection-wrapper', 'progress' => ['type' => 'fullscreen'], ], - // Hide button, add submit button trigger class, and disable validation. + // Disable validation, hide button, add submit button trigger class. '#attributes' => [ + 'formnovalidate' => 'formnovalidate', 'class' => [ 'js-hide', 'js-webform-entity-reference-submit', - 'js-webform-novalidate', ], ], ]; @@ -657,7 +682,7 @@ public static function submitEntityReferenceCallback(array $form, FormStateInter * @return array * The properties element. */ - public static function entityReferenceAjaxCallback(array $form, FormStateInterface $form_state) { + public static function ajaxEntityReferenceCallback(array $form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); return $element; diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformHorizontalRule.php b/web/modules/webform/src/Plugin/WebformElement/WebformHorizontalRule.php index 82b7217f39..cbf50a9f11 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformHorizontalRule.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformHorizontalRule.php @@ -82,7 +82,7 @@ public function buildText(array $element, WebformSubmissionInterface $webform_su return []; } - return PHP_EOL . '---' . PHP_EOL; + return ['#plain_text' => PHP_EOL . '---' . PHP_EOL]; } /** diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformImageFile.php b/web/modules/webform/src/Plugin/WebformElement/WebformImageFile.php index 3c715e1972..bb03aead0a 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformImageFile.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformImageFile.php @@ -2,6 +2,7 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\Core\Form\FormStateInterface; use Drupal\webform\WebformSubmissionInterface; /** @@ -20,6 +21,30 @@ */ class WebformImageFile extends WebformManagedFileBase { + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return parent::getDefaultProperties() + [ + 'max_resolution' => '', + 'min_resolution' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + parent::prepare($element, $webform_submission); + + // Add upload resolution validation. + $max_resolution = $this->getElementProperty($element, 'max_resolution'); + $min_resolution = $this->getElementProperty($element, 'min_resolution'); + if ($max_resolution || $min_resolution) { + $element['#upload_validators']['file_validate_image_resolution'] = [$max_resolution, $min_resolution]; + } + } + /** * {@inheritdoc} */ @@ -39,7 +64,7 @@ public function getItemFormats() { // Add support :image, :link, and :modal. $label = (string) $this->t('Original Image'); $t_args = ['@label' => $label]; - $formats[$label][":image"] = $this->t('@label: Image', $t_args);; + $formats[$label][":image"] = $this->t('@label: Image', $t_args); $formats[$label][":link"] = $this->t('@label: Link', $t_args); $formats[$label][":modal"] = $this->t('@label: Modal', $t_args); if (\Drupal::moduleHandler()->moduleExists('image')) { @@ -47,7 +72,7 @@ public function getItemFormats() { foreach ($image_styles as $id => $image_style) { $label = (string) $image_style->label(); $t_args = ['@label' => $label]; - $formats[$label]["$id:image"] = $this->t('@label: Image', $t_args);; + $formats[$label]["$id:image"] = $this->t('@label: Image', $t_args); $formats[$label]["$id:link"] = $this->t('@label: Link', $t_args); $formats[$label]["$id:modal"] = $this->t('@label: Modal', $t_args); } @@ -83,4 +108,35 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we } } + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['image'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Image settings'), + ]; + $form['image'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Image settings'), + ]; + $form['image']['max_resolution'] = [ + '#type' => 'webform_image_resolution', + '#title' => t('Maximum image resolution'), + '#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'), + '#width_title' => t('Maximum width'), + '#height_title' => t('Maximum height'), + ]; + $form['image']['min_resolution'] = [ + '#type' => 'webform_image_resolution', + '#title' => t('Minimum image resolution'), + '#description' => t('The minimum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'), + '#width_title' => t('Minimum width'), + '#height_title' => t('Minimum height'), + ]; + + return $form; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformLikert.php b/web/modules/webform/src/Plugin/WebformElement/WebformLikert.php index 62d70df42a..4ac156a220 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformLikert.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformLikert.php @@ -5,6 +5,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\webform\Element\WebformLikert as WebformLikertElement; use Drupal\webform\Entity\WebformOptions; +use Drupal\webform\Utility\WebformAccessibilityHelper; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\Plugin\WebformElementBase; @@ -34,6 +35,7 @@ public function getDefaultProperties() { 'default_value' => [], // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -48,7 +50,9 @@ public function getDefaultProperties() { 'format' => $this->getItemDefaultFormat(), 'format_html' => '', 'format_text' => '', + 'format_attributes' => [], // Likert settings. + 'sticky' => TRUE, 'questions' => [], 'questions_description_display' => 'description', 'questions_randomize' => FALSE, @@ -59,6 +63,7 @@ public function getDefaultProperties() { 'na_answer_text' => $this->t('N/A'), // Attributes. 'wrapper_attributes' => [], + 'label_attributes' => [], // iCheck settings. 'icheck' => '', ] + $this->getDefaultBaseProperties(); @@ -110,7 +115,7 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we // HTML emails. $header = []; $header['likert_question'] = [ - 'data' => '', + 'data' => WebformAccessibilityHelper::buildVisuallyHidden(t('Questions')), 'align' => 'left', 'width' => '40%', ]; @@ -149,6 +154,7 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we '#type' => 'table', '#header' => $header, '#rows' => $rows, + '#sticky' => $this->getElementProperty($element, 'sticky'), '#attributes' => [ 'class' => ['webform-likert-table'], ], @@ -380,6 +386,20 @@ protected function getElementSelectorInputsOptions(array $element) { return $selectors; } + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + $selector_options = $this->getElementSelectorOptions($element); + $selectors = reset($selector_options); + + $source_values = []; + foreach (array_keys($selectors) as $selector) { + $source_values[$selector] = $element['#answers']; + } + return $source_values; + } + /** * {@inheritdoc} */ @@ -446,7 +466,8 @@ public function form(array $form, FormStateInterface $form_state) { $form['likert']['na_answer_text'] = [ '#type' => 'textfield', '#title' => $this->t('N/A answer text'), - '#description' => $this->t('Text display display on webform.'), + '#description' => $this->t('Text displayed on the webform.'), + '#attributes' => ['data-webform-states-no-clear' => TRUE], '#states' => [ 'visible' => [ ':input[name="properties[na_answer]"]' => ['checked' => TRUE], @@ -456,6 +477,12 @@ public function form(array $form, FormStateInterface $form_state) { ], ], ]; + $form['likert']['sticky'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Drupal style "sticky" table headers (Javascript)'), + '#description' => $this->t('If checked, the answers will float at the top of the page as the user scrolls-thru the questions.'), + '#return_value' => TRUE, + ]; return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformLink.php b/web/modules/webform/src/Plugin/WebformElement/WebformLink.php index 55c2b6e9c6..cec62f56c5 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformLink.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformLink.php @@ -19,6 +19,21 @@ */ class WebformLink extends WebformCompositeBase { + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + $properties = parent::getDefaultProperties(); + + // Link does not have select menus. + unset( + $properties['select2'], + $properties['chosed'] + ); + + return $properties; + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformLocationBase.php b/web/modules/webform/src/Plugin/WebformElement/WebformLocationBase.php new file mode 100644 index 0000000000..d397324516 --- /dev/null +++ b/web/modules/webform/src/Plugin/WebformElement/WebformLocationBase.php @@ -0,0 +1,162 @@ +<?php + +namespace Drupal\webform\Plugin\WebformElement; + +use Drupal\Core\Form\FormStateInterface; + +/** + * Provides a base 'location' element. + */ +abstract class WebformLocationBase extends WebformCompositeBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + $properties = [ + 'title' => '', + 'default_value' => [], + 'multiple' => FALSE, + // Description/Help. + 'help' => '', + 'help_title' => '', + 'description' => '', + 'more' => '', + 'more_title' => '', + // Form display. + 'title_display' => '', + 'description_display' => '', + 'disabled' => FALSE, + // Form validation. + 'required' => FALSE, + 'required_error' => '', + // Attributes. + 'wrapper_attributes' => [], + 'label_attributes' => [], + // Submission display. + 'format' => $this->getItemDefaultFormat(), + 'format_html' => '', + 'format_text' => '', + 'format_items' => $this->getItemsDefaultFormat(), + 'format_items_html' => '', + 'format_items_text' => '', + ] + $this->getDefaultBaseProperties(); + + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $properties[$composite_key . '__title'] = (string) $composite_element['#title']; + // The value is always visible and supports a custom placeholder. + if ($composite_key == 'value') { + $properties[$composite_key . '__placeholder'] = ''; + } + else { + $properties[$composite_key . '__access'] = FALSE; + } + } + return $properties; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$element) { + // Hide all composite elements by default. + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + if ($composite_key != 'value' && !isset($element['#' . $composite_key . '__access'])) { + $element['#' . $composite_key . '__access'] = FALSE; + } + } + + parent::initialize($element); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Reverted #required label. + $form['validation']['required']['#description'] = $this->t('Check this option if the user must enter a value.'); + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function buildCompositeElementsTable() { + $header = [ + $this->t('Key'), + $this->t('Title/Placeholder'), + $this->t('Visible'), + ]; + + $rows = []; + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $title = (isset($composite_element['#title'])) ? $composite_element['#title'] : $composite_key; + $type = isset($composite_element['#type']) ? $composite_element['#type'] : NULL; + $t_args = ['@title' => $title]; + $attributes = ['style' => 'width: 100%; margin-bottom: 5px']; + + $row = []; + + // Key. + $row[$composite_key . '__key'] = [ + '#markup' => $composite_key, + '#access' => TRUE, + ]; + + // Title, placeholder, and description. + if ($type) { + $row['title_and_description'] = [ + 'data' => [ + $composite_key . '__title' => [ + '#type' => 'textfield', + '#title' => $this->t('@title title', $t_args), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Enter title…'), + '#attributes' => $attributes, + ], + $composite_key . '__placeholder' => [ + '#type' => 'textfield', + '#title' => $this->t('@title placeholder', $t_args), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Enter placeholder…'), + '#attributes' => $attributes, + ], + ], + ]; + } + else { + $row['title_and_description'] = ['data' => ['']]; + } + + // Access. + if ($composite_key === 'value') { + $row[$composite_key . '__access'] = [ + '#type' => 'checkbox', + '#default_value' => TRUE, + '#disabled' => TRUE, + '#access' => TRUE, + ]; + } + else { + $row[$composite_key . '__access'] = [ + '#type' => 'checkbox', + '#return_value' => TRUE, + ]; + } + + $rows[$composite_key] = $row; + } + + return [ + '#type' => 'table', + '#header' => $header, + ] + $rows; + } + +} diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformLocation.php b/web/modules/webform/src/Plugin/WebformElement/WebformLocationGeocomplete.php similarity index 55% rename from web/modules/webform/src/Plugin/WebformElement/WebformLocation.php rename to web/modules/webform/src/Plugin/WebformElement/WebformLocationGeocomplete.php index c548508634..0444f113f8 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformLocation.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformLocationGeocomplete.php @@ -8,92 +8,39 @@ use Drupal\webform\WebformSubmissionInterface; /** - * Provides an 'location' element. + * Provides an 'location' element using Geocomplete. * * @WebformElement( - * id = "webform_location", - * label = @Translation("Location"), - * description = @Translation("Provides a form element to collect valid location information (address, longitude, latitude, geolocation) using Google's location auto completion API."), + * id = "webform_location_geocomplete", + * label = @Translation("Location (Geocomplete)"), + * description = @Translation("Provides a form element to collect valid location information (address, longitude, latitude, geolocation) using Google Maps API's Geocoding and Places Autocomplete."), * category = @Translation("Composite elements"), * multiline = TRUE, * composite = TRUE, * states_wrapper = TRUE, + * deprecated = TRUE, + * deprecated_message = @Translation("The jQuery: Geocoding and Places Autocomplete Plugin library is not being maintained. It has been <a href=""https://www.drupal.org/node/2991275"">deprecated</a> and will be removed before Webform 8.x-5.0."), * ) */ -class WebformLocation extends WebformCompositeBase { +class WebformLocationGeocomplete extends WebformLocationBase { + + /** + * {@inheritdoc} + */ + public function getPluginLabel() { + return $this->elementManager->isExcluded('webform_location_places') ? $this->t('Location') : parent::getPluginLabel(); + } /** * {@inheritdoc} */ public function getDefaultProperties() { - $properties = [ - 'title' => '', - 'default_value' => [], - 'multiple' => FALSE, - // Description/Help. - 'help' => '', - 'description' => '', - 'more' => '', - 'more_title' => '', - // Form display. - 'title_display' => '', - 'description_display' => '', - 'disabled' => FALSE, - // Form validation. - 'required' => FALSE, - 'required_error' => '', - // Attributes. - 'wrapper_attributes' => [], - // Location settings. + return parent::getDefaultProperties() + [ 'geolocation' => FALSE, 'hidden' => FALSE, 'map' => FALSE, 'api_key' => '', - // Submission display. - 'format' => $this->getItemDefaultFormat(), - 'format_html' => '', - 'format_text' => '', - 'format_items' => $this->getItemsDefaultFormat(), - 'format_items_html' => '', - 'format_items_text' => '', ] + $this->getDefaultBaseProperties(); - - $composite_elements = $this->getCompositeElements(); - foreach ($composite_elements as $composite_key => $composite_element) { - $properties[$composite_key . '__title'] = (string) $composite_element['#title']; - // The value is always visible and supports a custom placeholder. - if ($composite_key == 'value') { - $properties[$composite_key . '__placeholder'] = ''; - } - else { - $properties[$composite_key . '__access'] = FALSE; - } - } - return $properties; - } - - /** - * {@inheritdoc} - */ - public function initialize(array &$element) { - // Hide all composite elements by default. - $composite_elements = $this->getCompositeElements(); - foreach ($composite_elements as $composite_key => $composite_element) { - if ($composite_key != 'value' && !isset($element['#' . $composite_key . '__access'])) { - $element['#' . $composite_key . '__access'] = FALSE; - } - } - - parent::initialize($element); - } - - /** - * {@inheritdoc} - */ - public function getItemFormats() { - return parent::getItemFormats() + [ - 'map' => $this->t('Map'), - ]; } /** @@ -146,11 +93,9 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we /** * {@inheritdoc} */ - public function preview() { - return parent::preview() + [ - '#map' => TRUE, - '#geolocation' => TRUE, - '#format' => 'map', + public function getItemFormats() { + return parent::getItemFormats() + [ + 'map' => $this->t('Map'), ]; } @@ -160,9 +105,6 @@ public function preview() { public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); - // Reverted #required label. - $form['validation']['required']['#description'] = $this->t('Check this option if the user must enter a value.'); - $form['composite']['geolocation'] = [ '#type' => 'checkbox', '#title' => $this->t("Use the browser's Geolocation as the default value"), @@ -216,86 +158,23 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - protected function buildCompositeElementsTable() { - $header = [ - $this->t('Key'), - $this->t('Title/Placeholder'), - $this->t('Visible'), + public function preview() { + return parent::preview() + [ + '#map' => TRUE, + '#geolocation' => TRUE, + '#format' => 'map', ]; - - $rows = []; - $composite_elements = $this->getCompositeElements(); - foreach ($composite_elements as $composite_key => $composite_element) { - $title = (isset($composite_element['#title'])) ? $composite_element['#title'] : $composite_key; - $type = isset($composite_element['#type']) ? $composite_element['#type'] : NULL; - $t_args = ['@title' => $title]; - $attributes = ['style' => 'width: 100%; margin-bottom: 5px']; - - $row = []; - - // Key. - $row[$composite_key . '__key'] = [ - '#markup' => $composite_key, - '#access' => TRUE, - ]; - - // Title, placeholder, and description. - if ($type) { - $row['title_and_description'] = [ - 'data' => [ - $composite_key . '__title' => [ - '#type' => 'textfield', - '#title' => $this->t('@title title', $t_args), - '#title_display' => 'invisible', - '#placeholder' => $this->t('Enter title...'), - '#attributes' => $attributes, - ], - $composite_key . '__placeholder' => [ - '#type' => 'textfield', - '#title' => $this->t('@title placeholder', $t_args), - '#title_display' => 'invisible', - '#placeholder' => $this->t('Enter placeholder...'), - '#attributes' => $attributes, - ], - ], - ]; - } - else { - $row['title_and_description'] = ['data' => ['']]; - } - - // Access. - if ($composite_key === 'value') { - $row[$composite_key . '__access'] = [ - '#type' => 'checkbox', - '#default_value' => TRUE, - '#disabled' => TRUE, - '#access' => TRUE, - ]; - } - else { - $row[$composite_key . '__access'] = [ - '#type' => 'checkbox', - '#return_value' => TRUE, - ]; - } - - $rows[$composite_key] = $row; - } - - return [ - '#type' => 'table', - '#header' => $header, - ] + $rows; } /** * {@inheritdoc} */ public function getTestValues(array $element, WebformInterface $webform, array $options = []) { - // Use test values included in settings and not from - // WebformCompositeBase::getTestValues. - return FALSE; + return [ + ['value' => 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA'], + ['value' => 'London SW1A 1AA, United Kingdom'], + ['value' => 'Moscow, Russia, 10307'], + ]; } } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformLocationPlaces.php b/web/modules/webform/src/Plugin/WebformElement/WebformLocationPlaces.php new file mode 100644 index 0000000000..30a7790a67 --- /dev/null +++ b/web/modules/webform/src/Plugin/WebformElement/WebformLocationPlaces.php @@ -0,0 +1,97 @@ +<?php + +namespace Drupal\webform\Plugin\WebformElement; + +use Drupal\webform\WebformInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url as UrlGenerator; + +/** + * Provides an 'location' element using Algolia Places. + * + * @WebformElement( + * id = "webform_location_places", + * label = @Translation("Location (Algolia Places)"), + * description = @Translation("Provides a form element to collect valid location information (address, longitude, latitude, geolocation) using Algolia Places."), + * category = @Translation("Composite elements"), + * multiline = TRUE, + * composite = TRUE, + * states_wrapper = TRUE, + * ) + */ +class WebformLocationPlaces extends WebformLocationBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'app_id' => '', + 'api_key' => '', + 'placeholder' => '', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function getPluginLabel() { + return $this->elementManager->isExcluded('webform_location_geocomplete') ? $this->t('Location') : parent::getPluginLabel(); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['composite']['app_id'] = [ + '#type' => 'textfield', + '#title' => $this->t('Algolia application id'), + '#description' => $this->t('Algolia requires users to use a valid application id and API key for more than 1,000 requests per day. By <a href="https://www.algolia.com/users/sign_up/places">signing up</a>, you can create a free Places app and access your API keys.'), + ]; + $default_app_id = \Drupal::config('webform.settings')->get('element.default_algolia_places_app_id'); + if ($default_app_id) { + $form['composite']['app_id']['#description'] .= '<br /><br />' . $this->t('Defaults to: %value', ['%value' => $default_app_id]); + } + else { + $form['composite']['app_id']['#required'] = TRUE; + if (\Drupal::currentUser()->hasPermission('administer webform')) { + $t_args = [':href' => UrlGenerator::fromRoute('webform.config.elements')->toString()]; + $form['composite']['app_id']['#description'] .= '<br /><br />' . $this->t('You can either enter an element specific application id and API key here or set the <a href=":href">default site-wide application id and API key</a>.', $t_args); + } + } + $form['composite']['api_key'] = [ + '#type' => 'textfield', + '#title' => $this->t('Algolia API key'), + ]; + $default_api_key = \Drupal::config('webform.settings')->get('element.default_algolia_places_api_key'); + if ($default_api_key) { + $form['composite']['api_key']['#description'] = $this->t('Defaults to: %value', ['%value' => $default_api_key]); + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + return [ + [ + 'value' => '1600 Pennsylvania Avenue, Washington, District of Columbia, United States of America', + 'lat' => '38.8635', + 'lng' => '-76.946', + 'name' => '1600 Pennsylvania Avenue', + 'city' => 'Washington', + 'country' => 'United States of America', + 'country_code' => 'us', + 'administrative' => 'District of Columbia', + 'county' => 'Prince George\'s County', + 'suburb' => '', + 'postcode' => '20020', + ], + ]; + } + +} diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformManagedFileBase.php b/web/modules/webform/src/Plugin/WebformElement/WebformManagedFileBase.php index ba0968acde..59e47e50f6 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformManagedFileBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformManagedFileBase.php @@ -2,35 +2,43 @@ namespace Drupal\webform\Plugin\WebformElement; +use Drupal\Component\Utility\Bytes; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; +use Drupal\Component\Transliteration\TransliterationInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Link; +use Drupal\Core\Render\Element; use Drupal\Core\Url as UrlGenerator; use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StreamWrapper\StreamWrapperInterface; -use Drupal\Component\Transliteration\TransliterationInterface; use Drupal\file\Entity\File; use Drupal\file\FileInterface; +use Drupal\webform\Element\WebformHtmlEditor; use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Plugin\WebformElementAttachmentInterface; use Drupal\webform\Plugin\WebformElementBase; -use Drupal\Component\Utility\Bytes; +use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionForm; use Drupal\webform\WebformSubmissionInterface; -use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\Plugin\WebformElementEntityReferenceInterface; use Drupal\webform\WebformLibrariesManagerInterface; use Drupal\webform\WebformTokenManagerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a base class webform 'managed_file' elements. */ -abstract class WebformManagedFileBase extends WebformElementBase { +abstract class WebformManagedFileBase extends WebformElementBase implements WebformElementAttachmentInterface, WebformElementEntityReferenceInterface { /** * List of blacklisted mime types that must be downloaded. @@ -99,14 +107,14 @@ abstract class WebformManagedFileBase extends WebformElementBase { * The webform libraries manager. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. - * @param \Drupal\file\FileUsage\FileUsageInterface|NULL $file_usage + * @param \Drupal\file\FileUsage\FileUsageInterface|null $file_usage * The file usage service. * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration * The transliteration service. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition,LoggerInterface $logger, ConfigFactoryInterface $config_factory, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager, WebformTokenManagerInterface $token_manager, WebformLibrariesManagerInterface $libraries_manager, FileSystemInterface $file_system, $file_usage, TransliterationInterface $transliteration, LanguageManagerInterface $language_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, ConfigFactoryInterface $config_factory, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, ElementInfoManagerInterface $element_info, WebformElementManagerInterface $element_manager, WebformTokenManagerInterface $token_manager, WebformLibrariesManagerInterface $libraries_manager, FileSystemInterface $file_system, $file_usage, TransliterationInterface $transliteration, LanguageManagerInterface $language_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $logger, $config_factory, $current_user, $entity_type_manager, $element_info, $element_manager, $token_manager, $libraries_manager); $this->fileSystem = $file_system; @@ -149,6 +157,9 @@ public function getDefaultProperties() { 'max_filesize' => '', 'file_extensions' => $file_extensions, 'file_name' => '', + 'file_help' => '', + 'file_preview' => '', + 'file_placeholder' => '', 'uri_scheme' => 'private', 'sanitize' => FALSE, 'button' => FALSE, @@ -199,6 +210,13 @@ public function isEnabled() { return (empty($scheme_options)) ? FALSE : TRUE; } + /** + * {@inheritdoc} + */ + public function hasManagedFiles(array $element) { + return TRUE; + } + /** * {@inheritdoc} */ @@ -212,7 +230,7 @@ public function displayDisabledWarning(array $element) { $scheme_options = static::getVisibleStreamWrappers(); $uri_scheme = $this->getUriScheme($element); if (!isset($scheme_options[$uri_scheme]) && $this->currentUser->hasPermission('administer webform')) { - drupal_set_message($this->t('The \'File\' element is unavailable because a <a href="https://www.ostraining.com/blog/drupal/creating-drupal-8-private-file-system/">private files directory</a> has not been configured and public file uploads have not been enabled. For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'), 'warning'); + $this->messenger()->addWarning($this->t('The \'File\' element is unavailable because a <a href="https://www.ostraining.com/blog/drupal/creating-drupal-8-private-file-system/">private files directory</a> has not been configured and public file uploads have not been enabled. For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>')); $context = [ 'link' => Link::fromTextAndUrl($this->t('Edit'), UrlGenerator::fromRoute('<current>'))->toString(), ]; @@ -248,24 +266,57 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#upload_location'] = $this->getUploadLocation($element, $webform_submission->getWebform()); } - $element['#upload_validators']['file_validate_size'] = [$this->getMaxFileSize($element)]; - $element['#upload_validators']['file_validate_extensions'] = [$this->getFileExtensions($element)]; - - // Use custom validation callback so that File entities can be converted - // into file ids (akk fids). + // Get file limit. + $file_limit = $webform_submission->getWebform()->getSetting('form_file_limit') + ?: \Drupal::config('webform.settings')->get('settings.default_form_file_limit') + ?: ''; + + // Validate callbacks. + $element_validate = []; + // Convert File entities into file ids (akk fids). + $element_validate[] = [get_class($this), 'validateManagedFile']; + // Check file upload limit. + if ($file_limit) { + $element_validate[] = [get_class($this), 'validateManagedFileLimit']; + } // NOTE: Using array_splice() to make sure that self::validateManagedFile // is executed before all other validation hooks are executed but after // \Drupal\file\Element\ManagedFile::validateManagedFile. - array_splice($element['#element_validate'], 1, 0, [[get_class($this), 'validateManagedFile']]); + array_splice($element['#element_validate'], 1, 0, $element_validate); + + // Upload validators. + $element['#upload_validators']['file_validate_size'] = [$this->getMaxFileSize($element)]; + $element['#upload_validators']['file_validate_extensions'] = [$this->getFileExtensions($element)]; - // Add file upload help to the element. - $element['help'] = [ + // Add file upload help to the element as #description, #help, or #more. + // Copy upload validator so that we can add webform's file limit to + // file upload help only. + $upload_validators = $element['#upload_validators']; + if ($file_limit) { + $upload_validators['webform_file_limit'] = [Bytes::toInt($file_limit)]; + } + $file_upload_help = [ '#theme' => 'file_upload_help', - '#upload_validators' => $element['#upload_validators'], + '#upload_validators' => $upload_validators, '#cardinality' => (empty($element['#multiple'])) ? 1 : $element['#multiple'], - '#prefix' => '<div class="description">', - '#suffix' => '</div>', ]; + $file_help = (isset($element['#file_help'])) ? $element['#file_help'] : 'description'; + if ($file_help !== 'none') { + if (isset($element["#$file_help"])) { + if (is_array($element["#$file_help"])) { + $file_help_content = $element["#$file_help"]; + } + else { + $file_help_content = ['#markup' => $element["#$file_help"]]; + } + $file_help_content += ['#suffix' => '<br/>']; + $element["#$file_help"] = ['content' => $file_help_content]; + } + else { + $element["#$file_help"] = []; + } + $element["#$file_help"]['file_upload_help'] = $file_upload_help; + } // Issue #2705471: Webform states File fields. // Workaround: Wrap the 'managed_file' element in a basic container. @@ -278,6 +329,17 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $container[$element['#webform_key']] = $element + ['#webform_managed_file_processed' => TRUE]; $element = $container; } + + // Add process callback. + // Set element's #process callback so that is not replaced by + // additional #process callbacks. + $this->setElementDefaultCallback($element, 'process'); + $element['#process'][] = [get_class($this), 'processManagedFile']; + + // Add managed file upload tracking. + if (\Drupal::moduleHandler()->moduleExists('file')) { + $element['#attached']['library'][] = 'webform/webform.element.managed_file'; + } } /** @@ -295,6 +357,11 @@ public function setDefaultValue(array &$element) { protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { $value = $this->getValue($element, $webform_submission, $options); $file = $this->getFile($element, $value, $options); + + if (empty($file)) { + return ''; + } + $format = $this->getItemFormat($element); switch ($format) { case 'id': @@ -331,6 +398,11 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we protected function formatTextItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { $value = $this->getValue($element, $webform_submission, $options); $file = $this->getFile($element, $value, $options); + + if (empty($file)) { + return ''; + } + $format = $this->getItemFormat($element); switch ($format) { case 'id': @@ -404,11 +476,11 @@ protected function getFile(array $element, $value, array $options) { * @return array * An associative array containing files. */ - protected function getFiles(array $element, $value, array $options) { + protected function getFiles(array $element, $value, array $options = []) { if (empty($value)) { return []; } - return $this->entityTypeManager->getStorage('file')->loadMultiple($value); + return $this->entityTypeManager->getStorage('file')->loadMultiple((array) $value); } /** @@ -444,60 +516,56 @@ public function postSave(array &$element, WebformSubmissionInterface $webform_su // Delete the old file uploads. $delete_fids = array_diff($original_fids, $fids); - $this->deleteFiles($delete_fids, $webform_submission); + static::deleteFiles($webform_submission, $delete_fids); - // Exit if there is no fids. - if (empty($fids)) { - return; - } - - /** @var \Drupal\file\FileInterface[] $files */ - $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids); - foreach ($files as $file) { - $source_uri = $file->getFileUri(); - $destination_uri = $this->getFileDestinationUri($element, $file, $webform_submission); - - // Save file if there is a new destination URI. - if ($source_uri != $destination_uri) { - $destination_uri = file_unmanaged_move($source_uri, $destination_uri); - $file->setFileUri($destination_uri); - $file->setFileName($this->fileSystem->basename($destination_uri)); - $file->save(); - } - - // Update file usage table. - // Setting file usage will also make the file's status permanent. - $this->fileUsage->delete($file, 'webform', 'webform_submission', $webform_submission->id()); - $this->fileUsage->add($file, 'webform', 'webform_submission', $webform_submission->id()); - } + // Add new files. + $this->addFiles($element, $webform_submission, $fids); } /** * {@inheritdoc} */ public function postDelete(array &$element, WebformSubmissionInterface $webform_submission) { - $fids = (array) ($webform_submission->getElementData($element['#webform_key']) ?: []); - $this->deleteFiles($fids, $webform_submission); + // Uploaded files are deleted via the webform submission. + // This ensures that all files associated with a submission are deleted. + // @see \Drupal\webform\WebformSubmissionStorage::delete } /** * {@inheritdoc} */ public function getTestValues(array $element, WebformInterface $webform, array $options = []) { - if ($this->isDisabled() || !isset($element['#webform_key'])) { + if ($this->isDisabled()) { return NULL; } + // Get element or composite key. + if (isset($element['#webform_key'])) { + $key = $element['#webform_key']; + } + elseif (isset($element['#webform_composite_key'])) { + $key = $element['#webform_composite_key']; + } + else { + return NULL; + } + + // Append delta to key. + // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::getTestValues + if (isset($options['delta'])) { + $key .= '_' . $options['delta']; + } + $file_extensions = explode(' ', $this->getFileExtensions($element)); $file_extension = $file_extensions[array_rand($file_extensions)]; $upload_location = $this->getUploadLocation($element, $webform); - $file_destination = $upload_location . '/' . $element['#webform_key'] . '.' . $file_extension; + $file_destination = $upload_location . '/' . $key . '.' . $file_extension; // Look for an existing temp files that have not been uploaded. $fids = $this->entityTypeManager->getStorage('file')->getQuery() ->condition('status', FALSE) ->condition('uid', $this->currentUser->id()) - ->condition('uri', $upload_location . '/' . $element['#webform_key'] . '.%', 'LIKE') + ->condition('uri', $upload_location . '/' . $key . '.%', 'LIKE') ->execute(); if ($fids) { return reset($fids); @@ -532,29 +600,34 @@ public function getTestValues(array $element, WebformInterface $webform, array $ * Max file size. */ protected function getMaxFileSize(array $element) { - // Set max file size. $max_filesize = $this->configFactory->get('webform.settings')->get('file.default_max_filesize') ?: file_upload_max_size(); $max_filesize = Bytes::toInt($max_filesize); if (!empty($element['#max_filesize'])) { - $max_filesize = min($max_filesize, Bytes::toInt($element['#max_filesize']) * 1024 * 1024); + $max_filesize = min($max_filesize, Bytes::toInt($element['#max_filesize'] . 'MB')); } return $max_filesize; } /** - * Get allowed file extensions for an element. + * Get the allowed file extensions for an element. * * @param array $element * An element. * * @return int - * File extension. + * File extensions. */ protected function getFileExtensions(array $element = NULL) { - if (!empty($element['#file_extensions'])) { - return $element['#file_extensions']; - } + return (!empty($element['#file_extensions'])) ? $element['#file_extensions'] : $this->getDefaultFileExtensions(); + } + /** + * Get the default allowed file extensions. + * + * @return int + * File extensions. + */ + protected function getDefaultFileExtensions() { $file_type = str_replace('webform_', '', $this->getPluginId()); return $this->configFactory->get('webform.settings')->get("file.default_{$file_type}_extensions"); } @@ -616,6 +689,121 @@ protected function getUriScheme(array $element) { } } + /** + * Process callback for managed file elements. + */ + public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) { + // Disable inline form errors for multiple file upload checkboxes. + if (!empty($element['#multiple'])) { + foreach (Element::children($element) as $key) { + if (isset($element[$key]['selected'])) { + $element[$key]['selected']['#error_no_message'] = TRUE; + } + } + } + + // Preview uploaded file. + if (!empty($element['#file_preview'])) { + // Get the element's plugin object. + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + /** @var \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase $element_plugin */ + $element_plugin = $element_manager->getElementInstance($element); + + // Get the webform submission. + /** @var \Drupal\webform\WebformSubmissionForm $form_object */ + $form_object = $form_state->getFormObject(); + /** @var \Drupal\webform\webform_submission $webform_submission */ + $webform_submission = $form_object->getEntity(); + + // Create a temporary preview element with an overridden #format. + $preview_element = ['#format' => $element['#file_preview']] + $element; + + // Convert '#theme': file_link to a container with a file preview. + $delta = 0; + foreach (Element::children($element) as $child_key) { + if (strpos($child_key, 'file_') !== 0) { + continue; + } + + // Set multiple options delta. + $options = ['delta' => $delta]; + $delta++; + + $fid = str_replace('file_', '', $child_key); + $file = File::load((string) $fid); + // Make sure the file entity exists. + if (!$file) { + continue; + } + + // Don't allow anonymous temporary files to be previewed. + // @see template_preprocess_file_link(). + // @see webform_preprocess_file_link(). + if ($file->isTemporary() && $file->getOwner()->isAnonymous() && strpos($file->getFileUri(), 'private://') === 0) { + continue; + } + + $preview = $element_plugin->previewManagedFile($preview_element, $webform_submission, $options); + if (isset($element[$child_key]['filename'])) { + // Single file. + // Covert file link to a container with preview. + unset($element[$child_key]['filename']['#theme']); + $element[$child_key]['filename']['#type'] = 'container'; + $element[$child_key]['filename']['#attributes']['class'][] = 'webform-managed-file-preview'; + $element[$child_key]['filename']['#attributes']['class'][] = Html::getClass($element['#type'] . '-preview'); + $element[$child_key]['filename']['preview'] = $preview; + } + elseif (isset($element[$child_key]['selected'])) { + // Multiple files. + // Convert file link checkbox #title to preview. + $element[$child_key]['selected']['#wrapper_attributes']['class'][] = 'webform-managed-file-preview-wrapper'; + $element[$child_key]['selected']['#wrapper_attributes']['class'][] = Html::getClass($element['#type'] . '-preview-wrapper'); + $element[$child_key]['selected']['#label_attributes']['class'][] = 'webform-managed-file-preview'; + $element[$child_key]['selected']['#label_attributes']['class'][] = Html::getClass($element['#type'] . '-preview'); + + $element[$child_key]['selected']['#title'] = \Drupal::service('renderer')->render($preview); + } + } + } + + // File placeholder. + if (!empty($element['#file_placeholder']) && (empty($element['#value']) || empty($element['#value']['fids']))) { + $element['file_placeholder'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => [ + 'webform-managed-file-placeholder', + Html::getClass($element['#type'] . '-placeholder'), + ], + ], + // Display placeholder before file upload input. + '#weight' => ($element['upload']['#weight'] - 1), + 'content' => WebformHtmlEditor::checkMarkup($element['#file_placeholder']), + ]; + } + + return $element; + } + + /** + * Preview a managed file element upload. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $options + * An array of options. + * + * @return string|array + * A preview. + */ + public function previewManagedFile(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $build = $this->formatHtmlItem($element, $webform_submission, $options); + return (is_array($build)) ? $build : ['#markup' => $build]; + } + /** * Form API callback. Consolidate the array of fids for this field into a single fids. */ @@ -634,6 +822,63 @@ public static function validateManagedFile(array &$element, FormStateInterface $ } } + /** + * Form API callback. Validate file upload limit. + * + * @see \Drupal\webform\WebformSubmissionForm::validateForm + */ + public static function validateManagedFileLimit(array &$element, FormStateInterface $form_state, &$complete_form) { + // Set empty files to NULL and exit. + if (empty($element['#files'])) { + return; + } + + // Only validate file limits for ajax uploads. + $wrapper_format = \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT); + if (!$wrapper_format || !in_array($wrapper_format, ['drupal_ajax', 'drupal_modal', 'drupal_dialog'])) { + return; + } + + $fids = array_keys($element['#files']); + + // Get WebformSubmissionForm object. + $form_object = $form_state->getFormObject(); + if (!($form_object instanceof WebformSubmissionForm)) { + return; + } + + // Skip validation when removing file upload. + $trigger_element = $form_state->getTriggeringElement(); + $op = (string) $trigger_element['#value']; + if (in_array($op, [(string) t('Remove'), (string) t('Remove selected')])) { + return; + } + + // Get file upload limit. + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $form_object->getEntity(); + $file_limit = $webform_submission->getWebform()->getSetting('form_file_limit') + ?: \Drupal::config('webform.settings')->get('settings.default_form_file_limit') + ?: ''; + $file_limit = Bytes::toInt($file_limit); + + // Track file size across all file upload elements. + static $total_file_size = 0; + /** @var \Drupal\file\FileInterface[] $files */ + $files = File::loadMultiple($fids); + foreach ($files as $file) { + $total_file_size += (int) $file->getSize(); + } + + // If has access and total file size exceeds file limit then display error. + $has_access = (!isset($element['#access']) || $element['#access']); + if ($has_access && $total_file_size > $file_limit) { + $t_args = ['%quota' => format_size($file_limit)]; + $message = t("This form's file upload quota of %quota has been exceeded. Please remove some files.", $t_args); + $form_state->setError($element, $message); + } + } + /** * {@inheritdoc} */ @@ -678,7 +923,7 @@ public function form(array $form, FormStateInterface $form_state) { $scheme_options = static::getVisibleStreamWrappers(); $form['file']['uri_scheme'] = [ '#type' => 'radios', - '#title' => t('Upload destination'), + '#title' => t('File upload destination'), '#description' => t('Select where the final files should be stored. Private file storage has more overhead than public files, but allows restricted access to files within this element.'), '#required' => TRUE, '#options' => $scheme_options, @@ -711,6 +956,31 @@ public function form(array $form, FormStateInterface $form_state) { $max_filesize = \Drupal::config('webform.settings')->get('file.default_max_filesize') ?: file_upload_max_size(); $max_filesize = Bytes::toInt($max_filesize); $max_filesize = ($max_filesize / 1024 / 1024); + $form['file']['file_help'] = [ + '#type' => 'select', + '#title' => $this->t('File upload help display'), + '#description' => $this->t('Determines the placement of the file upload help .'), + '#options' => [ + '' => $this->t('Description'), + 'help' => $this->t('Help'), + 'more' => $this->t('More'), + 'none' => $this->t('None'), + ], + ]; + $form['file']['file_placeholder'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('File upload placeholder'), + '#description' => $this->t('The placeholder will be shown before a file is uploaded.'), + ]; + $form['file']['file_preview'] = [ + '#type' => 'select', + '#title' => $this->t('File upload preview (Authenticated users only)'), + '#description' => $this->t('Select how the uploaded file previewed.') . '<br/><br/>' . + $this->t('Allowing anonymous users to preview files is dangerous.') . '<br/>' . + $this->t('For more information see: <a href="https://www.drupal.org/psa-2016-003">DRUPAL-PSA-2016-003</a>'), + '#options' => WebformOptionsHelper::appendValueToText($this->getItemFormats()), + '#empty_option' => '<' . $this->t('no preview') . '>', + ]; $form['file']['max_filesize'] = [ '#type' => 'number', '#title' => $this->t('Maximum file size'), @@ -719,11 +989,13 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('Enter the max file size a user may upload.'), '#min' => 1, '#max' => $max_filesize, + '#step' => 'any', ]; $form['file']['file_extensions'] = [ '#type' => 'textfield', '#title' => $this->t('Allowed file extensions'), - '#description' => $this->t('Separate extensions with a space and do not include the leading dot.'), + '#description' => $this->t('Separate extensions with a space and do not include the leading dot.') . '<br/><br/>' . + $this->t('Defaults to: %value', ['%value' => $this->getDefaultFileExtensions()]), '#maxlength' => 255, ]; $form['file']['file_name'] = [ @@ -751,10 +1023,12 @@ public function form(array $form, FormStateInterface $form_state) { '#message_type' => 'warning', '#message_message' => $this->t('For security reasons we advise to use %file_rename together with the %sanitization option.', $t_args), '#access' => TRUE, - '#states' => ['visible' => [ - ':input[name="properties[file_name][checkbox]"]' => ['checked' => TRUE], - ':input[name="properties[sanitize]"]' => ['checked' => FALSE], - ]], + '#states' => [ + 'visible' => [ + ':input[name="properties[file_name][checkbox]"]' => ['checked' => TRUE], + ':input[name="properties[sanitize]"]' => ['checked' => FALSE], + ], + ], ]; $form['file']['multiple'] = [ '#type' => 'checkbox', @@ -773,7 +1047,7 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['file']['button__title'] = [ '#type' => 'textfield', - '#title' => $this->t('Upload button title'), + '#title' => $this->t('File upload button title'), '#description' => $this->t('Defaults to: %value', ['%value' => $this->t('Choose file')]), '#states' => [ 'visible' => [ @@ -783,7 +1057,7 @@ public function form(array $form, FormStateInterface $form_state) { ]; $form['file']['button__attributes'] = [ '#type' => 'webform_element_attributes', - '#title' => $this->t('Upload button attributes'), + '#title' => $this->t('File upload button'), '#classes' => $this->configFactory->get('webform.settings')->get('settings.button_classes'), '#class__description' => $this->t("Apply classes to the button. Button classes default to 'button button-primary'."), '#states' => [ @@ -806,25 +1080,44 @@ public function form(array $form, FormStateInterface $form_state) { * In Drupal 8.4.x+ unused webform managed files are no longer * marked as temporary. * - * @param array $fids - * An array of file ids. * @param \Drupal\webform\WebformSubmissionInterface $webform_submission * A webform submission. + * @param null|array $fids + * An array of file ids. If NULL all files are deleted. */ - protected function deleteFiles(array $fids, WebformSubmissionInterface $webform_submission) { + public static function deleteFiles(WebformSubmissionInterface $webform_submission, array $fids = NULL) { + // Make sure the file.module is enabled since this method is called from + // \Drupal\webform\WebformSubmissionStorage::delete. + if (!\Drupal::moduleHandler()->moduleExists('file')) { + return; + } + + if ($fids === NULL) { + $fids = \Drupal::database()->select('file_usage', 'fu') + ->fields('fu', ['fid']) + ->condition('module', 'webform') + ->condition('type', 'webform_submission') + ->condition('id', $webform_submission->id()) + ->execute() + ->fetchCol(); + } + if (empty($fids)) { return; } + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + $make_unused_managed_files_temporary = \Drupal::config('webform.settings')->get('file.make_unused_managed_files_temporary'); $delete_temporary_managed_files = \Drupal::config('webform.settings')->get('file.delete_temporary_managed_files'); /** @var \Drupal\file\FileInterface[] $files */ $files = File::loadMultiple($fids); foreach ($files as $file) { - $this->fileUsage->delete($file, 'webform', 'webform_submission', $webform_submission->id()); + $file_usage->delete($file, 'webform', 'webform_submission', $webform_submission->id()); // Make unused files temporary. - if ($make_unused_managed_files_temporary && empty($this->fileUsage->listUsage($file)) && !$file->isTemporary()) { + if ($make_unused_managed_files_temporary && empty($file_usage->listUsage($file)) && !$file->isTemporary()) { $file->setTemporary(); $file->save(); } @@ -839,6 +1132,48 @@ protected function deleteFiles(array $fids, WebformSubmissionInterface $webform_ } } + /** + * Add a webform submission file's usage and mark it as permanent. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $fids + * An array of file ids. + */ + public function addFiles(array $element, WebformSubmissionInterface $webform_submission, array $fids) { + // Make sure the file.module is enabled since this method is called from + // \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::postSave. + if (!\Drupal::moduleHandler()->moduleExists('file')) { + return; + } + // Make sure there are files that need to added. + if (empty($fids)) { + return; + } + + /** @var \Drupal\file\FileInterface[] $files */ + $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids); + foreach ($files as $file) { + $source_uri = $file->getFileUri(); + $destination_uri = $this->getFileDestinationUri($element, $file, $webform_submission); + + // Save file if there is a new destination URI. + if ($source_uri != $destination_uri) { + $destination_uri = file_unmanaged_move($source_uri, $destination_uri); + $file->setFileUri($destination_uri); + $file->setFileName($this->fileSystem->basename($destination_uri)); + $file->save(); + } + + // Update file usage table. + // Setting file usage will also make the file's status permanent. + $this->fileUsage->delete($file, 'webform', 'webform_submission', $webform_submission->id()); + $this->fileUsage->add($file, 'webform', 'webform_submission', $webform_submission->id()); + } + } + /** * Determine the destination URI where to save an uploaded file. * @@ -871,18 +1206,28 @@ protected function getFileDestinationUri(array $element, FileInterface $file, We // Sanitize filename. // @see http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename + // @see \Drupal\webform_attachment\Element\WebformAttachmentBase::getFileName if (!empty($element['#sanitize'])) { - $destination_extension = Unicode::strtolower($destination_extension); + $destination_extension = mb_strtolower($destination_extension); $destination_basename = substr(pathinfo($destination_filename, PATHINFO_BASENAME), 0, -strlen(".$destination_extension")); - $destination_basename = Unicode::strtolower($destination_basename); + $destination_basename = mb_strtolower($destination_basename); $destination_basename = $this->transliteration->transliterate($destination_basename, $this->languageManager->getCurrentLanguage()->getId(), '-'); $destination_basename = preg_replace('([^\w\s\d\-_~,;:\[\]\(\].]|[\.]{2,})', '', $destination_basename); $destination_basename = preg_replace('/\s+/', '-', $destination_basename); $destination_basename = trim($destination_basename, '-'); - // If the basename if empty use the element's key. + + // If the basename is empty use the element's key, composite key, or type. if (empty($destination_basename)) { - $destination_basename = $element['#webform_key']; + if (isset($element['#webform_key'])) { + $destination_basename = $element['#webform_key']; + } + elseif (isset($element['#webform_composite_key'])) { + $destination_basename = $element['#webform_composite_key']; + } + else { + $destination_basename = $element['#type']; + } } $destination_filename = $destination_basename . '.' . $destination_extension; @@ -950,9 +1295,15 @@ public static function accessFileDownload($uri) { // Return file content headers. $headers = file_get_content_headers($file); - // Force blacklisted files to be downloaded. + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = \Drupal::service('file_system'); + $filename = $file_system->basename($uri); + // Force blacklisted files to be downloaded instead of opening in the browser. if (in_array($headers['Content-Type'], static::$blacklistedMimeTypes)) { - $headers['Content-Disposition'] = 'attachment'; + $headers['Content-Disposition'] = 'attachment; filename="' . Unicode::mimeHeaderEncode($filename) . '"'; + } + else { + $headers['Content-Disposition'] = 'inline; filename="' . Unicode::mimeHeaderEncode($filename) . '"'; } return $headers; @@ -975,4 +1326,53 @@ public static function getVisibleStreamWrappers() { return $stream_wrappers; } + /** + * {@inheritdoc} + */ + public function getTargetType(array $element) { + return 'file'; + } + + /** + * {@inheritdoc} + */ + public function getTargetEntity(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + if (empty($value)) { + return NULL; + } + return $this->getFile($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function getTargetEntities(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $value = $this->getValue($element, $webform_submission, $options); + if (empty($value)) { + return NULL; + } + return $this->getFiles($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function getAttachments(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $attachments = []; + $files = $this->getTargetEntities($element, $webform_submission, $options); + foreach ($files as $file) { + $attachments[] = [ + 'filecontent' => file_get_contents($file->getFileUri()), + 'filename' => $file->getFilename(), + 'filemime' => $file->getMimeType(), + 'filepath' => \Drupal::service('file_system')->realpath($file->getFileUri()), + // URI is used when debugging or resending messages. + // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::buildAttachments + '_uri' => file_create_url($file->getFileUri()), + ]; + } + return $attachments; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformMapping.php b/web/modules/webform/src/Plugin/WebformElement/WebformMapping.php index a4d02319b3..5e4c8d0346 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformMapping.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformMapping.php @@ -37,6 +37,7 @@ public function getDefaultProperties() { 'default_value' => [], // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -51,6 +52,7 @@ public function getDefaultProperties() { 'format' => $this->getItemDefaultFormat(), 'format_html' => '', 'format_text' => '', + 'format_attributes' => [], // Mapping settings. 'arrow' => '→', 'source' => [], @@ -61,6 +63,7 @@ public function getDefaultProperties() { 'destination__description' => '', // Attributes. 'wrapper_attributes' => [], + 'label_attributes' => [], ] + $this->getDefaultBaseProperties(); } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformMarkupBase.php b/web/modules/webform/src/Plugin/WebformElement/WebformMarkupBase.php index 869aa9fad0..1c78e82d37 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformMarkupBase.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformMarkupBase.php @@ -44,6 +44,7 @@ public function getDefaultProperties() { protected function getDefaultBaseProperties() { $properties = parent::getDefaultBaseProperties(); unset($properties['prepopulate']); + unset($properties['states_clear']); return $properties; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformRating.php b/web/modules/webform/src/Plugin/WebformElement/WebformRating.php index 1ce2de9a3b..63793cbb59 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformRating.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformRating.php @@ -124,6 +124,15 @@ public function form(array $form, FormStateInterface $form_state) { '#description' => $this->t('If checked, a reset button will be placed before the rating element.'), '#return_value' => TRUE, ]; + + // Only allow a rating element to be required if the min value can be + // set to 0. + $form['validation']['required_container']['#states'] = [ + 'visible' => [ + ':input[name="properties[min]"]' => ['value' => '0'], + ], + ]; + return $form; } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformSection.php b/web/modules/webform/src/Plugin/WebformElement/WebformSection.php index 061a55de17..b4545f2952 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformSection.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformSection.php @@ -22,7 +22,13 @@ class WebformSection extends ContainerBase { */ public function getDefaultProperties() { return [ + // Description/Help. 'help' => '', + 'help_title' => '', + 'description' => '', + 'more' => '', + 'more_title' => '', + // Title. 'title_tag' => \Drupal::config('webform.settings')->get('element.default_section_title_tag'), 'title_display' => '', ] + parent::getDefaultProperties(); @@ -55,6 +61,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['form']['title_tag'] = [ '#type' => 'webform_select_other', '#title' => $this->t('Title tag'), + '#description' => $this->t("The section's title HTML tag."), '#options' => [ 'h1' => $this->t('Header 1 (h1)'), 'h2' => $this->t('Header 2 (h2)'), diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformTableTrait.php b/web/modules/webform/src/Plugin/WebformElement/WebformTableTrait.php index 3d5bd0da6a..778a9aaf44 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformTableTrait.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformTableTrait.php @@ -3,6 +3,8 @@ namespace Drupal\webform\Plugin\WebformElement; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\OptGroup; +use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\WebformSubmissionInterface; /** @@ -14,10 +16,6 @@ trait WebformTableTrait { * {@inheritdoc} */ public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - if ($this->hasMultipleValues($element)) { - $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; - } - parent::prepare($element, $webform_submission); // Add missing element class. @@ -42,6 +40,12 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub } $element['#attached']['library'][] = 'webform/webform.element.' . $element['#type']; + + // Set table select element's #process callback so that fix UX + // and accessiblity issues. + if ($this->getPluginId() === 'tableselect') { + static::setProcessTableSelectCallback($element); + } } /** @@ -75,16 +79,126 @@ public function form(array $form, FormStateInterface $form_state) { protected function getTableSelectElementSelectorOptions(array $element, $input_selector = '') { $title = $this->getAdminLabel($element) . ' [' . $this->getPluginLabel() . ']'; $name = $element['#webform_key']; - $type = ($this->hasMultipleValues($element) ? $this->t('Checkbox') : $this->t('Radio')); + if ($this->hasMultipleValues($element)) { + $selectors = []; + foreach ($element['#options'] as $value => $text) { + if (is_array($text)) { + $text = $value; + } + $selectors[":input[name=\"{$name}[{$value}]$input_selector\"]"] = $text . ' [' . $this->t('Checkbox') . ']'; + } + return [$title => $selectors]; + } + else { + return [":input[name=\"{$name}\"]" => $title]; + } + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + if ($this->hasMultipleValues($element)) { + return []; + } + + $name = $element['#webform_key']; + $options = OptGroup::flattenOptions($element['#options']); + return [":input[name=\"{$name}\"]" => $options]; + } - $selectors = []; - foreach ($element['#options'] as $value => $text) { - if (is_array($text)) { - $text = $value; + /** + * Process table select and attach JavaScript. + * + * @param array $element + * An associative array containing the properties and children of + * the tableselect element. + * + * @return array + * The processed element. + * + * @see \Drupal\Core\Render\Element\Tableselect::processTableselect + */ + public static function processTableSelect(array $element) { + $element['#attributes']['class'][] = 'webform-tableselect'; + $element['#attributes']['class'][] = 'js-webform-tableselect'; + $element['#attached']['library'][] = 'webform/webform.element.tableselect'; + return $element; + } + + /** + * Process table selected options and add #title to the table's options. + * + * @param array $element + * An associative array containing the properties and children of + * the tableselect element. + * + * @return array + * The processed element. + * + * @see \Drupal\Core\Render\Element\Tableselect::processTableselect + */ + public static function processTableSelectOptions(array $element) { + foreach ($element['#options'] as $key => $choice) { + if (isset($element[$key]) && empty($element[$key]['#title'])) { + if ($title = static::getTableSelectOptionTitle($choice)) { + $element[$key]['#title'] = $title; + $element[$key]['#title_display'] = 'invisible'; + } + } + } + return $element; + } + + /** + * Set process table select element callbacks. + * + * @param array $element + * An associative array containing the properties and children of + * the table select element. + * + * @see \Drupal\Core\Render\Element\Tableselect::processTableselect + */ + public static function setProcessTableSelectCallback(array &$element) { + $class = get_called_class(); + $element['#process'] = [ + ['\Drupal\Core\Render\Element\Tableselect', 'processTableselect'], + [$class , 'processTableSelect'], + [$class , 'processTableSelectOptions'], + ]; + } + + /** + * Get table selection option title/text. + * + * Issue #2719453: Tableselect single radio button missing #title attribute + * and is not accessible, + * + * @param array $option + * A table select option. + * + * @return string|\Drupal\Component\Render\MarkupInterface|null + * Table selection option title/text. + * + * @see https://www.drupal.org/project/drupal/issues/2719453 + */ + public static function getTableSelectOptionTitle(array $option) { + if (is_array($option) && WebformArrayHelper::isAssociative($option)) { + // Get first value from custom options. + $title = reset($option); + if (is_array($title)) { + $title = \Drupal::service('renderer')->render($title); } - $selectors[":input[name=\"{$name}[{$value}]$input_selector\"]"] = $text . ' [' . $type . ']'; + return $title; + } + elseif (is_array($option) && !empty($option[0]['value'])) { + // Get value from default options. + // @see \Drupal\webform\Plugin\WebformElement\WebformTableTrait::prepare + return $option[0]['value']; + } + else { + return NULL; } - return [$title => $selectors]; } } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformTermReferenceTrait.php b/web/modules/webform/src/Plugin/WebformElement/WebformTermReferenceTrait.php index 6e48da43e5..3382c47ee3 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformTermReferenceTrait.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformTermReferenceTrait.php @@ -32,6 +32,16 @@ public function preview() { $vocabulary_id = 'tags'; } + // Make sure the vocabulary does not have more than 250 terms. + // This will prevent a fatal memory error when + // previewing term related elements. + /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $tree = $taxonomy_storage->loadTree($vocabulary_id); + if (count($tree) > 250) { + $vocabulary_id = NULL; + } + return parent::preview() + [ '#vocabulary' => $vocabulary_id, ]; diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformTime.php b/web/modules/webform/src/Plugin/WebformElement/WebformTime.php index 6b0159fe45..4f3b4923ab 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformTime.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformTime.php @@ -59,7 +59,7 @@ protected function formatTextItem(array $element, WebformSubmissionInterface $we $format = $this->getItemFormat($element); if ($format == 'value') { $time_format = (isset($element['#time_format'])) ? $element['#time_format'] : 'H:i'; - return date($time_format, strtotime($value)); + return static::formatTime($time_format, strtotime($value)); } return parent::formatTextItem($element, $webform_submission, $options); @@ -90,24 +90,24 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_select_other', '#title' => $this->t('Time format'), '#options' => [ - 'H:i' => $this->t('24 hour - @format (@time)', ['@format' => 'H:i', '@time' => date('H:i')]), - 'H:i:s' => $this->t('24 hour with seconds - @format (@time)', ['@format' => 'H:i:s', '@time' => date('H:i:s')]), - 'g:i A' => $this->t('12 hour - @format (@time)', ['@format' => 'g:i A', '@time' => date('g:i A')]), - 'g:i:s A' => $this->t('12 hour with seconds - @format (@time)', ['@format' => 'g:i:s A', '@time' => date('g:i:s A')]), + 'H:i' => $this->t('24 hour - @format (@time)', ['@format' => 'H:i', '@time' => static::formatTime('H:i')]), + 'H:i:s' => $this->t('24 hour with seconds - @format (@time)', ['@format' => 'H:i:s', '@time' => static::formatTime('H:i:s')]), + 'g:i A' => $this->t('12 hour - @format (@time)', ['@format' => 'g:i A', '@time' => static::formatTime('g:i A')]), + 'g:i:s A' => $this->t('12 hour with seconds - @format (@time)', ['@format' => 'g:i:s A', '@time' => static::formatTime('g:i:s A')]), ], - '#other__option_label' => $this->t('Custom...'), - '#other__placeholder' => $this->t('Custom time format...'), + '#other__option_label' => $this->t('Custom…'), + '#other__placeholder' => $this->t('Custom time format…'), '#other__description' => $this->t('Enter time format using <a href="http://php.net/manual/en/function.date.php">Time Input Format</a>.'), ]; $form['time']['time_container'] = $this->getFormInlineContainer(); $form['time']['time_container']['min'] = [ '#type' => 'webform_time', - '#title' => $this->t('Min'), + '#title' => $this->t('Minimum'), '#description' => $this->t('Specifies the minimum time.'), ]; $form['time']['time_container']['max'] = [ '#type' => 'webform_time', - '#title' => $this->t('Max'), + '#title' => $this->t('Maximum'), '#description' => $this->t('Specifies the maximum time.'), ]; $form['time']['step'] = [ @@ -128,4 +128,21 @@ public function form(array $form, FormStateInterface $form_state) { return $form; } + /** + * Format custom time. + * + * @param string $custom_format + * A PHP date format string suitable for input to date(). + * @param int $timestamp + * (optional) A UNIX timestamp to format. + * + * @return string + * Formatted time. + */ + protected static function formatTime($custom_format, $timestamp = NULL) { + /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ + $date_formatter = \Drupal::service('date.formatter'); + return $date_formatter->format($timestamp ?: time(), 'custom', $custom_format); + } + } diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformToggle.php b/web/modules/webform/src/Plugin/WebformElement/WebformToggle.php index 88f06fbcf1..2565a02fdf 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformToggle.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformToggle.php @@ -12,6 +12,8 @@ * label = @Translation("Toggle"), * description = @Translation("Provides a form element for toggling a single on/off state."), * category = @Translation("Advanced elements"), + * deprecated = TRUE, + * deprecated_message = @Translation("The Toogles library is not being maintained and has major accessibility issues. It has been <a href=""https://www.drupal.org/project/webform/issues/2890861"">deprecated</a> and will be removed before Webform 8.x-5.0."), * ) */ class WebformToggle extends Checkbox { diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformToggleTrait.php b/web/modules/webform/src/Plugin/WebformElement/WebformToggleTrait.php index 62dc041d99..b6e63e6159 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformToggleTrait.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformToggleTrait.php @@ -9,6 +9,17 @@ */ trait WebformToggleTrait { + /** + * {@inheritdoc} + */ + public function isExcluded() { + if (\Drupal::service('webform.libraries_manager')->isExcluded('jquery.toggles')) { + return TRUE; + } + + return parent::isExcluded(); + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformToggles.php b/web/modules/webform/src/Plugin/WebformElement/WebformToggles.php index 44950c177b..8c8839bc8c 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformToggles.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformToggles.php @@ -2,8 +2,6 @@ namespace Drupal\webform\Plugin\WebformElement; -use Drupal\webform\WebformSubmissionInterface; - /** * Provides a 'toggles' element. * @@ -12,6 +10,8 @@ * label = @Translation("Toggles"), * description = @Translation("Provides a form element for toggling multiple on/off states."), * category = @Translation("Options elements"), + * deprecated = TRUE, + * deprecated_message = @Translation("The Toogles library is not being maintained and has major accessibility issues. It has been <a href=""https://www.drupal.org/project/webform/issues/2890861"">deprecated</a> and will be removed before Webform 8.x-5.0."), * ) */ class WebformToggles extends OptionsBase { @@ -47,14 +47,6 @@ public function hasMultipleValues(array $element) { return TRUE; } - /** - * {@inheritdoc} - */ - public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { - $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; - parent::prepare($element, $webform_submission); - } - /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElement/WebformWizardPage.php b/web/modules/webform/src/Plugin/WebformElement/WebformWizardPage.php index 89aacbb353..f65f22086f 100644 --- a/web/modules/webform/src/Plugin/WebformElement/WebformWizardPage.php +++ b/web/modules/webform/src/Plugin/WebformElement/WebformWizardPage.php @@ -4,6 +4,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\webform\WebformInterface; +use Drupal\webform\WebformSubmissionInterface; /** * Provides a 'webform_wizard_page' element. @@ -11,7 +12,7 @@ * @WebformElement( * id = "webform_wizard_page", * label = @Translation("Wizard page"), - * description = @Translation("Provides an element to display multiple form elements as a page in a multistep form wizard."), + * description = @Translation("Provides an element to display multiple form elements as a page in a multi-step form wizard."), * category = @Translation("Wizard"), * ) */ @@ -66,6 +67,27 @@ public function preview() { return []; } + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array $element, WebformSubmissionInterface $webform_submission, array $options = []) { + $build = parent::formatHtmlItem($element, $webform_submission, $options); + + // Add edit page link container to preview. + // @see Drupal.behaviors.webformWizardPagesLink + if ($build && isset($options['view_mode']) && $options['view_mode'] === 'preview' && $webform_submission->getWebform()->getSetting('wizard_preview_link')) { + $build['#children']['wizard_page_link'] = [ + '#type' => 'container', + '#attributes' => [ + 'data-webform-page' => $element['#webform_key'], + 'class' => ['webform-wizard-page-edit'], + ], + ]; + } + + return $build; + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/Plugin/WebformElementAttachmentInterface.php b/web/modules/webform/src/Plugin/WebformElementAttachmentInterface.php new file mode 100644 index 0000000000..dfca60e1fe --- /dev/null +++ b/web/modules/webform/src/Plugin/WebformElementAttachmentInterface.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\webform\Plugin; + +use Drupal\webform\WebformSubmissionInterface; + +/** + * Defines the interface for webform elements can provide email attachments. + */ +interface WebformElementAttachmentInterface { + + /** + * Get email attachments. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $options + * An array of options. + * + * @return array + * An array containing email attachments which include an attachments + * 'filename', 'filemime', 'filepath', and 'filecontent'. + * + * @see \Drupal\mimemail\Utility\MimeMailFormatHelper::mimeMailHtmlBody + * @see \Drupal\smtp\Plugin\Mail\SMTPMailSystem::mail + * @see \Drupal\swiftmailer\Plugin\Mail\SwiftMailer::attachAsMimeMail + */ + public function getAttachments(array $element, WebformSubmissionInterface $webform_submission, array $options = []); + +} diff --git a/web/modules/webform/src/Plugin/WebformElementBase.php b/web/modules/webform/src/Plugin/WebformElementBase.php index 6b4c0f0183..ca66dbecfb 100644 --- a/web/modules/webform/src/Plugin/WebformElementBase.php +++ b/web/modules/webform/src/Plugin/WebformElementBase.php @@ -4,25 +4,29 @@ use Drupal\Component\Plugin\PluginBase; use Drupal\Component\Utility\NestedArray; -use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\OptGroup; use Drupal\Core\Link; +use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\Render\Element; use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\Render\Markup; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; +use Drupal\webform\Element\WebformCompositeFormElementTrait; use Drupal\webform\Element\WebformHtmlEditor; use Drupal\webform\Element\WebformMessage; use Drupal\webform\Entity\WebformOptions; use Drupal\webform\Plugin\WebformElement\Checkbox; use Drupal\webform\Plugin\WebformElement\Checkboxes; +use Drupal\webform\Plugin\WebformElement\ContainerBase; use Drupal\webform\Plugin\WebformElement\Details; +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; use Drupal\webform\Twig\TwigExtension; use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformElementHelper; @@ -49,6 +53,8 @@ class WebformElementBase extends PluginBase implements WebformElementInterface { use StringTranslationTrait; + use MessengerTrait; + use WebformCompositeFormElementTrait; /** * A logger instance. @@ -192,6 +198,7 @@ public function getDefaultProperties() { 'default_value' => '', // Description/Help. 'help' => '', + 'help_title' => '', 'description' => '', 'more' => '', 'more_title' => '', @@ -204,12 +211,9 @@ public function getDefaultProperties() { // Form validation. 'required' => FALSE, 'required_error' => '', - 'unique' => FALSE, - 'unique_user' => FALSE, - 'unique_entity' => FALSE, - 'unique_error' => '', // Attributes. 'wrapper_attributes' => [], + 'label_attributes' => [], 'attributes' => [], // Submission display. 'format' => $this->getItemDefaultFormat(), @@ -218,8 +222,19 @@ public function getDefaultProperties() { 'format_items' => $this->getItemsDefaultFormat(), 'format_items_html' => '', 'format_items_text' => '', + 'format_attributes' => [], ]; + // Unique validation. + if (!$this->isComposite()) { + $properties += [ + 'unique' => FALSE, + 'unique_user' => FALSE, + 'unique_entity' => FALSE, + 'unique_error' => '', + ]; + } + $properties += $this->getDefaultBaseProperties(); return $properties; @@ -235,11 +250,13 @@ protected function getDefaultMultipleProperties() { return [ 'multiple' => FALSE, 'multiple__header_label' => '', - 'multiple__label' => $this->t('item'), - 'multiple__labels' => $this->t('items'), - 'multiple__min_items' => 1, + 'multiple__min_items' => '', 'multiple__empty_items' => 1, - 'multiple__add_more' => 1, + 'multiple__add_more' => TRUE, + 'multiple__add_more_items' => 1, + 'multiple__add_more_button_label' => (string) $this->t('Add'), + 'multiple__add_more_input_label' => (string) $this->t('more items'), + 'multiple__no_items_message' => (string) $this->t('No items entered. Please add items below.'), 'multiple__sorting' => TRUE, 'multiple__operations' => TRUE, ]; @@ -261,6 +278,7 @@ protected function getDefaultBaseProperties() { 'flex' => 1, // Conditional logic. 'states' => [], + 'states_clear' => TRUE, // Element access. 'access_create_roles' => ['anonymous', 'authenticated'], 'access_create_users' => [], @@ -282,6 +300,7 @@ public function getTranslatableProperties() { 'title', 'label', 'help', + 'help_title', 'more', 'more_title', 'description', @@ -294,6 +313,10 @@ public function getTranslatableProperties() { 'markup', 'test', 'default_value', + 'header_label', + 'add_more_button_label', + 'add_more_input_label', + 'no_items_message', ]; } @@ -360,7 +383,7 @@ protected function getElementInfoDefaultProperty(array $element, $property_name) } $property_name = ($property_name[0] !== '#') ? '#' . $property_name : $property_name; $type = $element['#type']; - return $property_value = $this->elementInfo->getInfoProperty($type, $property_name, NULL) + return $this->elementInfo->getInfoProperty($type, $property_name, NULL) ?: $this->elementInfo->getInfoProperty("webform_$type", $property_name, NULL); } @@ -410,6 +433,13 @@ public function getPluginDescription() { return $this->pluginDefinition['description']; } + /** + * {@inheritdoc} + */ + public function getPluginCategory() { + return $this->pluginDefinition['category'] ?: $this->t('Other elements'); + } + /** * {@inheritdoc} */ @@ -452,6 +482,13 @@ public function isRoot() { return FALSE; } + /** + * {@inheritdoc} + */ + public function hasManagedFiles(array $element) { + return FALSE; + } + /** * {@inheritdoc} */ @@ -575,8 +612,8 @@ public function getRelatedTypes(array $element) { continue; } - // Skip disable or hidden. - if (!$element_instance->isEnabled() || $element_instance->isHidden()) { + // Skip disabled or hidden. + if ($element_instance->isDisabled() || $element_instance->isHidden()) { continue; } @@ -621,9 +658,6 @@ public function initialize(array &$element) { if (!empty($element['#title']) && empty($element['#admin_title'])) { $element['#admin_title'] = strip_tags($element['#title']); } - - // Replace global tokens which could include the [site:name]. - $this->replaceTokens($element); } /** @@ -649,6 +683,10 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#access'] = $this->checkAccessRules($operation, $element); } + // Enable webform template preprocessing enhancements. + // @see \Drupal\webform\Utility\WebformElementHelper::isWebformElement + $element['#webform_element'] = TRUE; + // Add #allowed_tags. $allowed_tags = $this->configFactory->get('webform.settings')->get('element.allowed_tags'); switch ($allowed_tags) { @@ -680,9 +718,17 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element['#wrapper_attributes']['class'][] = 'webform-element--title-inline'; } - // Check description markup. - if (isset($element['#description'])) { - $element['#description'] = WebformHtmlEditor::checkMarkup($element['#description']); + // Check markup properties. + $markup_properties = [ + '#description', + '#help', + '#more', + '#multiple__no_items_message', + ]; + foreach ($markup_properties as $markup_property) { + if (isset($element[$markup_property])) { + $element[$markup_property] = WebformHtmlEditor::checkMarkup($element[$markup_property]); + } } // Add default description display. @@ -697,8 +743,6 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element[$attributes_property]['class'][] = 'js-webform-tooltip-element'; $element[$attributes_property]['class'][] = 'webform-tooltip-element'; $element['#attached']['library'][] = 'webform/webform.tooltip'; - // More is not supported with tooltip. - unset($element['#more']); } // Add iCheck support. @@ -730,9 +774,15 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub $element[$attributes_property]['class'][] = 'webform-has-field-suffix'; } - // Set element's #element_validate callback so that is not replaced by - // additional #element_validate callbacks. + // Add 'data-webform-states-no-clear' attribute if #states_clear is FALSE. + if (isset($element['#states_clear']) && $element['#states_clear'] === FALSE) { + $element[$attributes_property]['data-webform-states-no-clear'] = TRUE; + } + + // Set element's #element_validate callback so that is not replaced when + // we append additional #element_validate callbacks. $this->setElementDefaultCallback($element, 'element_validate'); + $this->prepareElementValidateCallbacks($element, $webform_submission); if ($this->isInput($element)) { // Handle #readonly support. @@ -750,18 +800,6 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub if (!empty($element['#required_error'])) { $element['#attributes']['data-webform-required-error'] = $element['#required_error']; } - - // Add webform element #minlength, #unique, and/or #multiple - // validation handler. - if (isset($element['#minlength'])) { - $element['#element_validate'][] = [get_class($this), 'validateMinlength']; - } - if (isset($element['#unique'])) { - $element['#element_validate'][] = [get_class($this), 'validateUnique']; - } - if (isset($element['#multiple']) && $element['#multiple'] > 1) { - $element['#element_validate'][] = [get_class($this), 'validateMultiple']; - } } // Replace tokens for all properties. @@ -774,6 +812,14 @@ public function prepare(array &$element, WebformSubmissionInterface $webform_sub * {@inheritdoc} */ public function finalize(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + // Set element's #element_validate callback so that is not replaced when + // we append additional #pre_render callbacks. + $this->setElementDefaultCallback($element, 'pre_render'); + $this->prepareElementPreRenderCallbacks($element, $webform_submission); + + // Prepare composite element. + $this->prepareCompositeFormElement($element); + // Prepare multiple element. $this->prepareMultipleWrapper($element); @@ -865,10 +911,80 @@ protected function checkAccessRule(array $element, $operation, AccountInterface /** * {@inheritdoc} */ - public function replaceTokens(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + public function replaceTokens(array &$element, EntityInterface $entity = NULL) { foreach ($element as $key => $value) { - if (!Element::child($key)) { - $element[$key] = $this->tokenManager->replace($value, $webform_submission); + // Only replace tokens in properties. + if (Element::child($key)) { + continue; + } + + // Ignore tokens in #template and #format_* properties. + if (in_array($key, ['#template', '#format_html', '#format_text', 'format_items_html', 'format_items_text'])) { + continue; + } + + $element[$key] = $this->tokenManager->replace($value, $entity); + } + } + + /** + * Prepare an element's validation callbacks. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + */ + protected function prepareElementValidateCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + // Validation callbacks are only applicable to inputs. + if (!$this->isInput($element)) { + return; + } + + // Add webform element #minlength, #multiple, and/or #unique + // validation handler. + if (isset($element['#minlength'])) { + $element['#element_validate'][] = [get_class($this), 'validateMinlength']; + } + if (isset($element['#multiple']) && $element['#multiple'] > 1) { + $element['#element_validate'][] = [get_class($this), 'validateMultiple']; + } + if (isset($element['#unique']) && $webform_submission) { + $element['#element_validate'][] = [get_class($this), 'validateUnique']; + } + } + + /** + * Prepare an element's pre render callbacks. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + */ + protected function prepareElementPreRenderCallbacks(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + // Do nothing. + } + + /** + * Replace Core's composite #pre_render with Webform's composite #pre_render. + * + * @param array $element + * An element. + * + * @see \Drupal\Core\Render\Element\CompositeFormElementTrait + * @see \Drupal\webform\Element\WebformCompositeFormElementTrait + */ + protected function prepareCompositeFormElement(array &$element) { + if (empty($element['#pre_render'])) { + return; + } + + // Replace preRenderCompositeFormElement with + // preRenderWebformCompositeFormElement. + foreach ($element['#pre_render'] as $index => $pre_render) { + if (is_array($pre_render) && $pre_render[1] === 'preRenderCompositeFormElement') { + $element['#pre_render'][$index] = [get_called_class(), 'preRenderWebformCompositeFormElement']; } } } @@ -888,10 +1004,6 @@ protected function prepareWrapper(array &$element) { $class = get_class($this); - // Set element's #pre_render callback so that is not replaced by - // additional element #pre_render callbacks. - $this->setElementDefaultCallback($element, 'pre_render'); - // Fix #states wrapper. if ($has_states_wrapper) { $element['#pre_render'][] = [$class, 'preRenderFixStatesWrapper']; @@ -954,16 +1066,26 @@ protected function prepareMultipleWrapper(array &$element) { // Set the multiple element. $element['#element'] = $element; + // Remove properties that should only be applied to the parent element. - $element['#element'] = array_diff_key($element['#element'], array_flip(['#default_value', '#description', '#description_display', '#required', '#required_error', '#states', '#wrapper_attributes', '#prefix', '#suffix', '#element', '#tags', '#multiple'])); + $element['#element'] = array_diff_key($element['#element'], array_flip(['#access', '#default_value', '#description', '#description_display', '#required', '#required_error', '#states', '#wrapper_attributes', '#prefix', '#suffix', '#element', '#tags', '#multiple'])); + // Propagate #states to sub element. // @see \Drupal\webform\Element\WebformCompositeBase::processWebformComposite if (!empty($element['#states'])) { $element['#element']['#_webform_states'] = $element['#states']; } + // Always make the title invisible. $element['#element']['#title_display'] = 'invisible'; + // Set hidden element #after_build handler. + $element['#element']['#after_build'][] = [get_class($this), 'hiddenElementAfterBuild']; + + // Remove 'for' from the main element's label. + // This must be done after the $element['#element' is defined. + $element['#label_attributes']['webform-remove-for-attribute'] = TRUE; + // Change the element to a multiple element. $element['#type'] = 'webform_multiple'; $element['#webform_multiple'] = TRUE; @@ -1011,7 +1133,7 @@ public function displayDisabledWarning(array $element) { else { $message = $this->t('%title is a %type element, which has been disabled and will not be rendered. Please contact a site administrator.', $t_args); } - drupal_set_message($message, 'warning'); + $this->messenger()->addWarning($message); $context = [ '@title' => $this->getLabel($element), @@ -1030,13 +1152,14 @@ public function setDefaultValue(array &$element) {} * {@inheritdoc} */ public function getLabel(array $element) { - return $element['#title'] ?: $element['#webform_key']; + return (!empty($element['#title'])) ? $element['#title'] : $element['#webform_key']; } /** * {@inheritdoc} */ public function getAdminLabel(array $element) { + $element += ['#admin_title' => '', '#title' => '', '#webform_key' => '']; return $element['#admin_title'] ?: $element['#title'] ?: $element['#webform_key']; } @@ -1081,17 +1204,14 @@ public function buildText(array $element, WebformSubmissionInterface $webform_su * A render array representing an element as text or HTML. */ protected function build($format, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { - $options += [ - 'exclude_empty' => TRUE, - ]; $options['multiline'] = $this->isMultiline($element); $format_function = 'format' . ucfirst($format); $value = $this->$format_function($element, $webform_submission, $options); // Handle empty value. if ($value === '') { - // Return NULL for empty formatted value. - if (!empty($options['exclude_empty'])) { + // Return NULL if empty is excluded. + if ($this->isEmptyExcluded($element, $options)) { return NULL; } // Else set the formatted value to empty message/placeholder. @@ -1156,7 +1276,7 @@ protected function format($type, array &$element, WebformSubmissionInterface $we if (isset($options['delta'])) { return $this->$item_function($element, $webform_submission, $options); } - elseif ($this->getItemsFormat($element) == 'custom') { + elseif ($this->getItemsFormat($element) === 'custom' && !empty($element['#format_items_' . strtolower($type)])) { return $this->formatCustomItems($type, $element, $webform_submission, $options); } else { @@ -1164,7 +1284,7 @@ protected function format($type, array &$element, WebformSubmissionInterface $we } } else { - if ($this->getItemFormat($element) == 'custom') { + if ($this->getItemFormat($element) === 'custom' && !empty($element['#format_' . strtolower($type)])) { return $this->formatCustomItem($type, $element, $webform_submission, $options); } else { @@ -1184,11 +1304,13 @@ protected function format($type, array &$element, WebformSubmissionInterface $we * A webform submission. * @param array $options * An array of options. + * @param array $context + * (optional) Context to be passed to inline Twig template. * * @return array|string * The element's items formatted as plain text or a render array. */ - protected function formatCustomItems($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { + protected function formatCustomItems($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = [], array $context = []) { $name = strtolower($type); // Get value. @@ -1205,18 +1327,13 @@ protected function formatCustomItems($type, array &$element, WebformSubmissionIn $template = trim($element['#format_items_' . $name]); // Get context. - $context = (isset($options['context'])) ? $options['context'] : []; + $options += ['context' => []]; $context += [ 'value' => $value, 'items' => $items, - 'data' => $webform_submission->getData(), ]; - return [ - '#type' => 'inline_template', - '#template' => $template, - '#context' => $context, - ]; + return TwigExtension::buildTwigTemplate($webform_submission, $template, $options, $context); } /** @@ -1375,22 +1492,20 @@ protected function formatTextItems(array &$element, WebformSubmissionInterface $ * A webform submission. * @param array $options * An array of options. + * @param array $context + * (optional) Context to be passed to inline Twig template. * * @return array|string * The element's item formatted as plain text or a render array. */ - protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = []) { + protected function formatCustomItem($type, array &$element, WebformSubmissionInterface $webform_submission, array $options = [], array $context = []) { $name = strtolower($type); // Get template. $template = trim($element['#format_' . $name]); - // Get context. - $context = (isset($options['context'])) ? $options['context'] : []; - // Get context.value. - $value = $this->getValue($element, $webform_submission, $options); - $context['value'] = $value; + $context['value'] = $this->getValue($element, $webform_submission, $options); // Get content.item. $context['item'] = []; @@ -1403,15 +1518,13 @@ protected function formatCustomItem($type, array &$element, WebformSubmissionInt } } - // Add submission data to context. - $context['data'] = $webform_submission->getData(); - // Return inline template. - return [ - '#type' => 'inline_template', - '#template' => $template, - '#context' => $context, - ]; + if ($type === 'Text') { + return TwigExtension::renderTwigTemplate($webform_submission, $template, $options, $context); + } + else { + return TwigExtension::buildTwigTemplate($webform_submission, $template, $options, $context); + } } /** @@ -1437,15 +1550,7 @@ protected function formatHtmlItem(array $element, WebformSubmissionInterface $we // Build a render that used #plain_text so that HTML characters are escaped. // @see \Drupal\Core\Render\Renderer::ensureMarkupIsSafe - if ($value === '0') { - // Issue #2765609: #plain_text doesn't render empty-like values - // (e.g. 0 and "0"). - // Workaround: Use #markup until this issue is fixed. - $build = ['#markup' => $value]; - } - else { - $build = ['#plain_text' => $value]; - } + $build = ['#plain_text' => $value]; $options += ['prefixing' => TRUE]; if ($options['prefixing']) { @@ -1665,6 +1770,16 @@ public function formatTableColumn(array $element, WebformSubmissionInterface $we return $this->formatHtml($element, $webform_submission); } + /** + * {@inheritdoc} + */ + public function isEmptyExcluded(array $element, array $options) { + $options += [ + 'exclude_empty' => TRUE, + ]; + return !empty($options['exclude_empty']); + } + /****************************************************************************/ // Export methods. /****************************************************************************/ @@ -1745,11 +1860,11 @@ public static function validateMinlength(&$element, FormStateInterface &$form_st return; } - if (Unicode::strlen($element['#value']) < $element['#minlength']) { + if (!empty($element['#value']) && mb_strlen($element['#value']) < $element['#minlength']) { $t_args = [ '%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'], '%min' => $element['#minlength'], - '%length' => Unicode::strlen($element['#value']), + '%length' => mb_strlen($element['#value']), ]; $form_state->setError($element, t('%name cannot be less than %min characters but is currently %length characters long.', $t_args)); } @@ -1763,11 +1878,20 @@ public static function validateUnique(array &$element, FormStateInterface $form_ return; } + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + $name = $element['#webform_key']; $value = NestedArray::getValue($form_state->getValues(), $element['#parents']); - // Skip empty unique fields or composite arrays. - if ($value === '' || is_array($value)) { + // Skip composite elements. + $element_plugin = $element_manager->getElementInstance($element); + if ($element_plugin->isComposite()) { + return; + } + + // Skip empty values but allow for '0'. + if ($value === '' || $value === NULL || (is_array($value) && empty($value))) { return; } @@ -1777,13 +1901,13 @@ public static function validateUnique(array &$element, FormStateInterface $form_ $webform_submission = $form_object->getEntity(); $webform = $webform_submission->getWebform(); - // Build unique query. + // Build unique query which return a single duplicate value. $query = \Drupal::database()->select('webform_submission', 'ws'); $query->leftJoin('webform_submission_data', 'wsd', 'ws.sid = wsd.sid'); - $query->fields('ws', ['sid']); + $query->fields('wsd', ['value']); $query->condition('wsd.webform_id', $webform->id()); $query->condition('wsd.name', $name); - $query->condition('wsd.value', $value); + $query->condition('wsd.value', (array) $value, 'IN'); // Unique user condition. if (!empty($element['#unique_user'])) { $query->condition('ws.uid', $webform_submission->getOwnerId()); @@ -1803,24 +1927,31 @@ public static function validateUnique(array &$element, FormStateInterface $form_ if ($sid = $webform_submission->id()) { $query->condition('ws.sid', $sid, '<>'); } - // Using range() is more efficient than using countQuery() for data checks. + // Get single duplicate value. $query->range(0, 1); - $count = $query->execute()->fetchField(); + $duplicate_value = $query->execute()->fetchField(); - if ($count) { - if (isset($element['#unique_error'])) { - $form_state->setError($element, $element['#unique_error']); - } - elseif (isset($element['#title'])) { - $t_args = [ - '%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'], - '%value' => $value, - ]; - $form_state->setError($element, t('The value %value has already been submitted once for the %name element. You may have already submitted this webform, or you need to use a different value.', $t_args)); - } - else { - $form_state->setError($element); + // Skip NULL or empty string value. + if ($duplicate_value === FALSE || $duplicate_value === '') { + return; + } + + if (isset($element['#unique_error'])) { + $form_state->setError($element, $element['#unique_error']); + } + elseif (isset($element['#title'])) { + // Get #options display value. + if (isset($element['#options'])) { + $duplicate_value = WebformOptionsHelper::getOptionText($duplicate_value, $element['#options'], TRUE); } + $t_args = [ + '%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'], + '%value' => $duplicate_value, + ]; + $form_state->setError($element, t('The value %value has already been submitted once for the %name element. You may have already submitted this webform, or you need to use a different value.', $t_args)); + } + else { + $form_state->setError($element); } } @@ -1925,7 +2056,7 @@ public function getElementStateOptions() { $validation_optgroup => [ 'required' => $this->t('Required'), 'optional' => $this->t('Optional'), - ] + ], ]; // Set readwrite/readonly states for any element that supports it @@ -1958,8 +2089,6 @@ public function getElementStateOptions() { /** * {@inheritdoc} - * - * @see \Drupal\webform\Entity\Webform::getElementsSelectorOptions */ public function getElementSelectorOptions(array $element) { if ($this->hasMultipleValues($element) && $this->hasMultipleWrapper()) { @@ -1981,6 +2110,13 @@ public function getElementSelectorOptions(array $element) { } } + /** + * {@inheritdoc} + */ + public function getElementSelectorSourceValues(array $element) { + return []; + } + /** * Get an element's (sub)inputs selectors as options. * @@ -2107,49 +2243,53 @@ public function form(array $form, FormStateInterface $form_state) { ], ]; - /* Element description */ + /* Element description/help/more */ $form['element_description'] = [ '#type' => 'details', - '#title' => $this->t('Element description/help'), + '#title' => $this->t('Element description/help/more'), ]; - $form['element_description']['help'] = [ + $form['element_description']['description'] = [ '#type' => 'webform_html_editor', - '#title' => $this->t('Help text'), - '#description' => $this->t('A tooltip displayed after the title.'), + '#title' => $this->t('Description'), + '#description' => $this->t('A short description of the element used as help for the user when he/she uses the webform.'), + ]; + $form['element_description']['help'] = [ + '#type' => 'details', + '#title' => $this->t('Help'), + '#description' => $this->t("Displays a help tooltip after the element's title."), '#states' => [ 'invisible' => [ [':input[name="properties[title_display]"]' => ['value' => 'invisible']], - 'or', - [':input[name="properties[title_display]"]' => ['value' => 'attribute']], ], ], ]; - $form['element_description']['description'] = [ + $form['element_description']['help']['help_title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Help title'), + '#description' => $this->t("The text displayed in help tooltip after the element's title.") . '<br /><br />' . + $this->t("Defaults to the element's title"), + ]; + $form['element_description']['help']['help'] = [ '#type' => 'webform_html_editor', - '#title' => $this->t('Description'), - '#description' => $this->t('A short description of the element used as help for the user when he/she uses the webform.'), + '#title' => $this->t('Help text'), + '#description' => $this->t("The text displayed in help tooltip after the element's title."), ]; - $form['element_description']['more_title'] = [ + $form['element_description']['more'] = [ + '#type' => 'details', + '#title' => $this->t('More'), + '#description' => $this->t("Displays a read more hide/show widget below the element's description."), + ]; + $form['element_description']['more']['more_title'] = [ '#type' => 'textfield', - '#title' => $this->t('More label'), + '#title' => $this->t('More title'), '#description' => $this->t('The click-able label used to open and close more text.') . '<br /><br />' . $this->t('Defaults to: %value', ['%value' => $this->configFactory->get('webform.settings')->get('element.default_more_title')]), - '#states' => [ - 'invisible' => [ - ':input[name="properties[description_display]"]' => ['value' => 'tooltip'], - ], - ], ]; - $form['element_description']['more'] = [ + $form['element_description']['more']['more'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('More text'), '#description' => $this->t('A long description of the element that provides form additional information which can opened and closed.'), - '#states' => [ - 'invisible' => [ - ':input[name="properties[description_display]"]' => ['value' => 'tooltip'], - ], - ], ]; /* Form display */ @@ -2168,7 +2308,6 @@ public function form(array $form, FormStateInterface $form_state) { 'after' => $this->t('After'), 'inline' => $this->t('Inline'), 'invisible' => $this->t('Invisible'), - 'attribute' => $this->t('Attribute'), ], '#description' => $this->t('Determines the placement of the title.'), ]; @@ -2184,6 +2323,13 @@ public function form(array $form, FormStateInterface $form_state) { ], '#description' => $this->t('Determines the placement of the description.'), ]; + + // Remove unsupported title and description display from composite elements. + if ($this->isComposite()) { + unset($form['form']['display_container']['title_display']['#options']['inline']); + unset($form['form']['display_container']['description_display']['#options']['tooltip']); + } + $form['form']['field_container'] = $this->getFormInlineContainer(); $form['form']['field_container']['field_prefix'] = [ '#type' => 'textfield', @@ -2243,14 +2389,14 @@ public function form(array $form, FormStateInterface $form_state) { $form['form']['disabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Disabled'), - '#description' => $this->t('Make this element non-editable with the value <strong>ignored</strong>. Useful for displaying default value. Changeable via JavaScript.'), + '#description' => $this->t('Make this element non-editable with the user entered (e.g. via developer tools) value <strong>ignored</strong>. Useful for displaying default value. Changeable via JavaScript.'), '#return_value' => TRUE, '#weight' => 50, ]; $form['form']['readonly'] = [ '#type' => 'checkbox', '#title' => $this->t('Readonly'), - '#description' => $this->t('Make this element non-editable with the value <strong>submitted</strong>. Useful for displaying default value. Changeable via JavaScript.'), + '#description' => $this->t('Make this element non-editable with the user entered (e.g. via developer tools) value <strong>submitted</strong>. Useful for displaying default value. Changeable via JavaScript.'), '#return_value' => TRUE, '#weight' => 50, ]; @@ -2283,7 +2429,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['form']['icheck'] = [ '#type' => 'select', '#title' => 'Enhance using iCheck', - '#description' => $this->t('Replaces @type element with jQuery <a href=":href">iCheck</a> boxes.', ['@type' => Unicode::strtolower($this->getPluginLabel()), ':href' => 'http://icheck.fronteed.com/']), + '#description' => $this->t('Replaces @type element with jQuery <a href=":href">iCheck</a> boxes.', ['@type' => mb_strtolower($this->getPluginLabel()), ':href' => 'http://icheck.fronteed.com/']), '#empty_option' => $this->t('- Default -'), '#options' => [ (string) $this->t('Minimal') => [ @@ -2446,6 +2592,17 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_element_states', '#state_options' => $this->getElementStateOptions(), '#selector_options' => $webform->getElementsSelectorOptions(), + '#selector_sources' => $webform->getElementsSelectorSourceValues(), + '#disabled_message' => TRUE, + ]; + $form['conditional_logic']['states_clear'] = [ + '#type' => 'checkbox', + '#title' => 'Clear value(s) when hidden', + '#return_value' => TRUE, + '#description' => ($this instanceof ContainerBase) ? + $this->t("When this container is hidden all this container's subelement values will be cleared.") + : + $this->t("When this element is hidden, this element's value will be cleared."), ]; if ($this->hasProperty('states') && $this->hasProperty('required')) { $form['conditional_logic']['states_required_message'] = [ @@ -2476,17 +2633,16 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_codemirror', '#mode' => 'yaml', '#title' => $this->t('Default value'), - '#description' => $this->t('The default value of the webform element.'), ]; } else { $form['default']['default_value'] = [ '#type' => 'textfield', '#title' => $this->t('Default value'), - '#description' => $this->t('The default value of the webform element.'), '#maxlength' => NULL, ]; } + $form['default']['default_value']['#description'] = $this->t('The default value of the webform element.'); if ($this->hasProperty('multiple')) { $form['default']['default_value']['#description'] .= ' ' . $this->t('For multiple options, use commas to separate multiple defaults.'); } @@ -2501,6 +2657,7 @@ public function form(array $form, FormStateInterface $form_state) { ':input[name="properties[multiple][container][cardinality_number]"]' => ['value' => 1], ], ], + '#attributes' => ['data-webform-states-no-clear' => TRUE], ]; $form['multiple']['multiple__header'] = [ '#type' => 'checkbox', @@ -2513,69 +2670,119 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Table header label'), '#description' => $this->t('This is used as the table header for this webform element when displaying multiple values.'), ]; - $form['multiple']['multiple__label'] = [ - '#type' => 'textfield', - '#title' => $this->t('Item label'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], - '#description' => $this->t('This is used as the item label for this webform element when displaying multiple values.'), - ]; - $form['multiple']['multiple__labels'] = [ - '#type' => 'textfield', - '#title' => $this->t('Item labels'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], - '#description' => $this->t('This is used as the items label for this webform element when displaying multiple values.'), + $form['multiple']['multiple__no_items_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('No items message'), + '#description' => $this->t('This is used when there are no items entered.'), ]; $form['multiple']['multiple__min_items'] = [ '#type' => 'number', '#title' => $this->t('Minimum amount of items'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], - '#required' => TRUE, - '#min' => 1, + '#description' => $this->t('Minimum items defaults to 0 for optional elements and 1 for required elements.'), + '#min' => 0, '#max' => 20, ]; $form['multiple']['multiple__empty_items'] = [ '#type' => 'number', '#title' => $this->t('Number of empty items'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], '#required' => TRUE, '#min' => 0, '#max' => 20, ]; - $form['multiple']['multiple__add_more'] = [ - '#type' => 'number', - '#title' => $this->t('Number of add more items'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], - '#required' => TRUE, - '#min' => 1, - '#max' => 20, - ]; $form['multiple']['multiple__sorting'] = [ '#type' => 'checkbox', '#title' => $this->t('Allow users to sort elements.'), '#description' => $this->t('If unchecked, the elements will no longer be sortable.'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], '#return_value' => TRUE, ]; $form['multiple']['multiple__operations'] = [ '#type' => 'checkbox', '#title' => $this->t('Allow users to add/remove elements.'), '#description' => $this->t('If unchecked, the add/remove (+/x) buttons will be removed from each table row.'), - '#attributes' => ['data-webform-states-no-clear' => TRUE], '#return_value' => TRUE, ]; + $form['multiple']['multiple__add_more'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allows users to add more items'), + '#description' => $this->t('If checked, an add more input will be added below the multiple values.'), + '#return_value' => TRUE, + ]; + $form['multiple']['multiple__add_more_container'] = [ + '#type' => 'container', + '#states' => [ + 'visible' => [ + ':input[name="properties[multiple__add_more]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['multiple']['multiple__add_more_container']['multiple__add_more_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Add more button label'), + '#description' => $this->t('This is used as the add more items button label for this webform element when displaying multiple values.'), + ]; + $form['multiple']['multiple__add_more_container']['multiple__add_more_input_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Add more input label'), + '#description' => $this->t('This is used as the add more items input label for this webform element when displaying multiple values.'), + ]; + $form['multiple']['multiple__add_more_container']['multiple__add_more_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Add more button label'), + '#description' => $this->t('This is used as the add more items button label for this webform element when displaying multiple values.'), + ]; + $form['multiple']['multiple__add_more_container']['multiple__add_more_items'] = [ + '#type' => 'number', + '#title' => $this->t('Number of add more items'), + '#required' => TRUE, + '#min' => 1, + '#max' => 20, + ]; /* Wrapper attributes */ $form['wrapper_attributes'] = [ '#type' => 'details', - '#title' => $this->t('Wrapper attributes'), + '#title' => ($this->hasProperty('wrapper_type')) ? + $this->t('Wrapper type and attributes') : + $this->t('Wrapper attributes'), ]; + $form['wrapper_attributes']['wrapper_type'] = [ + '#type' => 'select', + '#title' => $this->t('Wrapper type'), + '#options' => [ + 'fieldset' => $this->t('Fieldset'), + 'form_element' => $this->t('Form element'), + 'container' => $this->t('Container'), + ], + '#description' => '<b>' . t('Fieldset') . ':</b> ' . t('Wraps inputs in a fieldset.') . ' <strong>' . t('Recommended') . '</strong>' . + '<br/><br/><b>' . t('Form element') . ':</b> ' . t('Wraps inputs in a basic form element with title and description.') . + '<br/><br/><b>' . t('Container') . ':</b> ' . t('Wraps inputs in a basic div with no title or description.'), + ]; + // Hide element description and display when using a container wrapper. + if ($this->hasProperty('wrapper_type')) { + $form['element_description']['#states'] = [ + '!visible' => [ + ':input[name="properties[wrapper_type]"]' => ['value' => 'container'], + ], + ]; + $form['form']['display_container']['#states'] = [ + '!visible' => [ + ':input[name="properties[wrapper_type]"]' => ['value' => 'container'], + ], + ]; + $form['form']['field_container']['#states'] = [ + '!visible' => [ + ':input[name="properties[wrapper_type]"]' => ['value' => 'container'], + ], + ]; + } + $form['wrapper_attributes']['wrapper_attributes'] = [ '#type' => 'webform_element_attributes', '#title' => $this->t('Wrapper'), - '#class__description' => $this->t("Apply classes to the element's wrapper around both the field and its label. Select 'custom...' to enter custom classes."), + '#class__description' => $this->t("Apply classes to the element's wrapper around both the field and its label. Select 'custom…' to enter custom classes."), '#style__description' => $this->t("Apply custom styles to the element's wrapper around both the field and its label."), - '#attributes__description' => $this->t("Enter additional attributes to be added the element's wrapper."), + '#attributes__description' => $this->t("Enter additional attributes to be added to the element's wrapper."), '#classes' => $this->configFactory->get('webform.settings')->get('element.wrapper_classes'), ]; @@ -2591,6 +2798,34 @@ public function form(array $form, FormStateInterface $form_state) { '#classes' => $this->configFactory->get('webform.settings')->get('element.classes'), ]; + /* Label attributes */ + + $form['label_attributes'] = [ + '#type' => 'details', + '#title' => $this->t('Label attributes'), + ]; + $form['label_attributes']['label_attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Label'), + '#class__description' => $this->t("Apply classes to the element's label."), + '#style__description' => $this->t("Apply custom styles to the element's label."), + '#attributes__description' => $this->t("Enter additional attributes to be added to the element's label."), + ]; + + /* Summary attributes */ + + $form['summary_attributes'] = [ + '#type' => 'details', + '#title' => $this->t('Summary attributes'), + ]; + $form['summary_attributes']['summary_attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Summary'), + '#class__description' => $this->t("Apply classes to the details' summary around both the field and its label."), + '#style__description' => $this->t("Apply custom styles to the details' summary."), + '#attributes__description' => $this->t("Enter additional attributes to be added to the details' summary."), + ]; + /* Submission display */ $has_edit_twig_access = TwigExtension::hasEditTwigAccess(); @@ -2612,9 +2847,9 @@ public function form(array $form, FormStateInterface $form_state) { $format = isset($element_properties['format']) ? $element_properties['format'] : NULL; $format_custom = ($has_edit_twig_access || $format === 'custom'); if ($format_custom) { - $form['display']['item']['format']['#options'] += ['custom' => $this->t('Custom...')]; + $form['display']['item']['format']['#options'] += ['custom' => $this->t('Custom…')]; } - $custom_states = [ + $format_custom_states = [ 'visible' => [':input[name="properties[format]"]' => ['value' => 'custom']], 'required' => [':input[name="properties[format]"]' => ['value' => 'custom']], ]; @@ -2622,47 +2857,47 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_codemirror', '#mode' => 'twig', '#title' => $this->t('Item format custom HTML'), - '#description' => $this->t('The HTML to display for a single element value. You may include HTML or <a href=":href">Twig</a>. You may enter data from the submission as per the "Replacement patterns" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), - '#states' => $custom_states, + '#description' => $this->t('The HTML to display for a single element value. You may include HTML or <a href=":href">Twig</a>. You may enter data from the submission as per the "variables" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), + '#states' => $format_custom_states, '#access' => $format_custom, ]; $form['display']['item']['format_text'] = [ '#type' => 'webform_codemirror', '#mode' => 'twig', '#title' => $this->t('Item format custom Text'), - '#description' => $this->t('The text to display for a single element value. You may include <a href=":href">Twig</a>. You may enter data from the submission as per the "Replacement patterns" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), - '#states' => $custom_states, + '#description' => $this->t('The text to display for a single element value. You may include <a href=":href">Twig</a>. You may enter data from the submission as per the "variables" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), + '#states' => $format_custom_states, '#access' => $format_custom, ]; - $items = [ - 'value' => '{{ value }}', - ]; - $formats = $this->getItemFormats(); - foreach ($formats as $format_name => $format) { - if (is_array($format)) { - foreach ($format as $sub_format_name => $sub_format) { - $items["item['$sub_format_name']"] = "{{ item['$sub_format_name'] }}"; + if ($has_edit_twig_access) { + // Containers use the 'children' variable and inputs use the + // 'value' variable. + $twig_variables = ($this instanceof ContainerBase) ? ['children' => '{{ children }}'] : ['value' => '{{ value }}']; + + // Composite Twig variables. + if ($this instanceof WebformCompositeBase) { + // Add composite elements to items. + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $twig_variables["element.$composite_key"] = "{{ element.$composite_key }}"; } } - else { - $items["item.$format_name"] = "{{ item.$format_name }}"; + + $formats = $this->getItemFormats(); + foreach ($formats as $format_name => $format) { + if (is_array($format)) { + foreach ($format as $sub_format_name => $sub_format) { + $twig_variables["item['$sub_format_name']"] = "{{ item['$sub_format_name'] }}"; + } + } + else { + $twig_variables["item.$format_name"] = "{{ item.$format_name }}"; + } } + $form['display']['item']['twig'] = TwigExtension::buildTwigHelp($twig_variables); + $form['display']['item']['twig']['#states'] = $format_custom_states; + WebformElementHelper::setPropertyRecursive($form['display']['item']['twig'], '#access', TRUE); } - $form['display']['item']['patterns'] = [ - '#type' => 'details', - '#title' => $this->t('Replacement patterns'), - '#access' => $has_edit_twig_access, - '#states' => $custom_states, - '#value' => [ - 'description' => [ - '#markup' => '<p>' . $this->t('The following replacement tokens are available for single element value.') . '</p>', - ], - 'items' => [ - '#theme' => 'item_list', - '#items' => $items, - ], - ], - ]; // Items. $form['display']['items'] = [ @@ -2685,9 +2920,9 @@ public function form(array $form, FormStateInterface $form_state) { $format_items = isset($element_properties['format_items']) ? $element_properties['format_items'] : NULL; $format_items_custom = ($has_edit_twig_access || $format_items === 'custom'); if ($format_items_custom) { - $form['display']['items']['format_items']['#options'] += ['custom' => $this->t('Custom...')]; + $form['display']['items']['format_items']['#options'] += ['custom' => $this->t('Custom…')]; } - $custom_states = [ + $format_items_custom_states = [ 'visible' => [':input[name="properties[format_items]"]' => ['value' => 'custom']], 'required' => [':input[name="properties[format_items]"]' => ['value' => 'custom']], ]; @@ -2695,36 +2930,39 @@ public function form(array $form, FormStateInterface $form_state) { '#type' => 'webform_codemirror', '#mode' => 'twig', '#title' => $this->t('Items format custom HTML'), - '#description' => $this->t('The HTML to display for multiple element values. You may include HTML or <a href=":href">Twig</a>. You may enter data from the submission as per the "Replacement patterns" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), - '#states' => $custom_states, + '#description' => $this->t('The HTML to display for multiple element values. You may include HTML or <a href=":href">Twig</a>. You may enter data from the submission as per the "variables" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), + '#states' => $format_items_custom_states, '#access' => $format_items_custom, ]; $form['display']['items']['format_items_text'] = [ '#type' => 'webform_codemirror', '#mode' => 'twig', '#title' => $this->t('Items format custom Text'), - '#description' => $this->t('The text to display for multiple element values. You may include <a href=":href">Twig</a>. You may enter data from the submission as per the "Replacement patterns" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), - '#states' => $custom_states, + '#description' => $this->t('The text to display for multiple element values. You may include <a href=":href">Twig</a>. You may enter data from the submission as per the "variables" below.', [':href' => 'http://twig.sensiolabs.org/documentation']), + '#states' => $format_items_custom_states, '#access' => $format_items_custom, ]; - $items = [ - '{{ value }}', - '{{ items }}', - ]; - $form['display']['items']['patterns'] = [ + if ($format_items_custom) { + $twig_variables = [ + '{{ value }}', + '{{ items }}', + ]; + $form['display']['items']['twig'] = TwigExtension::buildTwigHelp($twig_variables); + $form['display']['items']['twig']['#states'] = $format_items_custom_states; + WebformElementHelper::setPropertyRecursive($form['display']['items']['twig'], '#access', TRUE); + } + + $form['display']['format_attributes'] = [ '#type' => 'details', - '#title' => $this->t('Replacement patterns'), - '#access' => $has_edit_twig_access, - '#states' => $custom_states, - '#value' => [ - 'description' => [ - '#markup' => '<p>' . $this->t('The following replacement tokens are available for multiple element values.') . '</p>', - ], - 'items' => [ - '#theme' => 'item_list', - '#items' => $items, - ], - ], + '#title' => $this->t('Display wrapper attributes'), + ]; + $form['display']['format_attributes']['format_attributes'] = [ + '#type' => 'webform_element_attributes', + '#title' => $this->t('Display'), + '#class__description' => $this->t("Apply classes to the element's display wrapper. Select 'custom…' to enter custom classes."), + '#style__description' => $this->t("Apply custom styles to the element's display wrapper."), + '#attributes__description' => $this->t("Enter additional attributes to be added to the element's display wrapper."), + '#classes' => $this->configFactory->get('webform.settings')->get('element.wrapper_classes'), ]; /* Administration */ @@ -2756,17 +2994,17 @@ public function form(array $form, FormStateInterface $form_state) { $operations = [ 'create' => [ - '#title' => $this->t('Create webform submission'), + '#title' => $this->t('Create submission'), '#description' => $this->t('Select roles and users that should be able to populate this element when creating a new submission.'), '#open' => TRUE, ], 'update' => [ - '#title' => $this->t('Update webform submission'), + '#title' => $this->t('Update submission'), '#description' => $this->t('Select roles and users that should be able to update this element when updating an existing submission.'), '#open' => FALSE, ], 'view' => [ - '#title' => $this->t('View webform submission'), + '#title' => $this->t('View submission'), '#description' => $this->t('Select roles and users that should be able to view this element when viewing a submission.'), '#open' => FALSE, ], @@ -2906,8 +3144,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#parents' => ['properties', 'custom'], ]; - $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); - $this->tokenManager->elementValidate($form); // Set custom properties. @@ -2960,6 +3196,8 @@ protected function buildConfigurationFormTabs(array $form, FormStateInterface $f 'multiple', 'wrapper_attributes', 'element_attributes', + 'label_attributes', + 'summary_attributes', 'display', 'admin', 'custom', @@ -3071,7 +3309,7 @@ protected function setConfigurationFormDefaultValue(array &$form, array &$elemen case 'radios': case 'select': // Handle invalid default_value throwing - // "An illegal choice has been detected..." error. + // "An illegal choice has been detected…" error. if (!is_array($default_value) && isset($property_element['#options'])) { $flattened_options = OptGroup::flattenOptions($property_element['#options']); if (!isset($flattened_options[$default_value])) { diff --git a/web/modules/webform/src/Plugin/WebformElementInterface.php b/web/modules/webform/src/Plugin/WebformElementInterface.php index 2282570730..22fbd6dafd 100644 --- a/web/modules/webform/src/Plugin/WebformElementInterface.php +++ b/web/modules/webform/src/Plugin/WebformElementInterface.php @@ -3,6 +3,7 @@ namespace Drupal\webform\Plugin; use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -86,7 +87,7 @@ public function hasProperty($property_name); * Get the URL for the element's API documentation. * * @return \Drupal\Core\Url|null - * The the URL for the element's API documentation. + * The URL for the element's API documentation. */ public function getPluginApiUrl(); @@ -114,6 +115,14 @@ public function getPluginLabel(); */ public function getPluginDescription(); + /** + * Gets the category of the plugin instance. + * + * @return string + * The category of the plugin instance. + */ + public function getPluginCategory(); + /** * Gets the type name (aka id) of the plugin instance with the 'webform_' prefix. * @@ -249,6 +258,17 @@ public function hasMultipleWrapper(); */ public function hasMultipleValues(array $element); + /** + * Determine if the element is or includes a managed_file upload element. + * + * @param array $element + * An element. + * + * @return bool + * TRUE if the element is or includes a managed_file upload element. + */ + public function hasManagedFiles(array $element); + /** * Retrieves the default properties for the defined element type. * @@ -326,7 +346,7 @@ public function finalize(array &$element, WebformSubmissionInterface $webform_su * @return bool * TRUE is the element can be accessed by the user. * - * @see \Drupal\webform\Entity\Webform::checkAccessRules + * @see \Drupal\webform\WebformAccessRulesManagerInterface::checkWebformAccess */ public function checkAccessRules($operation, array $element, AccountInterface $account = NULL); @@ -335,10 +355,10 @@ public function checkAccessRules($operation, array $element, AccountInterface $a * * @param array $element * An element. - * @param \Drupal\webform\WebformSubmissionInterface $webform_submission - * A webform submission. + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * A webform or webform submission entity. */ - public function replaceTokens(array &$element, WebformSubmissionInterface $webform_submission = NULL); + public function replaceTokens(array &$element, EntityInterface $entity = NULL); /** * Display element disabled warning. @@ -558,6 +578,19 @@ public function getItemsDefaultFormat(); */ public function getItemsFormat(array $element); + /** + * Checks if an empty element is excluded. + * + * @param array $element + * An element. + * @param array $options + * An array of options. + * + * @return bool + * TRUE if an empty element is excluded. + */ + public function isEmptyExcluded(array $element, array $options); + /****************************************************************************/ // Preview method. /****************************************************************************/ @@ -711,9 +744,24 @@ public function getElementStateOptions(); * * @return array * An array of element selectors. + * + * @see \Drupal\webform\Entity\Webform::getElementsSelectorSourceOption */ public function getElementSelectorOptions(array $element); + /** + * Get an element's selectors source values. + * + * @param array $element + * An element. + * + * @return array + * An array of element selectors source values. + * + * @see \Drupal\webform\Entity\Webform::getElementsSelectorSourceValues + */ + public function getElementSelectorSourceValues(array $element); + /** * Get an element's (sub)input selector value. * diff --git a/web/modules/webform/src/Plugin/WebformElementManager.php b/web/modules/webform/src/Plugin/WebformElementManager.php index 51e2bda8ad..09047dc075 100644 --- a/web/modules/webform/src/Plugin/WebformElementManager.php +++ b/web/modules/webform/src/Plugin/WebformElementManager.php @@ -180,6 +180,18 @@ public function buildElement(array &$element, array $form, FormStateInterface $f $this->moduleHandler->alter($hooks, $element, $form_state, $context); } + /** + * {@inheritdoc} + */ + public function processElement(array &$element) { + $element_plugin = $this->getElementInstance($element); + $element_plugin->initialize($element); + $element_plugin->prepare($element); + $element_plugin->finalize($element); + $element_plugin->setDefaultValue($element); + return $element; + } + /** * {@inheritdoc} */ @@ -305,4 +317,11 @@ public function getAllProperties() { return $properties; } + /** + * {@inheritdoc} + */ + public function isExcluded($type) { + return $this->configFactory->get('webform.settings')->get('element.excluded_elements.' . $type) ? TRUE : FALSE; + } + } diff --git a/web/modules/webform/src/Plugin/WebformElementManagerInterface.php b/web/modules/webform/src/Plugin/WebformElementManagerInterface.php index 21ed7bdc87..1f28da3843 100644 --- a/web/modules/webform/src/Plugin/WebformElementManagerInterface.php +++ b/web/modules/webform/src/Plugin/WebformElementManagerInterface.php @@ -35,7 +35,7 @@ public function initializeElement(array &$element); * @param array $element * An associative array containing an element with a #type property. * @param array $form - * An associative array containing the structure of the form. + * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @@ -45,6 +45,21 @@ public function initializeElement(array &$element); */ public function buildElement(array &$element, array $form, FormStateInterface $form_state); + /** + * Process a form element and apply webform element specific enhancements. + * + * This method allows any form API element to be enhanced using webform + * specific features include custom validation, external libraries, + * accessibility improvements, etc… + * + * @param array $element + * An associative array containing an element with a #type property. + * + * @return array + * The processed form element with webform element specific enhancements. + */ + public function processElement(array &$element); + /** * Invoke a method for a Webform element. * @@ -60,7 +75,7 @@ public function buildElement(array &$element, array $form, FormStateInterface $f * associative array as described above. * * @return mixed|null - * Return result of the invoked method. NULL will be returned if the + * Return result of the invoked method. NULL will be returned if the * element and/or method name does not exist. * * @see \Drupal\webform\WebformSubmissionForm::prepareElements @@ -132,4 +147,15 @@ public function getTranslatableProperties(); */ public function getAllProperties(); + /** + * Determine if an element type is excluded. + * + * @param string $type + * The element type. + * + * @return bool + * TRUE if the element is excluded. + */ + public function isExcluded($type); + } diff --git a/web/modules/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php b/web/modules/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php index 776a572575..5ff951e277 100644 --- a/web/modules/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php +++ b/web/modules/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php @@ -112,7 +112,15 @@ protected function formatRecordFieldDefinitionValue(array &$record, WebformSubmi switch ($field_type) { case 'created': case 'changed': - $record[] = date('Y-m-d H:i:s', $webform_submission->get($field_name)->value); + case 'timestamp': + if (!empty($webform_submission->$field_name->value)) { + /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ + $date_formatter = \Drupal::service('date.formatter'); + $record[] = $date_formatter->format($webform_submission->$field_name->value, 'custom', 'Y-m-d H:i:s'); + } + else { + $record[] = ''; + } break; case 'entity_reference': @@ -201,9 +209,12 @@ protected function getElements() { $export_options = $this->getConfiguration(); $this->elements = $this->getWebform()->getElementsInitializedFlattenedAndHasValue('view'); + // Replace tokens which can be used in an element's #title. + $this->elements = $this->tokenManager->replace($this->elements, $this->getWebform()); if ($export_options['excluded_columns']) { $this->elements = array_diff_key($this->elements, $export_options['excluded_columns']); } + return $this->elements; } diff --git a/web/modules/webform/src/Plugin/WebformExporterBase.php b/web/modules/webform/src/Plugin/WebformExporterBase.php index eee47e893d..ee1d2ff016 100644 --- a/web/modules/webform/src/Plugin/WebformExporterBase.php +++ b/web/modules/webform/src/Plugin/WebformExporterBase.php @@ -146,6 +146,13 @@ public function isArchive() { return $this->pluginDefinition['archive']; } + /** + * {@inheritdoc} + */ + public function hasFiles() { + return $this->pluginDefinition['files']; + } + /** * {@inheritdoc} */ @@ -256,7 +263,7 @@ public function writeFooter() {} * {@inheritdoc} */ public function getFileTempDirectory() { - return file_directory_temp(); + return $this->configFactory->get('webform.settings')->get('export.temp_directory') ?: file_directory_temp(); } /** diff --git a/web/modules/webform/src/Plugin/WebformExporterInterface.php b/web/modules/webform/src/Plugin/WebformExporterInterface.php index 2d0c6eb785..ad2db2a36d 100644 --- a/web/modules/webform/src/Plugin/WebformExporterInterface.php +++ b/web/modules/webform/src/Plugin/WebformExporterInterface.php @@ -51,6 +51,14 @@ public function isExcluded(); */ public function isArchive(); + /** + * Determine if exporter can include uploaded files (in a zipped archive). + * + * @return bool + * TRUE if exporter can include uploaded files (in a zipped archive). + */ + public function hasFiles(); + /** * Determine if exporter has options. * diff --git a/web/modules/webform/src/Plugin/WebformHandler/ActionWebformHandler.php b/web/modules/webform/src/Plugin/WebformHandler/ActionWebformHandler.php index 7ed0d3f46f..ca8cea2f2c 100644 --- a/web/modules/webform/src/Plugin/WebformHandler/ActionWebformHandler.php +++ b/web/modules/webform/src/Plugin/WebformHandler/ActionWebformHandler.php @@ -25,6 +25,7 @@ * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED, * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED, * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, + * tokens = TRUE, * ) */ class ActionWebformHandler extends WebformHandlerBase { @@ -65,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio */ public function getSummary() { $configuration = $this->getConfiguration(); - $this->configuration = $configuration['settings']; + $settings = $configuration['settings']; // Get state labels. $states = [ @@ -75,7 +76,7 @@ public function getSummary() { WebformSubmissionInterface::STATE_UPDATED => $this->t('Updated'), WebformSubmissionInterface::STATE_LOCKED => $this->t('Locked'), ]; - $this->configuration['states'] = array_intersect_key($states, array_combine($this->configuration['states'], $this->configuration['states'])); + $settings['states'] = array_intersect_key($states, array_combine($settings['states'], $settings['states'])); // Get message type. $message_types = [ @@ -84,15 +85,15 @@ public function getSummary() { 'warning' => t('Warning'), 'info' => t('Info'), ]; - $this->configuration['message'] = $this->configuration['message'] ? WebformHtmlEditor::checkMarkup($this->configuration['message']) : NULL; - $this->configuration['message_type'] = $message_types[$this->configuration['message_type']]; + $settings['message'] = $settings['message'] ? WebformHtmlEditor::checkMarkup($settings['message']) : NULL; + $settings['message_type'] = $message_types[$settings['message_type']]; // Get data element keys. - $data = Yaml::decode($this->configuration['data']) ?: []; - $this->configuration['data'] = array_keys($data); + $data = Yaml::decode($settings['data']) ?: []; + $settings['data'] = array_keys($data); return [ - '#settings' => $this->configuration, + '#settings' => $settings, ] + parent::getSummary(); } @@ -126,10 +127,10 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#type' => 'checkboxes', '#title' => $this->t('Execute'), '#options' => [ - WebformSubmissionInterface::STATE_DRAFT => $this->t('...when <b>draft</b> is saved.'), - WebformSubmissionInterface::STATE_CONVERTED => $this->t('...when anonymous submission is <b>converted</b> to authenticated.'), - WebformSubmissionInterface::STATE_COMPLETED => $this->t('...when submission is <b>completed</b>.'), - WebformSubmissionInterface::STATE_UPDATED => $this->t('...when submission is <b>updated</b>.'), + WebformSubmissionInterface::STATE_DRAFT => $this->t('…when <b>draft</b> is saved.'), + WebformSubmissionInterface::STATE_CONVERTED => $this->t('…when anonymous submission is <b>converted</b> to authenticated.'), + WebformSubmissionInterface::STATE_COMPLETED => $this->t('…when submission is <b>completed</b>.'), + WebformSubmissionInterface::STATE_UPDATED => $this->t('…when submission is <b>updated</b>.'), ], '#required' => TRUE, '#access' => $results_disabled ? FALSE : TRUE, @@ -208,7 +209,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#rows' => $elements_rows, ], ]; - $form['actions']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['actions']['token_tree_link'] = $this->buildTokenTreeElement(); // Development. $form['development'] = [ @@ -223,9 +224,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['debug'], ]; - $this->tokenManager->elementValidate($form); + $this->elementTokenValidate($form); - return $this->setSettingsParentsRecursively($form); + return $this->setSettingsParents($form); } /** @@ -251,7 +252,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); - parent::applyFormStateToConfiguration($form_state); + $this->applyFormStateToConfiguration($form_state); // Cleanup states. $this->configuration['states'] = array_values(array_filter($this->configuration['states'])); @@ -300,14 +301,14 @@ protected function executeAction(WebformSubmissionInterface $webform_submission) // Append notes. if ($this->configuration['notes']) { $notes = rtrim($webform_submission->getNotes()); - $notes .= ($notes ? PHP_EOL . PHP_EOL : '') . $this->tokenManager->replace($this->configuration['notes'], $webform_submission); + $notes .= ($notes ? PHP_EOL . PHP_EOL : '') . $this->replaceTokens($this->configuration['notes'], $webform_submission); $webform_submission->setNotes($notes); } // Set data. if ($this->configuration['data']) { $data = Yaml::decode($this->configuration['data']); - $data = $this->tokenManager->replace($data, $webform_submission); + $data = $this->replaceTokens($data, $webform_submission); foreach ($data as $key => $value) { $webform_submission->setElementData($key, $value); } @@ -316,10 +317,10 @@ protected function executeAction(WebformSubmissionInterface $webform_submission) // Display message. if ($this->configuration['message']) { $message = WebformHtmlEditor::checkMarkup( - $this->tokenManager->replace($this->configuration['message'], $webform_submission) + $this->replaceTokens($this->configuration['message'], $webform_submission) ); $message_type = $this->configuration['message_type']; - drupal_set_message(\Drupal::service('renderer')->renderPlain($message), $message_type); + $this->messenger()->addMessage(\Drupal::service('renderer')->renderPlain($message), $message_type); } // Resave the webform submission without trigger any hooks or handlers. @@ -396,7 +397,7 @@ protected function displayDebug(WebformSubmissionInterface $webform_submission) '#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'], ]; - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), 'warning', TRUE); + $this->messenger()->addWarning(\Drupal::service('renderer')->renderPlain($build), TRUE); } } diff --git a/web/modules/webform/src/Plugin/WebformHandler/DebugWebformHandler.php b/web/modules/webform/src/Plugin/WebformHandler/DebugWebformHandler.php index 12b2fb1a03..748a0b83b3 100644 --- a/web/modules/webform/src/Plugin/WebformHandler/DebugWebformHandler.php +++ b/web/modules/webform/src/Plugin/WebformHandler/DebugWebformHandler.php @@ -2,10 +2,10 @@ namespace Drupal\webform\Plugin\WebformHandler; -use Drupal\Core\Serialization\Yaml; use Drupal\Core\Form\FormStateInterface; -use Drupal\webform\Utility\WebformYaml; use Drupal\webform\Plugin\WebformHandlerBase; +use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\Utility\WebformYaml; use Drupal\webform\WebformSubmissionInterface; /** @@ -27,8 +27,10 @@ class DebugWebformHandler extends WebformHandlerBase { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { - $build = ['#markup' => 'Submitted values are:<pre>' . WebformYaml::tidy(Yaml::encode($webform_submission->getData())) . '</pre>']; - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), 'warning'); + $data = $webform_submission->getData(); + WebformElementHelper::convertRenderMarkupToStrings($data); + $build = ['#markup' => 'Submitted values are:<pre>' . WebformYaml::encode($data) . '</pre>']; + $this->messenger()->addWarning(\Drupal::service('renderer')->renderPlain($build)); } } diff --git a/web/modules/webform/src/Plugin/WebformHandler/EmailWebformHandler.php b/web/modules/webform/src/Plugin/WebformHandler/EmailWebformHandler.php index f858c7ee67..fdf6787d4b 100644 --- a/web/modules/webform/src/Plugin/WebformHandler/EmailWebformHandler.php +++ b/web/modules/webform/src/Plugin/WebformHandler/EmailWebformHandler.php @@ -2,30 +2,29 @@ namespace Drupal\webform\Plugin\WebformHandler; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; -use Drupal\Component\Utility\Xss; -use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Mail\MailManagerInterface; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\Render\Markup; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\file\Entity\File; use Drupal\webform\Element\WebformMessage; use Drupal\webform\Element\WebformSelectOther; -use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase; -use Drupal\webform\Twig\TwigExtension; -use Drupal\webform\Utility\WebformElementHelper; -use Drupal\webform\Utility\WebformOptionsHelper; +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Plugin\WebformHandlerBase; use Drupal\webform\Plugin\WebformHandlerMessageInterface; +use Drupal\webform\Twig\TwigExtension; +use Drupal\webform\Utility\Mail; +use Drupal\webform\Utility\WebformElementHelper; +use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\WebformSubmissionConditionsValidatorInterface; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformThemeManagerInterface; @@ -43,6 +42,7 @@ * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED, * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED, * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, + * tokens = TRUE, * ) */ class EmailWebformHandler extends WebformHandlerBase implements WebformHandlerMessageInterface { @@ -59,9 +59,16 @@ class EmailWebformHandler extends WebformHandlerBase implements WebformHandlerMe /** * Default option value. + * + * @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::getMessageEmails */ const DEFAULT_OPTION = '_default_'; + /** + * Default value. (This is used by the handler's settings.) + */ + const DEFAULT_VALUE = '_default'; + /** * The current user. * @@ -166,17 +173,20 @@ public static function create(ContainerInterface $container, array $configuratio */ public function getSummary() { $settings = $this->getEmailConfiguration(); + // Simplify the [webform_submission:values:.*] tokens. array_walk($settings, function (&$value, $key) { if (is_string($value)) { $value = preg_replace('/\[webform:([^:]+)\]/', '[\1]', $value); $value = preg_replace('/\[webform_role:([^:]+)\]/', '[\1]', $value); + $value = preg_replace('/\[webform_access:type:([^:]+)\]/', '[\1]', $value); $value = preg_replace('/\[webform_submission:(?:node|source_entity|values):([^]]+)\]/', '[\1]', $value); $value = preg_replace('/\[webform_submission:([^]]+)\]/', '[\1]', $value); $value = preg_replace('/(:raw|:value)(:html)?\]/', ']', $value); } }); + // Set state. $states = [ WebformSubmissionInterface::STATE_DRAFT => $this->t('Draft Saved'), WebformSubmissionInterface::STATE_CONVERTED => $this->t('Converted'), @@ -186,31 +196,59 @@ public function getSummary() { ]; $settings['states'] = array_intersect_key($states, array_combine($settings['states'], $settings['states'])); + // Set theme name. + if ($settings['theme_name']) { + $settings['theme_name'] = $this->themeManager->getThemeName($settings['theme_name']); + } + return [ '#settings' => $settings, ] + parent::getSummary(); } + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + parent::setConfiguration($configuration); + + // Make sure 'default' is converted to '_default'. + // @see https://www.drupal.org/project/webform/issues/2980470 + // @see webform_update_8131() + // @todo Webform 8.x-6.x: Remove the below code. + $default_configuration = $this->defaultConfiguration(); + foreach ($this->configuration as $key => $value) { + if ($value === 'default' + && isset($default_configuration[$key]) + && $default_configuration[$key] === static::DEFAULT_VALUE) { + $this->configuration[$key] = static::DEFAULT_VALUE; + } + } + + return $this; + } + /** * {@inheritdoc} */ public function defaultConfiguration() { return [ 'states' => [WebformSubmissionInterface::STATE_COMPLETED], - 'to_mail' => 'default', + 'to_mail' => static::DEFAULT_VALUE, 'to_options' => [], 'cc_mail' => '', 'cc_options' => [], 'bcc_mail' => '', 'bcc_options' => [], - 'from_mail' => 'default', + 'from_mail' => static::DEFAULT_VALUE, 'from_options' => [], - 'from_name' => 'default', - 'subject' => 'default', - 'body' => 'default', + 'from_name' => static::DEFAULT_VALUE, + 'subject' => static::DEFAULT_VALUE, + 'body' => static::DEFAULT_VALUE, 'excluded_elements' => [], 'ignore_access' => FALSE, 'exclude_empty' => TRUE, + 'exclude_empty_checkbox' => FALSE, 'html' => TRUE, 'attachments' => FALSE, 'twig' => FALSE, @@ -219,6 +257,7 @@ public function defaultConfiguration() { 'return_path' => '', 'sender_mail' => '', 'sender_name' => '', + 'theme_name' => '', ]; } @@ -256,6 +295,7 @@ protected function getDefaultConfigurationValues() { 'return_path' => $webform_settings->get('mail.default_return_path') ?: '', 'sender_mail' => $webform_settings->get('mail.default_sender_mail') ?: '', 'sender_name' => $webform_settings->get('mail.default_sender_name') ?: '', + 'theme_name' => '', ]; return $this->defaultValues; @@ -279,13 +319,14 @@ protected function getDefaultConfigurationValue($name) { * Get mail configuration values. * * @return array - * An associative array containing email configuration values. + * An associative array containing email configuration values, + * along with the default configuration values. */ - protected function getEmailConfiguration() { + public function getEmailConfiguration() { $configuration = $this->getConfiguration(); $email = []; foreach ($configuration['settings'] as $key => $value) { - $email[$key] = ($value === 'default') ? $this->getDefaultConfigurationValue($key) : $value; + $email[$key] = ($value === static::DEFAULT_VALUE) ? $this->getDefaultConfigurationValue($key) : $value; } return $email; } @@ -299,36 +340,80 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta // Get options, mail, and text elements as options (text/value). $text_element_options_value = []; $text_element_options_raw = []; - $options_element_options = []; - $mail_element_options = []; $name_element_options = []; + $mail_element_options = []; + $options_element_options = []; $elements = $this->webform->getElementsInitializedAndFlattened(); - foreach ($elements as $key => $element) { + foreach ($elements as $element_key => $element) { $element_plugin = $this->elementManager->getElementInstance($element); if (!$element_plugin->isInput($element) || !isset($element['#type'])) { continue; } - $title = (isset($element['#title'])) ? new FormattableMarkup('@title (@key)', ['@title' => $element['#title'], '@key' => $key]) : $key; - - $text_element_options_value["[webform_submission:values:$key:value]"] = $title; - $text_element_options_raw["[webform_submission:values:$key:raw]"] = $title; + // Set title. + $element_title = (isset($element['#title'])) ? new FormattableMarkup('@title (@key)', ['@title' => $element['#title'], '@key' => $element_key]) : $element_key; + // Add options element token, which can include multiple values. if (isset($element['#options'])) { - $options_element_options["[webform_submission:values:$key:raw]"] = $title; + $options_element_options["[webform_submission:values:$element_key:raw]"] = $element_title; } - elseif (in_array($element['#type'], ['email', 'hidden', 'value', 'select', 'radios', 'textfield', 'webform_email_multiple', 'webform_email_confirm'])) { - $mail_element_options["[webform_submission:values:$key:raw]"] = $title; + + // Multiple value elements can NOT be used as a tokens. + if ($element_plugin->hasMultipleValues($element)) { + continue; } - // Name elements options can only include the 'webform_name' composite - // and single value elements. - if ($element['#type'] == 'webform_name') { - $name_element_options["[webform_submission:values:$key:value]"] = $title; + if (!$element_plugin->isComposite()) { + // Add text element value and raw tokens. + $text_element_options_value["[webform_submission:values:$element_key:value]"] = $element_title; + $text_element_options_raw["[webform_submission:values:$element_key:raw]"] = $element_title; + + // Add name element token. + $name_element_options["[webform_submission:values:$element_key:raw]"] = $element_title; + + // Add mail element token. + if (in_array($element['#type'], ['email', 'hidden', 'value', 'textfield', 'webform_email_multiple', 'webform_email_confirm'])) { + $mail_element_options["[webform_submission:values:$element_key:raw]"] = $element_title; + } } - elseif (!$element_plugin->isComposite() && !$element_plugin->hasMultipleValues($elements)) { - $name_element_options["[webform_submission:values:$key:raw]"] = $title; + + // Allow 'webform_name' composite to be used a value token. + if ($element['#type'] === 'webform_name') { + $name_element_options["[webform_submission:values:$element_key:value]"] = $element_title; + } + + // Handle composite sub elements. + if ($element_plugin instanceof WebformCompositeBase) { + $composite_elements = $element_plugin->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $composite_element_plugin = $this->elementManager->getElementInstance($element); + if (!$composite_element_plugin->isInput($element) || !isset($composite_element['#type'])) { + continue; + } + + // Set composite title. + if (isset($element['#title'])) { + $f_args = [ + '@title' => $element['#title'], + '@composite_title' => $composite_element['#title'], + '@key' => $element_key, + '@composite_key' => $composite_key, + ]; + $composite_title = new FormattableMarkup('@title: @composite_title (@key: @composite_key)', $f_args); + } + else { + $composite_title = "$element_key:$composite_key"; + } + + // Add name element token. Only applies to basic (not composite) elements. + $name_element_options["[webform_submission:values:$element_key:$composite_key:raw]"] = $composite_title; + + // Add mail element token. + if (in_array($composite_element['#type'], ['email', 'webform_email_multiple', 'webform_email_confirm'])) { + $mail_element_options["[webform_submission:values:$element_key:$composite_key:raw]"] = $composite_title; + } + } } } @@ -344,6 +429,23 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta } } + // Get email and name other. + $other_element_email_options = [ + '[site:mail]' => 'Site email address', + '[current-user:mail]' => 'Current user email address [Authenticated only]', + '[webform:author:mail]' => 'Webform author email address', + '[webform_submission:user:mail]' => 'Webform submission owner email address [Authenticated only]', + ]; + $other_element_name_options = [ + '[site:name]' => 'Site name', + '[current-user:display-name]' => 'Current user display name', + '[current-user:account-name]' => 'Current user account name', + '[webform:author:display-name]' => 'Webform author display name', + '[webform:author:account-name]' => 'Webform author account name', + '[webform_submission:author:display-name]' => 'Webform submission author display name', + '[webform_submission:author:account-name]' => 'Webform submission author account name', + ]; + // Disable client-side HTML5 validation which is having issues with hidden // element validation. // @see http://stackoverflow.com/questions/22148080/an-invalid-form-control-with-name-is-not-focusable @@ -355,19 +457,18 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Send to'), '#open' => TRUE, ]; - $form['to']['to_mail'] = $this->buildElement('to_mail', $this->t('To email'), $this->t('To email address'), $mail_element_options, $options_element_options, $roles_element_options, TRUE); - $form['to']['cc_mail'] = $this->buildElement('cc_mail', $this->t('CC email'), $this->t('CC email address'), $mail_element_options, $options_element_options, $roles_element_options, FALSE); - $form['to']['bcc_mail'] = $this->buildElement('bcc_mail', $this->t('BCC email'), $this->t('BCC email address'), $mail_element_options, $options_element_options, $roles_element_options, FALSE); + $form['to']['to_mail'] = $this->buildElement('to_mail', $this->t('To email'), $this->t('To email address'), TRUE, $mail_element_options, $options_element_options, $roles_element_options, $other_element_email_options); + $form['to']['cc_mail'] = $this->buildElement('cc_mail', $this->t('CC email'), $this->t('CC email address'), FALSE, $mail_element_options, $options_element_options, $roles_element_options, $other_element_email_options); + $form['to']['bcc_mail'] = $this->buildElement('bcc_mail', $this->t('BCC email'), $this->t('BCC email address'), FALSE, $mail_element_options, $options_element_options, $roles_element_options, $other_element_email_options); $token_types = ['webform', 'webform_submission']; // Show webform role tokens if they have been specified. if (!empty($roles_element_options)) { $token_types[] = 'webform_role'; } - - $form['to']['token_tree_link'] = $this->tokenManager->buildTreeLink( - $token_types, - $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values.') - ); + if ($this->moduleHandler->moduleExists('webform_access')) { + $token_types[] = 'webform_access'; + } + $form['to']['token_tree_link'] = $this->buildTokenTreeElement($token_types); if (empty($roles_element_options) && $this->currentUser->hasPermission('administer webform')) { $form['to']['roles_message'] = [ @@ -386,12 +487,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Send from'), '#open' => TRUE, ]; - $form['from']['from_mail'] = $this->buildElement('from_mail', $this->t('From email'), $this->t('From email address'), $mail_element_options, $options_element_options, NULL, TRUE); - $form['from']['from_name'] = $this->buildElement('from_name', $this->t('From name'), $this->t('From name'), $name_element_options); - $form['from']['token_tree_link'] = $this->tokenManager->buildTreeLink( - ['webform', 'webform_submission'], - $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values.') - ); + $form['from']['from_mail'] = $this->buildElement('from_mail', $this->t('From email'), $this->t('From email address'), TRUE, $mail_element_options, $options_element_options, NULL, $other_element_email_options); + $form['from']['from_name'] = $this->buildElement('from_name', $this->t('From name'), $this->t('From name'), FALSE, $name_element_options, NULL, NULL, $other_element_name_options); + $form['from']['token_tree_link'] = $this->buildTokenTreeElement(); // Message. $form['message'] = [ @@ -399,7 +497,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Message'), '#open' => TRUE, ]; - $form['message'] += $this->buildElement('subject', $this->t('Subject'), $this->t('subject'), $text_element_options_raw); + $form['message'] += $this->buildElement('subject', $this->t('Subject'), $this->t('subject'), FALSE, $text_element_options_raw); $has_edit_twig_access = (TwigExtension::hasEditTwigAccess() || $this->configuration['twig']); @@ -408,11 +506,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta // HTML (CKEditor), Plain text (CodeMirror), and Twig (CodeMirror) // custom body elements. $body_options = []; - $body_options[WebformSelectOther::OTHER_OPTION] = $this->t('Custom body...'); + $body_options[WebformSelectOther::OTHER_OPTION] = $this->t('Custom body…'); if ($has_edit_twig_access) { - $body_options['twig'] = $this->t('Twig template...'); + $body_options['twig'] = $this->t('Twig template…'); } - $body_options['default'] = $this->t('Default'); + $body_options[static::DEFAULT_VALUE] = $this->t('Default'); $body_options[(string) $this->t('Elements')] = $text_element_options_value; // Get default format. @@ -451,13 +549,13 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Body'), '#options' => $body_options, '#required' => TRUE, - '#parents' => ['settings', 'body'], '#default_value' => $body_select_default_value, ]; foreach ($body_default_values as $format => $default_value) { if ($format == 'html') { $form['message']['body_custom_' . $format] = [ '#type' => 'webform_html_editor', + '#format' => $this->configFactory->get('webform.settings')->get('html_editor.mail_format'), ]; } else { @@ -467,9 +565,8 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ]; } $form['message']['body_custom_' . $format] += [ - '#title' => $this->t('Body custom value (@format)', ['@label' => $format]), + '#title' => $this->t('Body custom value (@format)', ['@format' => $format]), '#title_display' => 'hidden', - '#parents' => ['settings', 'body_custom_' . $format], '#default_value' => $body_custom_default_values[$format], '#states' => [ 'visible' => [ @@ -482,6 +579,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], ], ]; + // Must set #parents because body_custom_* is not a configuration value. + // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::validateConfigurationForm + $form['message']['body_custom_' . $format]['#parents'] = ['settings', 'body_custom_' . $format]; // Default body. $form['message']['body_default_' . $format] = [ @@ -493,7 +593,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#attributes' => ['readonly' => 'readonly', 'disabled' => 'disabled'], '#states' => [ 'visible' => [ - ':input[name="settings[body]"]' => ['value' => 'default'], + ':input[name="settings[body]"]' => ['value' => static::DEFAULT_VALUE], ':input[name="settings[html]"]' => ['checked' => ($format == 'html') ? TRUE : FALSE], ], ], @@ -505,7 +605,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#mode' => 'twig', '#title' => $this->t('Body custom value (Twig)'), '#title_display' => 'hidden', - '#parents' => ['settings', 'body_custom_twig'], '#default_value' => $body_custom_default_values['twig'], '#access' => $has_edit_twig_access, '#states' => [ @@ -516,6 +615,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ':input[name="settings[body]"]' => ['value' => 'twig'], ], ], + // Must set #parents because body_custom_twig is not a configuration value. + // @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::validateConfigurationForm + '#parents' => ['settings', 'body_custom_twig'], ]; $form['message']['body_custom_twig_help'] = TwigExtension::buildTwigHelp() + [ '#access' => $has_edit_twig_access, @@ -526,10 +628,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], ]; // Tokens. - $form['message']['token_tree_link'] = $this->tokenManager->buildTreeLink( - ['webform', 'webform_submission'], - $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values and use [webform_submission:values:ELEMENT_KEY:value] to get HTML values.') - ); + $form['message']['token_tree_link'] = $this->buildTokenTreeElement(); // Elements. $form['elements'] = [ @@ -542,7 +641,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#type' => 'webform_excluded_elements', '#webform_id' => $this->webform->id(), '#default_value' => $this->configuration['excluded_elements'], - '#parents' => ['settings', 'excluded_elements'], ]; $form['elements']['ignore_access'] = [ '#type' => 'checkbox', @@ -550,18 +648,21 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => $this->t('If checked, access controls for included element will be ignored.'), '#return_value' => TRUE, '#default_value' => $this->configuration['ignore_access'], - '#parents' => ['settings', 'ignore_access'], ]; $form['elements']['exclude_empty'] = [ '#type' => 'checkbox', '#title' => t('Exclude empty elements'), '#return_value' => TRUE, '#default_value' => $this->configuration['exclude_empty'], - '#parents' => ['settings', 'exclude_empty'], ]; - + $form['elements']['exclude_empty_checkbox'] = [ + '#type' => 'checkbox', + '#title' => t('Exclude unselected checkboxes'), + '#return_value' => TRUE, + '#default_value' => $this->configuration['exclude_empty_checkbox'], + ]; $elements = $this->webform->getElementsInitializedFlattenedAndHasValue(); - foreach ($elements as $key => $element) { + foreach ($elements as $element) { if (!empty($element['#access_view_roles']) || !empty($element['#private'])) { $form['elements']['ignore_access_message'] = [ '#type' => 'webform_message', @@ -575,6 +676,35 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta } } + // Attachments. + $form['attachments'] = [ + '#type' => 'details', + '#title' => $this->t('Attachments'), + '#access' => $this->getWebform()->hasAttachments(), + ]; + if (!$this->supportsAttachments()) { + $t_args = [ + ':href_smtp' => 'https://www.drupal.org/project/smtp', + ':href_mailsystem' => 'https://www.drupal.org/project/mailsystem', + ':href_swiftmailer' => 'https://www.drupal.org/project/swiftmailer', + ]; + $form['attachments']['attachments_message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('To send email attachments, please install and configure the <a href=":href_smtp">SMTP Authentication Support</a> module or the <a href=":href_mailsystem">Mail System</a> and <a href=":href_swiftmailer">SwiftMailer</a> module.', $t_args), + '#message_type' => 'warning', + '#message_close' => TRUE, + '#message_storage' => WebformMessage::STORAGE_SESSION, + ]; + } + $form['attachments']['attachments'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Include files as attachments'), + '#description' => $this->t('If checked, only elements selected in the above email values will be attached the email.'), + '#return_value' => TRUE, + '#disabled' => !$this->supportsAttachments(), + '#default_value' => $this->configuration['attachments'], + ]; + // Additional. $results_disabled = $this->getWebform()->getSetting('results_disabled'); $form['additional'] = [ @@ -586,14 +716,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#type' => 'checkboxes', '#title' => $this->t('Send email'), '#options' => [ - WebformSubmissionInterface::STATE_DRAFT => $this->t('...when <b>draft</b> is saved.'), - WebformSubmissionInterface::STATE_CONVERTED => $this->t('...when anonymous submission is <b>converted</b> to authenticated.'), - WebformSubmissionInterface::STATE_COMPLETED => $this->t('...when submission is <b>completed</b>.'), - WebformSubmissionInterface::STATE_UPDATED => $this->t('...when submission is <b>updated</b>.'), - WebformSubmissionInterface::STATE_DELETED => $this->t('...when submission is <b>deleted</b>.'), + WebformSubmissionInterface::STATE_DRAFT => $this->t('…when <b>draft</b> is saved.'), + WebformSubmissionInterface::STATE_CONVERTED => $this->t('…when anonymous submission is <b>converted</b> to authenticated.'), + WebformSubmissionInterface::STATE_COMPLETED => $this->t('…when submission is <b>completed</b>.'), + WebformSubmissionInterface::STATE_UPDATED => $this->t('…when submission is <b>updated</b>.'), + WebformSubmissionInterface::STATE_DELETED => $this->t('…when submission is <b>deleted</b>.'), + WebformSubmissionInterface::STATE_LOCKED => $this->t('…when submission is <b>locked</b>.'), ], '#access' => $results_disabled ? FALSE : TRUE, - '#parents' => ['settings', 'states'], '#default_value' => $results_disabled ? [WebformSubmissionInterface::STATE_COMPLETED] : $this->configuration['states'], ]; $form['additional']['states_message'] = [ @@ -607,13 +737,13 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ], ]; // Settings: Reply-to. - $form['additional']['reply_to'] = $this->buildElement('reply_to', $this->t('Reply-to email'), $this->t('Reply-to email address'), $mail_element_options, NULL, NULL, FALSE); + $form['additional']['reply_to'] = $this->buildElement('reply_to', $this->t('Reply-to email'), $this->t('Reply-to email address'), FALSE, $mail_element_options, NULL, NULL, $other_element_email_options); // Settings: Return path. - $form['additional']['return_path'] = $this->buildElement('return_path', $this->t('Return path'), $this->t('Return path email address'), $mail_element_options, NULL, NULL, FALSE); + $form['additional']['return_path'] = $this->buildElement('return_path', $this->t('Return path'), $this->t('Return path email address'), FALSE, $mail_element_options, NULL, NULL, $other_element_email_options); // Settings: Sender mail. - $form['additional']['sender_mail'] = $this->buildElement('sender_mail', $this->t('Sender email'), $this->t('Sender email address'), $mail_element_options, $options_element_options); + $form['additional']['sender_mail'] = $this->buildElement('sender_mail', $this->t('Sender email'), $this->t('Sender email address'), FALSE, $mail_element_options, $options_element_options, NULL, $other_element_email_options); // Settings: Sender name. - $form['additional']['sender_name'] = $this->buildElement('sender_name', $this->t('Sender name'), $this->t('Sender name'), $name_element_options); + $form['additional']['sender_name'] = $this->buildElement('sender_name', $this->t('Sender name'), $this->t('Sender name'), FALSE, $name_element_options, NULL, NULL, $other_element_name_options); // Settings: HTML. $form['additional']['html'] = [ @@ -621,17 +751,16 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Send email as HTML'), '#return_value' => TRUE, '#access' => $this->supportsHtml(), - '#parents' => ['settings', 'html'], '#default_value' => $this->configuration['html'], ]; - // Settings: Attachments. - $form['additional']['attachments'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Include files as attachments'), - '#return_value' => TRUE, - '#access' => $this->supportsAttachments(), - '#parents' => ['settings', 'attachments'], - '#default_value' => $this->configuration['attachments'], + + // Setting: Themes. + $form['additional']['theme_name'] = [ + '#type' => 'select', + '#title' => $this->t('Theme to render this email'), + '#description' => $this->t('Select the theme that will be used to render this email.'), + '#options' => $this->themeManager->getThemeNames(), + '#default_value' => $this->configuration['theme_name'], ]; // Development. @@ -644,7 +773,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Enable debugging'), '#description' => $this->t('If checked, sent emails will be displayed onscreen to all users.'), '#return_value' => TRUE, - '#parents' => ['settings', 'debug'], '#default_value' => $this->configuration['debug'], ]; @@ -652,9 +780,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta // WORKAROUND: Convert all Render/Markup to strings. WebformElementHelper::convertRenderMarkupToStrings($form); - $this->tokenManager->elementValidate($form, $token_types); + $this->elementTokenValidate($form, $token_types); - return $form; + return $this->setSettingsParents($form); } /** @@ -736,14 +864,17 @@ public function postDelete(WebformSubmissionInterface $webform_submission) { * {@inheritdoc} */ public function getMessage(WebformSubmissionInterface $webform_submission) { - // Switch to default theme. - $this->themeManager->setDefaultTheme(); + $theme_name = $this->configuration['theme_name']; + + // Switch to custom or default theme. + $this->themeManager->setCurrentTheme($theme_name); $token_options = [ 'email' => TRUE, 'excluded_elements' => $this->configuration['excluded_elements'], 'ignore_access' => $this->configuration['ignore_access'], 'exclude_empty' => $this->configuration['exclude_empty'], + 'exclude_empty_checkbox' => $this->configuration['exclude_empty_checkbox'], 'html' => ($this->configuration['html'] && $this->supportsHtml()), ]; @@ -763,8 +894,8 @@ public function getMessage(WebformSubmissionInterface $webform_submission) { continue; } - // Determine if configuration value set to 'default'. - $is_default_configuration = ($configuration_value === 'default'); + // Determine if configuration value set to '_default'. + $is_default_configuration = ($configuration_value === static::DEFAULT_VALUE); // Determine if configuration value should use global configuration. $is_global_configuration = in_array($configuration_key, ['reply_to', 'return_path', 'sender_mail', 'sender_name']); if ($is_default_configuration || (!$configuration_value && $is_global_configuration)) { @@ -786,7 +917,7 @@ public function getMessage(WebformSubmissionInterface $webform_submission) { $token_options['clear'] = (strpos($configuration_key, '_mail') !== FALSE) ? TRUE : FALSE; // Get replace token values. - $token_value = $this->tokenManager->replace($configuration_value, $webform_submission, $token_data, $token_options); + $token_value = $this->replaceTokens($configuration_value, $webform_submission, $token_data, $token_options); // Decode entities for all message values except the HTML message body. if (!empty($token_value) && is_string($token_value) && !($token_options['html'] && $configuration_key === 'body')) { @@ -804,8 +935,8 @@ public function getMessage(WebformSubmissionInterface $webform_submission) { if ($this->configuration['html'] && $this->supportsHtml()) { // Apply optional global format to body. // NOTE: $message['body'] is not passed-thru Xss::filter() to allow - // style tags to be supoported. - if ($format = $this->configFactory->get('webform.settings')->get('html_editor.format')) { + // style tags to be supported. + if ($format = $this->configFactory->get('webform.settings')->get('html_editor.mail_format')) { $build = [ '#type' => 'processed_text', '#text' => $message['body'], @@ -821,6 +952,9 @@ public function getMessage(WebformSubmissionInterface $webform_submission) { // Add webform submission. $message['webform_submission'] = $webform_submission; + // Add handler. + $message['handler'] = $this; + // Switch back to active theme. $this->themeManager->setActiveTheme(); @@ -860,7 +994,7 @@ protected function getMessageEmails(WebformSubmissionInterface $webform_submissi $emails[] = $email_options[self::DEFAULT_OPTION]; } - // Get submission email addresseses as an array. + // Get submission email addresses as an array. $options_element_value = $webform_submission->getElementData($element_name); if (is_array($options_element_value)) { $options_values = $options_element_value; @@ -897,11 +1031,16 @@ protected function getMessageEmails(WebformSubmissionInterface $webform_submissi // Add user role email addresses to 'To', 'CC', and 'BCC'. // IMPORTANT: This is the only place where user email addresses can be - // used as tokens. This prevents the webform module from being used to - // spam users or worse...expose user email addresses to malicious users. + // used as tokens. This prevents the webform module from being used to + // spam users or worse…expose user email addresses to malicious users. if (in_array($configuration_name, ['to', 'cc', 'bcc'])) { $roles = $this->configFactory->get('webform.settings')->get('mail.roles'); - $emails = $this->tokenManager->replace($emails, $webform_submission, ['webform_role' => $roles]); + $token_data = []; + $token_data['webform_role'] = $roles; + if ($this->moduleHandler->moduleExists('webform_access')) { + $token_data['webform_access'] = $webform_submission; + } + $emails = $this->replaceTokens($emails, $webform_submission, $token_data); } // Resplit emails to make sure that emails are unique. @@ -932,37 +1071,18 @@ protected function getMessageAttachments(WebformSubmissionInterface $webform_sub } $attachments = []; - $elements = $this->webform->getElementsInitializedAndFlattened(); - foreach ($elements as $configuration_key => $element) { - $element_plugin = $this->elementManager->getElementInstance($element); - // Only elements that extend the 'Managed file' element can add - // file attachments. - if (!($element_plugin instanceof WebformManagedFileBase)) { + $elements = $this->getWebform()->getElementsInitializedAndFlattened(); + $element_attachments = $this->getWebform()->getElementsAttachments(); + foreach ($element_attachments as $element_attachment) { + // Check if the element attachment key is excluded and should not attach any files. + if (isset($this->configuration['excluded_elements'][$element_attachment])) { continue; } - // Check if the element is excluded and should not attach any files. - if (isset($this->configuration['excluded_elements'][$configuration_key])) { - continue; - } - - // Get file ids. - $fids = $webform_submission->getElementData($configuration_key); - if (empty($fids)) { - continue; - } - - /** @var \Drupal\file\FileInterface[] $files */ - $files = File::loadMultiple(is_array($fids) ? $fids : [$fids]); - foreach ($files as $file) { - $attachments[] = [ - 'filecontent' => file_get_contents($file->getFileUri()), - 'filename' => $file->getFilename(), - 'filemime' => $file->getMimeType(), - // Add URL to be used by resend webform. - 'file' => $file, - ]; - } + $element = $elements[$element_attachment]; + /** @var \Drupal\webform\Plugin\WebformElementAttachmentInterface $element_plugin */ + $element_plugin = $this->elementManager->getElementInstance($element); + $attachments = array_merge($attachments, $element_plugin->getAttachments($element, $webform_submission)); } return $attachments; } @@ -980,7 +1100,7 @@ public function sendMessage(WebformSubmissionInterface $webform_submission, arra $message['from_name'] = preg_replace('/[<>]/', '', $message['from_name']); if (!empty($message['from_name'])) { - $from = $message['from_name'] . ' <' . $from . '>'; + $from = Mail::formatDisplayName($message['from_name']) . ' <' . $from . '>'; } $current_langcode = $this->languageManager->getCurrentLanguage()->getId(); @@ -992,7 +1112,7 @@ public function sendMessage(WebformSubmissionInterface $webform_submission, arra '%form' => $this->getWebform()->label(), '%handler' => $this->label(), ]; - drupal_set_message($this->t('%form: Email not sent for %handler handler because a <em>To</em>, <em>CC</em>, or <em>BCC</em> email was not provided.', $t_args), 'warning', TRUE); + $this->messenger()->addWarning($this->t('%form: Email not sent for %handler handler because a <em>To</em>, <em>CC</em>, or <em>BCC</em> email was not provided.', $t_args), TRUE); } return; } @@ -1006,10 +1126,11 @@ public function sendMessage(WebformSubmissionInterface $webform_submission, arra '#webform_submission' => $webform_submission, '#handler' => $this, ]; - $message['body'] = trim((string) $this->themeManager->renderPlain($build)); + $theme_name = $this->configuration['theme_name']; + $message['body'] = trim((string) $this->themeManager->renderPlain($build, $theme_name)); if ($this->configuration['html']) { - switch ($this->getMailSystemSender()) { + switch ($this->getMailSystemFormatter()) { case 'swiftmailer': // SwiftMailer requires that the body be valid Markup. $message['body'] = Markup::create($message['body']); @@ -1019,24 +1140,38 @@ public function sendMessage(WebformSubmissionInterface $webform_submission, arra // Send message. $key = $this->getWebform()->id() . '_' . $this->getHandlerId(); - $this->mailManager->mail('webform', $key, $to, $current_langcode, $message, $from); - // Log message in Drupal's log. - $context = [ - '@form' => $this->getWebform()->label(), - '@title' => $this->label(), - 'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(), - ]; - $this->getLogger()->notice('@form webform sent @title email.', $context); - - // Log message in Webform's submission log. - $t_args = [ - '@from_name' => $message['from_name'], - '@from_mail' => $message['from_mail'], - '@to_mail' => $message['to_mail'], - '@subject' => $message['subject'], - ]; - $this->log($webform_submission, 'sent email', $this->t("'@subject' sent to '@to_mail' from '@from_name' [@from_mail]'.", $t_args)); + // Remove webform_submission and handler to prevent memory limit + // issues during testing. + if (drupal_valid_test_ua()) { + unset($message['webform_submission'], $message['handler']); + } + + $result = $this->mailManager->mail('webform', $key, $to, $current_langcode, $message, $from); + + if ($webform_submission->getWebform()->hasSubmissionLog()) { + // Log detailed message to the 'webform_submission' log. + $context = [ + '@from_name' => $message['from_name'], + '@from_mail' => $message['from_mail'], + '@to_mail' => $message['to_mail'], + '@subject' => $message['subject'], + 'link' => ($webform_submission->id()) ? $webform_submission->toLink($this->t('View'))->toString() : NULL, + 'webform_submission' => $webform_submission, + 'handler_id' => $this->getHandlerId(), + 'operation' => 'sent email', + ]; + $this->getLogger('webform_submission')->notice("'@subject' sent to '@to_mail' from '@from_name' [@from_mail]'.", $context); + } + else { + // Log general message to the 'webform' log. + $context = [ + '@form' => $this->getWebform()->label(), + '@title' => $this->label(), + 'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers')->toString(), + ]; + $this->getLogger('webform')->notice('@form webform sent @title email.', $context); + } // Debug by displaying send email onscreen. if ($this->configuration['debug']) { @@ -1046,10 +1181,12 @@ public function sendMessage(WebformSubmissionInterface $webform_submission, arra '%to_mail' => $message['to_mail'], '%subject' => $message['subject'], ]; - drupal_set_message($this->t("%subject sent to %to_mail from %from_name [%from_mail].", $t_args), 'warning', TRUE); + $this->messenger()->addWarning($this->t("%subject sent to %to_mail from %from_name [%from_mail].", $t_args), TRUE); $debug_message = $this->buildDebugMessage($webform_submission, $message); - drupal_set_message($this->themeManager->renderPlain($debug_message, FALSE), 'warning', TRUE); + $this->messenger()->addWarning($this->themeManager->renderPlain($debug_message), TRUE); } + + return $result['send']; } /** @@ -1114,25 +1251,12 @@ public function resendMessageForm(array $message) { '#type' => 'value', '#value' => $message['attachments'], ]; - - // Display attached files. if ($message['attachments']) { - $file_links = []; - foreach ($message['attachments'] as $attachment) { - $file_links[] = [ - '#theme' => 'file_link', - '#file' => $attachment['file'], - '#prefix' => '<div>', - '#suffix' => '</div>', - ]; - } $element['files'] = [ '#type' => 'item', '#title' => $this->t('Attachments'), - '#markup' => $this->themeManager->renderPlain($file_links), - ]; + ] + $this->buildAttachments($message['attachments']); } - // Preload HTML Editor and CodeMirror so that they can be properly // initialized when loaded via Ajax. $element['#attached']['library'][] = 'webform/webform.element.html_editor'; @@ -1167,13 +1291,22 @@ protected function supportsHtml() { * TRUE if emailing files as attachments is supported. */ protected function supportsAttachments() { - // If 'system.mail.interface.default' is 'test_mail_collector' allow - // email attachments during testing. + // If 'system.mail.interface.default' is 'test_mail_collector' + // allow email attachments during testing. if ($this->configFactory->get('system.mail')->get('interface.default') == 'test_mail_collector') { return TRUE; } - return $this->moduleHandler->moduleExists('mailsystem'); + // If webform_test.module is installed and this is a test webform + // allow email attachments. + if (strpos($this->getWebform()->id(), 'test_') === 0 + && $this->moduleHandler->moduleExists('webform_test')) { + return TRUE; + } + + // The Mail System module, which supports a variety of mail handlers, + // and the SMTP module support attachments. + return $this->moduleHandler->moduleExists('mailsystem') || $this->moduleHandler->moduleExists('smtp'); } /** @@ -1223,29 +1356,38 @@ protected function buildDebugMessage(WebformSubmissionInterface $webform_submiss $build['body'] = [ '#type' => 'item', '#title' => $this->t('Body'), - '#markup' => ($message['html']) ? $message['body'] : '<pre>' . htmlentities($message['body']) . '</pre>', - '#allowed_tags' => Xss::getAdminTagList(), + '#markup' => Markup::create('<pre>' . htmlentities($message['body']) . '</pre>'), '#wrapper_attributes' => ['style' => 'margin: 0'], ]; + // Attachments. + if (!empty($message['attachments'])) { + $build['attachments_divider'] = ['#markup' => '<hr />']; + $build['attachments'] = [ + '#type' => 'item', + '#title' => $this->t('Attachments'), + '#wrapper_attributes' => ['style' => 'margin: 0'], + 'files' => $this->buildAttachments($message['attachments']), + ]; + } return $build; } /** - * Get the Mail System's sender module name. + * Get the Mail System's formatter module name. * * @return string - * The Mail System's sender module name. + * The Mail System's formatter module name. */ - protected function getMailSystemSender() { + protected function getMailSystemFormatter() { $mailsystem_config = $this->configFactory->get('mailsystem.settings'); - // Get the default sender. - $mailsystem_sender = $mailsystem_config->get('defaults.sender'); + // Get the default formatter. + $mailsystem_formatter = $mailsystem_config->get('defaults.formatter'); // Look for a global setting for the webform module. - $mailsystem_sender = $mailsystem_config->get('modules.webform.none.sender') ?: $mailsystem_sender; + $mailsystem_formatter = $mailsystem_config->get('modules.webform.none.formatter') ?: $mailsystem_formatter; // Look for a specific setting for this webform module's email. $key = 'email_' . $this->getHandlerId(); - $mailsystem_sender = $mailsystem_config->get("modules.webform.$key.sender") ?: $mailsystem_sender; - return $mailsystem_sender; + $mailsystem_formatter = $mailsystem_config->get("modules.webform.$key.formatter") ?: $mailsystem_formatter; + return $mailsystem_formatter; } /** @@ -1270,7 +1412,7 @@ protected function getBodyDefaultValues($format = NULL) { } /** - * Build A select other element for email addresss and names. + * Build A select other element for email address and names. * * @param string $name * The element's key. @@ -1278,25 +1420,29 @@ protected function getBodyDefaultValues($format = NULL) { * The element's title. * @param string $label * The element's label. + * @param bool $required + * TRUE if the element is required. * @param array $element_options * The element options. * @param array $options_options * The options options. * @param array $role_options * The (user) role options. - * @param bool $required - * TRUE if the element is required. + * @param array $other_options + * The other options. * * @return array * A select other element. */ - protected function buildElement($name, $title, $label, array $element_options, array $options_options = NULL, array $role_options = NULL, $required = FALSE) { + protected function buildElement($name, $title, $label, $required = FALSE, array $element_options, array $options_options = NULL, array $role_options = NULL, array $other_options = NULL) { list($element_name, $element_type) = (strpos($name, '_') !== FALSE) ? explode('_', $name) : [$name, 'text']; + $default_option = $this->getDefaultConfigurationValue($name); + $options = []; - $options[WebformSelectOther::OTHER_OPTION] = $this->t('Custom @label...', ['@label' => $label]); - if ($default_option = $this->getDefaultConfigurationValue($name)) { - $options[(string) $this->t('Default')] = ['default' => $default_option]; + $options[WebformSelectOther::OTHER_OPTION] = $this->t('Custom @label…', ['@label' => $label]); + if ($default_option) { + $options[(string) $this->t('Default')] = [static::DEFAULT_VALUE => $default_option]; } if ($element_options) { $options[(string) $this->t('Elements')] = $element_options; @@ -1307,6 +1453,9 @@ protected function buildElement($name, $title, $label, array $element_options, a if ($role_options) { $options[(string) $this->t('Roles')] = $role_options; } + if ($other_options) { + $options[(string) $this->t('Other')] = $other_options; + } $ajax_wrapper = Html::getUniqueId('ajax-wrapper'); @@ -1321,12 +1470,11 @@ protected function buildElement($name, $title, $label, array $element_options, a '#options' => $options, '#empty_option' => (!$required) ? $this->t('- None -') : NULL, '#other__title' => $title, - '#other__title_display' => 'hidden', - '#other__placeholder' => $this->t('Enter @label...', ['@label' => $label]), + '#other__title_display' => 'invisible', + '#other__placeholder' => $this->t('Enter @label…', ['@label' => $label]), '#other__type' => ($element_type == 'mail') ? 'webform_email_multiple' : 'textfield', '#other__allow_tokens' => TRUE, '#required' => $required, - '#parents' => ['settings', $name], '#default_value' => $this->configuration[$name], ]; @@ -1383,18 +1531,19 @@ protected function buildElement($name, $title, $label, array $element_options, a '#value' => $this->t('Update'), '#name' => $element_name . '_update_button', '#validate' => [], - '#limit_validation_errors' => [$element[$name]['#parents']], + '#limit_validation_errors' => [['settings', $name]], '#submit' => [[get_called_class(), 'rebuildCallback']], '#ajax' => [ 'callback' => [get_called_class(), 'ajaxCallback'], 'wrapper' => $ajax_wrapper, 'progress' => ['type' => 'fullscreen'], ], + // Disable validation, hide button, add submit button trigger class. '#attributes' => [ + 'formnovalidate' => 'formnovalidate', 'class' => [ 'js-hide', "js-$ajax_wrapper-submit", - 'js-webform-novalidate', ], ], ]; @@ -1423,10 +1572,10 @@ protected function buildElement($name, $title, $label, array $element_options, a $mapping_options[self::DEFAULT_OPTION] = $this->t('Default (This email address will always be included)'); // Set placeholder emails. - $destination_placeholde_emails = ['example@example.com', '[site:mail]']; + $destination_placeholder_emails = ['example@example.com', '[site:mail]']; if ($role_options) { $role_names = array_keys($role_options); - $destination_placeholde_emails[] = ($role_names[0] === '[webform_role:authenticated]' && isset($role_names[1])) ? $role_names[1] : $role_names[0]; + $destination_placeholder_emails[] = ($role_names[0] === '[webform_role:authenticated]' && isset($role_names[1])) ? $role_names[1] : $role_names[0]; } $element[$options_name] = [ '#type' => 'webform_mapping', @@ -1434,7 +1583,6 @@ protected function buildElement($name, $title, $label, array $element_options, a '#description' => $this->t('The selected element has multiple options. You may enter email addresses for each choice. When that choice is selected, an email will be sent to the corresponding addresses. If a field is left blank, no email will be sent for that option. You may use tokens.') . '<br /><br />', '#description_display' => 'before', '#required' => TRUE, - '#parents' => ['settings', $options_name], '#default_value' => WebformOptionsHelper::decodeConfig($this->configuration[$options_name]), '#source' => $mapping_options, @@ -1444,20 +1592,47 @@ protected function buildElement($name, $title, $label, array $element_options, a '#destination__allow_tokens' => TRUE, '#destination__title' => $this->t('Email addresses'), '#destination__description' => NULL, - '#destination__placeholder' => implode(', ', $destination_placeholde_emails), + '#destination__placeholder' => implode(', ', $destination_placeholder_emails), ]; } else { $element[$options_name] = [ '#type' => 'value', '#value' => [], - '#parents' => ['settings', $options_name], ]; } return $element; } + /** + * Build attachment to be displayed via debug message and resend form. + * + * @param array $attachments + * An array of email attachments. + * + * @return array + * A renderable array containing links to attachments. + */ + protected function buildAttachments(array $attachments) { + $build = []; + foreach ($attachments as $attachment) { + $t_args = [ + '@filename' => $attachment['filename'], + '@filemime' => $attachment['filemime'], + '@filesize' => format_size(mb_strlen($attachment['filecontent'])), + ]; + if (!empty($attachment['_uri'])) { + $t_args[':href'] = $attachment['_uri']; + $build[] = ['#markup' => $this->t('<strong><a href=":href">@filename</a></strong> (@filemime) - @filesize ', $t_args)]; + } + else { + $build[] = ['#markup' => $this->t('<strong>@filename</strong> (@filemime) - @filesize ', $t_args)]; + } + } + return $build; + } + /** * Rebuild callback. * @@ -1506,4 +1681,12 @@ protected function getElementKeyFromToken($token, $format = 'raw') { } } + /** + * {@inheritdoc} + */ + protected function buildTokenTreeElement(array $token_types = [], $description = NULL) { + $description = $description ?: $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values.'); + return parent::buildTokenTreeElement($token_types, $description); + } + } diff --git a/web/modules/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php b/web/modules/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php index a315e8f1fd..524bfc0fb2 100644 --- a/web/modules/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php +++ b/web/modules/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php @@ -35,6 +35,7 @@ * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED, * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED, * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, + * tokens = TRUE, * ) */ class RemotePostWebformHandler extends WebformHandlerBase { @@ -74,6 +75,17 @@ class RemotePostWebformHandler extends WebformHandlerBase { */ protected $elementManager; + /** + * List of unsupported webform submission properties. + * + * The below properties will not being included in a remote post. + * + * @var array + */ + protected $unsupportedProperties = [ + 'metatag', + ]; + /** * {@inheritdoc} */ @@ -111,18 +123,21 @@ public static function create(ContainerInterface $container, array $configuratio */ public function getSummary() { $configuration = $this->getConfiguration(); + $settings = $configuration['settings']; + if (!$this->isResultsEnabled()) { - $configuration['settings']['updated_url'] = ''; - $configuration['settings']['deleted_url'] = ''; + $settings['updated_url'] = ''; + $settings['deleted_url'] = ''; } if (!$this->isDraftEnabled()) { - $configuration['settings']['draft_url'] = ''; + $settings['draft_url'] = ''; } if (!$this->isConvertEnabled()) { - $configuration['settings']['converted_url'] = ''; + $settings['converted_url'] = ''; } + return [ - '#settings' => $configuration['settings'], + '#settings' => $settings, ] + parent::getSummary(); } @@ -215,7 +230,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('@title URL', $t_args), '#description' => $this->t('The full URL to POST to when an existing webform submission is @state. (e.g. @url)', $t_args), '#required' => ($state === WebformSubmissionInterface::STATE_COMPLETED), - '#parents' => ['settings', $state_url], '#default_value' => $this->configuration[$state_url], ]; $form[$state][$state_custom_data] = [ @@ -223,7 +237,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#mode' => 'yaml', '#title' => $this->t('@title custom data', $t_args), '#description' => $this->t('Enter custom data that will be included when a webform submission is @state.', $t_args), - '#parents' => ['settings', $state_custom_data], '#states' => ['visible' => [':input[name="settings[' . $state_url . ']"]' => ['filled' => TRUE]]], '#default_value' => $this->configuration[$state_custom_data], ]; @@ -248,23 +261,22 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#required' => TRUE, '#options' => [ 'POST' => 'POST', + 'PUT' => 'PUT', 'GET' => 'GET', ], - '#parents' => ['settings', 'method'], '#default_value' => $this->configuration['method'], ]; $form['additional']['type'] = [ '#type' => 'select', '#title' => $this->t('Post type'), - '#description' => $this->t('Use x-www-form-urlencoded if unsure, as it is the default format for HTML webforms. You also have the option to post data in <a href="http://www.json.org/" target="_blank">JSON</a> format.'), + '#description' => $this->t('Use x-www-form-urlencoded if unsure, as it is the default format for HTML webforms. You also have the option to post data in <a href="http://www.json.org/">JSON</a> format.'), '#options' => [ 'x-www-form-urlencoded' => $this->t('x-www-form-urlencoded'), 'json' => $this->t('JSON'), ], - '#parents' => ['settings', 'type'], '#states' => [ - 'visible' => [':input[name="settings[method]"]' => ['value' => 'POST']], - 'required' => [':input[name="settings[method]"]' => ['value' => 'POST']], + '!visible' => [':input[name="settings[method]"]' => ['value' => 'GET']], + '!required' => [':input[name="settings[method]"]' => ['value' => 'GET']], ], '#default_value' => $this->configuration['type'], ]; @@ -273,22 +285,19 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#mode' => 'yaml', '#title' => $this->t('Custom data'), '#description' => $this->t('Enter custom data that will be included in all remote post requests.'), - '#parents' => ['settings', 'custom_data'], '#default_value' => $this->configuration['custom_data'], ]; $form['additional']['custom_options'] = [ '#type' => 'webform_codemirror', '#mode' => 'yaml', '#title' => $this->t('Custom options'), - '#description' => $this->t('Enter custom <a href=":href">request options</a> that will be used by the Guzzle HTTP client. Request options can included custom headers.', [':href' => 'http://docs.guzzlephp.org/en/stable/request-options.html']), - '#parents' => ['settings', 'custom_options'], + '#description' => $this->t('Enter custom <a href=":href">request options</a> that will be used by the Guzzle HTTP client. Request options can include custom headers.', [':href' => 'http://docs.guzzlephp.org/en/stable/request-options.html']), '#default_value' => $this->configuration['custom_options'], ]; $form['additional']['message'] = [ '#type' => 'webform_html_editor', '#title' => $this->t('Custom error response message'), '#description' => $this->t('This message is displayed when the response status code is not 2xx'), - '#parents' => ['settings', 'message'], '#default_value' => $this->configuration['message'], ]; $form['additional']['messages_token'] = [ @@ -299,8 +308,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form['additional']['messages'] = [ '#type' => 'webform_multiple', '#title' => $this->t('Custom error response messages'), - '#description' => $this->t('Enter custom response messages for specific status codes.') . '<br/>' . $this->t('Defaults to: %value', ['%value' => $this->messageManager->render(WebformMessageManagerInterface::SUBMISSION_EXCEPTION)]), + '#description' => $this->t('Enter custom response messages for specific status codes.') . '<br/>' . $this->t('Defaults to: %value', ['%value' => $this->messageManager->render(WebformMessageManagerInterface::SUBMISSION_EXCEPTION_MESSAGE)]), '#empty_items' => 0, + '#no_items_message' => $this->t('No error response messages entered. Please add messages below.'), '#add' => FALSE, '#element' => [ 'code' => [ @@ -324,7 +334,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Response message'), ], ], - '#parents' => ['settings', 'messages'], '#default_value' => $this->configuration['messages'], ]; @@ -338,7 +347,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Enable debugging'), '#description' => $this->t('If checked, posted submissions will be displayed onscreen to all users.'), '#return_value' => TRUE, - '#parents' => ['settings', 'debug'], '#default_value' => $this->configuration['debug'], ]; @@ -364,15 +372,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title_display' => 'invisible', '#webform_id' => $webform->id(), '#required' => TRUE, - '#parents' => ['settings', 'excluded_data'], '#default_value' => $this->configuration['excluded_data'], ]; - $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $this->elementTokenValidate($form); - $this->tokenManager->elementValidate($form); - - return $form; + return $this->setSettingsParents($form); } /** @@ -422,9 +427,13 @@ protected function remotePost($state, WebformSubmissionInterface $webform_submis $this->messageManager->setWebformSubmission($webform_submission); $request_url = $this->configuration[$state . '_url']; + $request_url = $this->replaceTokens($request_url, $webform_submission); $request_method = (!empty($this->configuration['method'])) ? $this->configuration['method'] : 'POST'; - $request_type = ($request_method == 'POST') ? $this->configuration['type'] : NULL; + $request_type = ($request_method !== 'GET') ? $this->configuration['type'] : NULL; + + // Get request options with tokens replaced. $request_options = (!empty($this->configuration['custom_options'])) ? Yaml::decode($this->configuration['custom_options']) : []; + $request_options = $this->replaceTokens($request_options, $webform_submission); try { if ($request_method === 'GET') { @@ -434,8 +443,9 @@ protected function remotePost($state, WebformSubmissionInterface $webform_submis $response = $this->httpClient->get($request_url, $request_options); } else { + $method = strtolower($request_method); $request_options[($request_type == 'json' ? 'json' : 'form_params')] = $this->getRequestData($state, $webform_submission); - $response = $this->httpClient->post($request_url, $request_options); + $response = $this->httpClient->$method($request_url, $request_options); } } catch (RequestException $request_exception) { @@ -467,7 +477,7 @@ protected function remotePost($state, WebformSubmissionInterface $webform_submis if ($submission_has_token) { $response_data = $this->getResponseData($response); $token_data = ['webform_handler' => [$this->getHandlerId() => [$state => $response_data]]]; - $submission_data = $this->tokenManager->replace($submission_data, $webform_submission, $token_data); + $submission_data = $this->replaceTokens($submission_data, $webform_submission, $token_data); $webform_submission->setData($submission_data); // Resave changes to the submission data without invoking any hooks // or handlers. @@ -494,10 +504,16 @@ protected function getRequestData($state, WebformSubmissionInterface $webform_su // Get submission and elements data. $data = $webform_submission->toArray(TRUE); - // Flatten data. - // Prioritizing elements before the submissions fields. - $data = $data['data'] + $data; + // Remove unsupported properties from data. + // These are typically added by other module's like metatag. + $unsupported_properties = array_combine($this->unsupportedProperties, $this->unsupportedProperties); + $data = array_diff_key($data, $unsupported_properties); + + // Flatten data and prioritize the element data over the + // webform submission data. + $element_data = $data['data']; unset($data['data']); + $data = $element_data + $data; // Excluded selected submission data. $data = array_diff_key($data, $this->configuration['excluded_data']); @@ -505,6 +521,10 @@ protected function getRequestData($state, WebformSubmissionInterface $webform_su // Append uploaded file name, uri, and base64 data to data. $webform = $this->getWebform(); foreach ($data as $element_key => $element_value) { + if (empty($element_value)) { + continue; + } + $element = $webform->getElement($element_key); if (!$element) { continue; @@ -515,15 +535,17 @@ protected function getRequestData($state, WebformSubmissionInterface $webform_su continue; } - /** @var \Drupal\file\FileInterface $file */ - $file = File::load($element_value); - if (!$file) { - continue; + if ($element_plugin->hasMultipleValues($element)) { + foreach ($element_value as $fid) { + $data['_' . $element_key][] = $this->getResponseFileData($fid); + } + } + else { + $data['_' . $element_key] = $this->getResponseFileData($element_value); + // @deprecated in Webform 8.x-5.0-rc17. Use new format + // The code needs to be removed before 8.x-5.0 or 8.x-6.x. + $data += $this->getResponseFileData($element_value, $element_key . '__'); } - - $data[$element_key .'__name'] = $file->getFilename(); - $data[$element_key .'__uri'] = $file->getFileUri(); - $data[$element_key .'__data'] = base64_encode(file_get_contents($file->getFileUri())); } // Append custom data. @@ -537,11 +559,39 @@ protected function getRequestData($state, WebformSubmissionInterface $webform_su } // Replace tokens. - $data = $this->tokenManager->replace($data, $webform_submission); + $data = $this->replaceTokens($data, $webform_submission); return $data; } + /** + * Get response file data. + * + * @param int $fid + * A file id. + * @param string|null $prefix + * A prefix to prepended to data. + * + * @return array + * An associative array containing file data (name, uri, mime, and data). + */ + protected function getResponseFileData($fid, $prefix = '') { + /** @var \Drupal\file\FileInterface $file */ + $file = File::load($fid); + if (!$file) { + return []; + } + + $data = []; + $data[$prefix . 'id'] = (int) $file->id(); + $data[$prefix . 'name'] = $file->getFilename(); + $data[$prefix . 'uri'] = $file->getFileUri(); + $data[$prefix . 'mime'] = $file->getMimeType(); + $data[$prefix . 'uuid'] = $file->uuid(); + $data[$prefix . 'data'] = base64_encode(file_get_contents($file->getFileUri())); + return $data; + } + /** * Get response data. * @@ -602,10 +652,10 @@ protected function isDraftEnabled() { } /** - * Determine if converting anoynmous submissions to authenticated is enabled. + * Determine if converting anonymous submissions to authenticated is enabled. * * @return bool - * TRUE if converting anoynmous submissions to authenticated is enabled. + * TRUE if converting anonymous submissions to authenticated is enabled. */ protected function isConvertEnabled() { return $this->isDraftEnabled() && ($this->getWebform()->getSetting('form_convert_anonymous') === TRUE); @@ -761,7 +811,7 @@ protected function debug($message, $state, $request_url, $request_method, $reque '#markup' => $message, ]; - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), $type); + $this->messenger()->addMessage(\Drupal::service('renderer')->renderPlain($build), $type); } /** @@ -810,12 +860,12 @@ protected function handleError($state, $message, $request_url, $request_method, ], ]; $build_message = [ - '#markup' => $this->tokenManager->replace($custom_response_message, $this->getWebform(), $token_data) + '#markup' => $this->replaceTokens($custom_response_message, $this->getWebform(), $token_data), ]; - drupal_set_message(\Drupal::service('renderer')->renderPlain($build_message), 'error'); + $this->messenger()->addError(\Drupal::service('renderer')->renderPlain($build_message)); } else { - $this->messageManager->display(WebformMessageManagerInterface::SUBMISSION_EXCEPTION, 'error'); + $this->messageManager->display(WebformMessageManagerInterface::SUBMISSION_EXCEPTION_MESSAGE, 'error'); } } @@ -829,13 +879,23 @@ protected function handleError($state, $message, $request_url, $request_method, * A custom custom response message. */ protected function getCustomResponseMessage($response) { - $status_code = $response->getStatusCode(); - foreach ($this->configuration['messages'] as $message_item) { - if ($message_item['code'] == $status_code) { - return $message_item['message']; + if ($response instanceof ResponseInterface) { + $status_code = $response->getStatusCode(); + foreach ($this->configuration['messages'] as $message_item) { + if ($message_item['code'] == $status_code) { + return $message_item['message']; + } } } return (!empty($this->configuration['message'])) ? $this->configuration['message'] : ''; } + /** + * {@inheritdoc} + */ + protected function buildTokenTreeElement(array $token_types = [], $description = NULL) { + $description = $description ?: $this->t('Use [webform_submission:values:ELEMENT_KEY:raw] to get plain text values.'); + return parent::buildTokenTreeElement($token_types, $description); + } + } diff --git a/web/modules/webform/src/Plugin/WebformHandler/SettingsWebformHandler.php b/web/modules/webform/src/Plugin/WebformHandler/SettingsWebformHandler.php index 11cf72ea23..40cb28ea53 100644 --- a/web/modules/webform/src/Plugin/WebformHandler/SettingsWebformHandler.php +++ b/web/modules/webform/src/Plugin/WebformHandler/SettingsWebformHandler.php @@ -27,6 +27,7 @@ * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED, * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED, * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, + * tokens = TRUE, * ) */ class SettingsWebformHandler extends WebformHandlerBase { @@ -75,16 +76,20 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function getSummary() { + $configuration = $this->getConfiguration(); + $settings = $configuration['settings']; + $setting_definitions = $this->getSettingsDefinitions(); - $settings = $this->getSettingsOverride(); - foreach ($settings as $name => $value) { - $settings[$name] = [ + $setting_override = $this->getSettingsOverride(); + foreach ($setting_override as $name => $value) { + $settings['settings'][$name] = [ 'title' => $setting_definitions[$name]['label'], 'value' => $value, ]; } + return [ - '#settings' => $this->configuration + ['settings' => $settings], + '#settings' => $settings, ] + parent::getSummary(); } @@ -125,7 +130,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description' => $this->t('A message to be displayed on the preview page.'), '#default_value' => $this->configuration['preview_message'], ]; - $form['preview_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['preview_settings']['token_tree_link'] = $this->buildTokenTreeElement(); // Confirmation settings. $confirmation_type = $this->getWebform()->getSetting('confirmation_type'); @@ -159,7 +164,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['confirmation_message'], '#access' => !empty($this->configuration['confirmation_message']) || $has_confirmation_message, ]; - $form['confirmation_settings']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['confirmation_settings']['token_tree_link'] = $this->buildTokenTreeElement(); // Custom settings. $custom_settings = $this->configuration; @@ -176,6 +181,8 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#title' => $this->t('Custom settings (YAML)'), '#description' => $this->t('Enter the setting name and value as YAML.'), '#default_value' => $custom_settings, + // Must set #parents because custom is not a configuration value. + // @see \Drupal\webform\Plugin\WebformHandler\SettingsWebformHandler::submitConfigurationForm '#parents' => ['settings', 'custom'], ]; @@ -221,9 +228,9 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['debug'], ]; - $this->tokenManager->elementValidate($form); + $this->elementTokenValidate($form); - return $this->setSettingsParentsRecursively($form); + return $this->setSettingsParents($form); } /** @@ -248,10 +255,10 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { // Completely reset configuration so that custom configuration will always // be reset. - $this->configuration = []; + $this->configuration = $this->defaultConfiguration(); parent::submitConfigurationForm($form, $form_state); - parent::applyFormStateToConfiguration($form_state); + $this->applyFormStateToConfiguration($form_state); // Remove all empty strings from preview and confirmation settings. $this->configuration = array_filter($this->configuration); @@ -331,7 +338,7 @@ protected function displayDebug(WebformSubmissionInterface $webform_submission) '#header' => $header, '#rows' => $rows, ]; - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), 'warning', FALSE); + $this->messenger()->addWarning(\Drupal::service('renderer')->renderPlain($build)); } /****************************************************************************/ @@ -389,7 +396,7 @@ protected function getSubmissionSettingsOverride(WebformSubmissionInterface $web // Replace token value and cast booleans and integers. $type = $settings_definitions[$name]['type']; if (in_array($type, ['boolean', 'integer'])) { - $value = $this->tokenManager->replace($value, $webform_submission); + $value = $this->replaceTokens($value, $webform_submission); settype($value, $type); $settings_override[$name] = $value; } diff --git a/web/modules/webform/src/Plugin/WebformHandlerBase.php b/web/modules/webform/src/Plugin/WebformHandlerBase.php index 1da7c877e0..e098270fd0 100644 --- a/web/modules/webform/src/Plugin/WebformHandlerBase.php +++ b/web/modules/webform/src/Plugin/WebformHandlerBase.php @@ -3,11 +3,13 @@ namespace Drupal\webform\Plugin; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Plugin\PluginBase; -use Drupal\Core\Render\Element; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionConditionsValidatorInterface; use Drupal\webform\WebformSubmissionInterface; @@ -30,6 +32,13 @@ abstract class WebformHandlerBase extends PluginBase implements WebformHandlerIn */ protected $webform = NULL; + /** + * The webform submission. + * + * @var \Drupal\webform\WebformSubmissionInterface + */ + protected $webformSubmission = NULL; + /** * The webform handler ID. * @@ -93,6 +102,13 @@ abstract class WebformHandlerBase extends PluginBase implements WebformHandlerIn */ protected $conditionsValidator; + /** + * The webform token manager. + * + * @var \Drupal\webform\WebformTokenManagerInterface + */ + protected $tokenManager; + /** * Constructs a WebformHandlerBase object. * @@ -101,7 +117,7 @@ abstract class WebformHandlerBase extends PluginBase implements WebformHandlerIn * webform. Make sure not include any services as a dependency injection * that directly connect to the database. This will prevent * "LogicException: The database connection is not serializable." exceptions - * from being thrown when a form is serialized via an Ajax callaback and/or + * from being thrown when a form is serialized via an Ajax callback and/or * form build. * * @param array $configuration @@ -128,6 +144,10 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition $this->configFactory = $config_factory; $this->submissionStorage = $entity_type_manager->getStorage('webform_submission'); $this->conditionsValidator = $conditions_validator; + + // @todo Webform 8.x-6.x: Properly inject the token manager. + // @todo Webform 8.x-6.x: Update handlers that injects the token manager. + $this->tokenManager = \Drupal::service('webform.token_manager'); } /** @@ -160,6 +180,21 @@ public function getWebform() { return $this->webform; } + /** + * {@inheritdoc} + */ + public function setWebformSubmission(WebformSubmissionInterface $webform_submission = NULL) { + $this->webformSubmission = $webform_submission; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWebformSubmission() { + return $this->webformSubmission; + } + /** * {@inheritdoc} */ @@ -199,6 +234,13 @@ public function supportsConditions() { return $this->pluginDefinition['conditions']; } + /** + * {@inheritdoc} + */ + public function supportsTokens() { + return $this->pluginDefinition['tokens']; + } + /** * {@inheritdoc} */ @@ -274,11 +316,26 @@ public function getWeight() { return $this->weight; } + /** + * {@inheritdoc} + */ + public function enable() { + return $this->setStatus(TRUE); + } + + /** + * {@inheritdoc} + */ + public function disable() { + return $this->setStatus(FALSE); + } + /** * {@inheritdoc} */ public function isExcluded() { - return $this->configFactory->get('webform.settings')->get('handler.excluded_handlers.' . $this->pluginDefinition['id']) ? TRUE : FALSE; + return $this->configFactory->get('webform.settings') + ->get('handler.excluded_handlers.' . $this->pluginDefinition['id']) ? TRUE : FALSE; } /** @@ -325,8 +382,14 @@ public function checkConditions(WebformSubmissionInterface $webform_submission) return TRUE; } + // Get conditions. $state = key($conditions); $conditions = $conditions[$state]; + + // Replace tokens in conditions. + $conditions = $this->replaceTokens($conditions, $webform_submission); + + // Validation conditions. $result = $this->conditionsValidator->validateConditions($conditions, $webform_submission); // Negate result for 'disabled' state. @@ -555,7 +618,24 @@ public function deleteElement($key, array $element) {} * * This helper method looks looks for the handler default configuration keys * within a form and set a matching element's #parents property to - * ['settings', '{element_kye}'] + * ['settings', '{element_key}'] + * + * @param array $elements + * An array of form elements. + * + * @return array + * Form element with #parents set. + */ + protected function setSettingsParents(array &$elements) { + return $this->setSettingsParentsRecursively($elements); + } + + /** + * Set configuration settings parents. + * + * This helper method looks looks for the handler default configuration keys + * within a form and set a matching element's #parents property to + * ['settings', '{element_key}'] * * @param array $elements * An array of form elements. @@ -567,7 +647,7 @@ protected function setSettingsParentsRecursively(array &$elements) { $default_configuration = $this->defaultConfiguration(); foreach ($elements as $element_key => &$element) { // Only a form element can have #parents. - if (Element::property($element_key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $element_key)) { continue; } @@ -577,7 +657,14 @@ protected function setSettingsParentsRecursively(array &$elements) { continue; } - if (array_key_exists($element_key, $default_configuration) && isset($element['#type'])) { + // Only set #parents when #element has… + // - Default configuration. + // - Is an input. + // - #default_value or #value (aka input). + // - Not a container with children. + if (array_key_exists($element_key, $default_configuration) + && isset($element['#type']) + && !WebformElementHelper::hasChildren($element)) { $element['#parents'] = ['settings', $element_key]; } else { @@ -587,18 +674,80 @@ protected function setSettingsParentsRecursively(array &$elements) { return $elements; } + /****************************************************************************/ + // Token methods. + /****************************************************************************/ + + /** + * Replace tokens in text with no render context. + * + * @param string|array $text + * A string of text that may contain tokens. + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * A Webform or Webform submission entity. + * @param array $data + * (optional) An array of keyed objects. + * @param array $options + * (optional) A keyed array of settings and flags to control the token + * replacement process. Supported options are: + * - langcode: A language code to be used when generating locale-sensitive + * tokens. + * - callback: A callback function that will be used to post-process the + * array of token replacements after they are generated. + * - clear: A boolean flag indicating that tokens should be removed from the + * final text if no replacement value can be generated. + * + * @return string|array + * Text or array with tokens replaced. + */ + protected function replaceTokens($text, EntityInterface $entity = NULL, array $data = [], array $options = []) { + return $this->tokenManager->replaceNoRenderContext($text, $entity, $data, $options); + } + + /** + * Build token tree element. + * + * @param array $token_types + * (optional) An array containing token types that should be shown in the tree. + * @param string $description + * (optional) Description to appear after the token tree link. + * + * @return array + * A render array containing a token tree link wrapped in a div. + */ + protected function buildTokenTreeElement(array $token_types = [], $description = NULL) { + return $this->tokenManager->buildTreeElement($token_types, $description); + } + + /** + * Validate form that should have tokens in it. + * + * @param array $form + * A form. + * @param array $token_types + * An array containing token types that should be validated. + * + * @see token_element_validate() + */ + protected function elementTokenValidate(array &$form, array $token_types = ['webform', 'webform_submission', 'webform_handler']) { + return $this->tokenManager->elementValidate($form, $token_types); + } + /****************************************************************************/ // Logging methods. /****************************************************************************/ /** - * Get webform logger. + * Get webform or webform_submission logger. + * + * @param string $channel + * The logger channel. Defaults to 'webform'. * * @return \Drupal\Core\Logger\LoggerChannelInterface * Webform logger */ - protected function getLogger() { - return $this->loggerFactory->get('webform'); + protected function getLogger($channel = 'webform') { + return $this->loggerFactory->get($channel); } /** @@ -612,6 +761,18 @@ protected function getLogger() { * The message to be logged. * @param array $data * The data to be saved with log record. + * + * @deprecated Instead call the 'webform_submission' logger channel directly. + * + * $message = 'Some message with an %argument.' + * $context = [ + * '%argument' => 'Some value' + * 'link' => $webform_submission->toLink($this->t('Edit'), 'edit-form')->toString(), + * 'webform_submission' => $webform_submission, + * 'handler_id' => NULL, + * 'data' => [], + * ]; + * \Drupal::logger('webform_submission')->notice($message, $context); */ protected function log(WebformSubmissionInterface $webform_submission, $operation, $message = '', array $data = []) { if ($webform_submission->getWebform()->hasSubmissionLog()) { @@ -624,4 +785,38 @@ protected function log(WebformSubmissionInterface $webform_submission, $operatio } } + /****************************************************************************/ + // TEMP: Messenger methods to be remove once Drupal 8.6.x+ is supported version. + /****************************************************************************/ + + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * Sets the messenger. + * + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + */ + public function setMessenger(MessengerInterface $messenger) { + $this->messenger = $messenger; + } + + /** + * Gets the messenger. + * + * @return \Drupal\Core\Messenger\MessengerInterface + * The messenger. + */ + public function messenger() { + if (!isset($this->messenger)) { + $this->messenger = \Drupal::messenger(); + } + return $this->messenger; + } + } diff --git a/web/modules/webform/src/Plugin/WebformHandlerInterface.php b/web/modules/webform/src/Plugin/WebformHandlerInterface.php index cb37038b67..bfe6885419 100644 --- a/web/modules/webform/src/Plugin/WebformHandlerInterface.php +++ b/web/modules/webform/src/Plugin/WebformHandlerInterface.php @@ -91,6 +91,14 @@ public function cardinality(); */ public function supportsConditions(); + /** + * Determine if webform handler supports tokens. + * + * @return bool + * TRUE if the webform handler supports tokens. + */ + public function supportsTokens(); + /** * Returns the unique ID representing the webform handler. * @@ -181,6 +189,20 @@ public function getConditions(); */ public function setConditions(array $conditions); + /** + * Enables the webform handler. + * + * @return $this + */ + public function enable(); + + /** + * Disables the webform handler. + * + * @return $this + */ + public function disable(); + /** * Checks if the handler is excluded via webform.settings. * @@ -224,10 +246,10 @@ public function isSubmissionOptional(); public function isSubmissionRequired(); /** - * Initialize webform handler. + * Set the webform that this is handler is attached to. * * @param \Drupal\webform\WebformInterface $webform - * A webform object. + * A webform. * * @return $this * This webform handler. @@ -242,13 +264,32 @@ public function setWebform(WebformInterface $webform); */ public function getWebform(); + /** + * Set the webform submission that this handler is handling. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return $this + * This webform handler. + */ + public function setWebformSubmission(WebformSubmissionInterface $webform_submission = NULL); + + /** + * Get the webform submission that this handler is handling. + * + * @return \Drupal\webform\WebformSubmissionInterface + * A webform submission. + */ + public function getWebformSubmission(); + /** * Check handler conditions against a webform submission. * * Note: Conditions are only applied to callbacks that require a * webform submissions. * - * Conditions are ignored by... + * Conditions are ignored by… * - \Drupal\webform\Plugin\WebformHandlerInterface::alterElements * - \Drupal\webform\Plugin\WebformHandlerInterface::preCreate * diff --git a/web/modules/webform/src/Plugin/WebformSourceEntity/QueryStringWebformSourceEntity.php b/web/modules/webform/src/Plugin/WebformSourceEntity/QueryStringWebformSourceEntity.php index 050584058b..633e471e60 100644 --- a/web/modules/webform/src/Plugin/WebformSourceEntity/QueryStringWebformSourceEntity.php +++ b/web/modules/webform/src/Plugin/WebformSourceEntity/QueryStringWebformSourceEntity.php @@ -59,22 +59,6 @@ class QueryStringWebformSourceEntity extends PluginBase implements WebformSource */ protected $webformEntityReferenceManager; - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_type.manager'), - $container->get('current_route_match'), - $container->get('request_stack'), - $container->get('language_manager'), - $container->get('webform.entity_reference_manager') - ); - } - /** * QueryStringWebformSourceEntity constructor. * @@ -105,6 +89,22 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition $this->webformEntityReferenceManager = $webform_entity_reference_manager; } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + $container->get('current_route_match'), + $container->get('request_stack'), + $container->get('language_manager'), + $container->get('webform.entity_reference_manager') + ); + } + /** * {@inheritdoc} */ @@ -134,7 +134,8 @@ public function getSourceEntity(array $ignored_types) { return NULL; } - if (is_subclass_of($source_entity, TranslatableInterface::class) && $source_entity->hasTranslation($this->languageManager->getCurrentLanguage()->getId())) { + // Get translated source entity. + if ($source_entity instanceof TranslatableInterface && $source_entity->hasTranslation($this->languageManager->getCurrentLanguage()->getId())) { $source_entity = $source_entity->getTranslation($this->languageManager->getCurrentLanguage()->getId()); } @@ -146,16 +147,18 @@ public function getSourceEntity(array $ignored_types) { // Check that the webform is referenced by the source entity. if (!$webform->getSetting('form_prepopulate_source_entity')) { // Get source entity's webform field. - $webform_field_name = $this->webformEntityReferenceManager->getFieldName($source_entity); - if (!$webform_field_name) { - return NULL; + $webform_field_names = $this->webformEntityReferenceManager->getFieldNames($source_entity); + foreach ($webform_field_names as $webform_field_name) { + // Check that source entity's reference webform is the + // current webform. + foreach ($source_entity->$webform_field_name as $item) { + if ($item->target_id === $webform->id()) { + return $source_entity; + } + } } - // Check that source entity's reference webform is the current YAML - // webform. - if ($source_entity->$webform_field_name->target_id != $webform->id()) { - return NULL; - } + return NULL; } return $source_entity; diff --git a/web/modules/webform/src/Plugin/WebformSourceEntity/RouteParametersWebformSourceEntity.php b/web/modules/webform/src/Plugin/WebformSourceEntity/RouteParametersWebformSourceEntity.php index 18dcdcf8ef..fba4d62642 100644 --- a/web/modules/webform/src/Plugin/WebformSourceEntity/RouteParametersWebformSourceEntity.php +++ b/web/modules/webform/src/Plugin/WebformSourceEntity/RouteParametersWebformSourceEntity.php @@ -27,18 +27,6 @@ class RouteParametersWebformSourceEntity extends PluginBase implements WebformSo */ protected $routeMatch; - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('current_route_match') - ); - } - /** * RouteParametersWebformSourceEntity constructor. * @@ -57,10 +45,28 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition $this->routeMatch = $route_match; } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('current_route_match') + ); + } + /** * {@inheritdoc} */ public function getSourceEntity(array $ignored_types) { + // Use current account when viewing a user's submissions. + // @see \Drupal\webform\WebformSubmissionListBuilder + if ($this->routeMatch->getRouteName() === 'entity.webform_submission.user') { + return NULL; + } + // Get the most specific source entity available in the current route's // parameters. $parameters = $this->routeMatch->getParameters()->all(); diff --git a/web/modules/webform/src/Plugin/WebformSourceEntityInterface.php b/web/modules/webform/src/Plugin/WebformSourceEntityInterface.php index 99a8860982..536d8b6d1f 100644 --- a/web/modules/webform/src/Plugin/WebformSourceEntityInterface.php +++ b/web/modules/webform/src/Plugin/WebformSourceEntityInterface.php @@ -13,10 +13,10 @@ interface WebformSourceEntityInterface extends PluginInspectionInterface { * Detect and return a source entity from current context. * * @param string[] $ignored_types - * Entity types that may not be source entity. + * Entity types that may not be used as a source entity. * * @return \Drupal\Core\Entity\EntityInterface|null - * Source entity or NULL should no source entity be found. + * Source entity or NULL when no source entity is found. */ public function getSourceEntity(array $ignored_types); diff --git a/web/modules/webform/src/Plugin/WebformSourceEntityManager.php b/web/modules/webform/src/Plugin/WebformSourceEntityManager.php index 4f7046b54d..0dadf72aec 100644 --- a/web/modules/webform/src/Plugin/WebformSourceEntityManager.php +++ b/web/modules/webform/src/Plugin/WebformSourceEntityManager.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\SortArray; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; @@ -35,6 +36,47 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac $this->setCacheBackend($cache_backend, 'webform_source_entity_info_plugins'); } + /** + * {@inheritdoc} + */ + protected function alterDefinitions(&$definitions) { + // Unset elements that are missing target element or dependencies. + foreach ($definitions as $element_key => $element_definition) { + // Check element's (module) dependencies exist. + foreach ($element_definition['dependencies'] as $dependency) { + if (!$this->moduleHandler->moduleExists($dependency)) { + unset($definitions[$element_key]); + continue; + } + } + } + + // Additionally sort by weight so we always have them sorted in proper + // order. + uasort($definitions, [SortArray::class, 'sortByWeightElement']); + + parent::alterDefinitions($definitions); + } + + /** + * Get the main source entity. Applies to only paragraphs. + * + * @param \Drupal\Core\Entity\EntityInterface $source_entity + * A source entity. + * + * @return \Drupal\Core\Entity\EntityInterface + * The main source entity. + * + * @see \Drupal\webform\Plugin\Field\FieldFormatter\WebformEntityReferenceEntityFormatter::viewElements + * @see \Drupal\webform\Plugin\WebformSourceEntity\QueryStringWebformSourceEntity::getSourceEntity + */ + public static function getMainSourceEntity(EntityInterface $source_entity) { + while ($source_entity && $source_entity->getEntityTypeId() === 'paragraph') { + $source_entity = $source_entity->getParentEntity(); + } + return $source_entity; + } + /** * {@inheritdoc} */ @@ -55,15 +97,4 @@ public function getSourceEntity($ignored_types = []) { return NULL; } - /** - * {@inheritdoc} - */ - protected function alterDefinitions(&$definitions) { - parent::alterDefinitions($definitions); - - // Additionally sort by weight so we always have them sorted in proper - // order. - uasort($definitions, [SortArray::class, 'sortByWeightElement']); - } - } diff --git a/web/modules/webform/src/Plugin/WebformSourceEntityManagerInterface.php b/web/modules/webform/src/Plugin/WebformSourceEntityManagerInterface.php index 5487804baa..8e88a7da37 100644 --- a/web/modules/webform/src/Plugin/WebformSourceEntityManagerInterface.php +++ b/web/modules/webform/src/Plugin/WebformSourceEntityManagerInterface.php @@ -13,10 +13,10 @@ interface WebformSourceEntityManagerInterface extends PluginManagerInterface { * Detect and return a source entity from current context. * * @param string|string[] $ignored_types - * Entity types that may not be source entity. + * Entity types that may not be used as a source entity. * * @return \Drupal\Core\Entity\EntityInterface|null - * Source entity or NULL should no source entity be found + * Source entity or NULL when no source entity is found. */ public function getSourceEntity($ignored_types = []); diff --git a/web/modules/webform/src/Routing/WebformRouteSubscriber.php b/web/modules/webform/src/Routing/WebformRouteSubscriber.php index 0171020fa0..1259c61004 100644 --- a/web/modules/webform/src/Routing/WebformRouteSubscriber.php +++ b/web/modules/webform/src/Routing/WebformRouteSubscriber.php @@ -14,7 +14,7 @@ class WebformRouteSubscriber extends RouteSubscriberBase { * {@inheritdoc} */ protected function alterRoutes(RouteCollection $collection) { - // Reove 'Contribute' route if explicitly disabled or the Contribute module + // Remove 'Contribute' route if explicitly disabled or the Contribute module // is installed. if (\Drupal::config('webform.settings')->get('ui.contribute_disabled') || \Drupal::moduleHandler()->moduleExists('contribute')) { $collection->remove('webform.contribute'); diff --git a/web/modules/webform/src/Tests/Access/WebformAccessEntityJsonApiTest.php b/web/modules/webform/src/Tests/Access/WebformAccessEntityJsonApiTest.php new file mode 100644 index 0000000000..dc8790e685 --- /dev/null +++ b/web/modules/webform/src/Tests/Access/WebformAccessEntityJsonApiTest.php @@ -0,0 +1,81 @@ +<?php + +namespace Drupal\webform\Tests\Access; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform entity JSON API access. + * + * @group Webform + */ +class WebformAccessEntityJsonApiTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'jsonapi']; + + /** + * Tests webform entity REST acces. + */ + public function testRestAccess() { + $webform = Webform::load('contact'); + $uuid = $webform->uuid(); + + $account = $this->drupalCreateUser(); + + $configuration_account = $this->drupalCreateUser([ + 'access any webform configuration', + ]); + + /**************************************************************************/ + + // Check anonymous access denied to webform. + $this->drupalGet("jsonapi/webform/webform/$uuid"); + $this->assertRaw('"title":"Forbidden","status":"403","detail":"The current user is not allowed to GET the selected resource. Access to webform configuration is required."'); + + // Login authenticated user. + $this->drupalLogin($account); + + // Check authenticated access allowed to webform. + $this->drupalGet('/webform/contact'); + $this->assertFieldByName('subject'); + + // Check authenticated access denied to webform via _format=hal_json. + $this->drupalGet("jsonapi/webform/webform/$uuid"); + $this->assertRaw('"title":"Forbidden","status":"403","detail":"The current user is not allowed to GET the selected resource. Access to webform configuration is required."'); + + // Login rest (permission) user. + $this->drupalLogin($configuration_account); + + // Check rest access allowed to webform. + $this->drupalGet("jsonapi/webform/webform/$uuid"); + $this->assertNoRaw('"title":"Forbidden","status":"403","detail":"The current user is not allowed to GET the selected resource. Access to webform configuration is required."'); + $this->assertRaw('"title":"Contact",'); + + // Allow anonymous role to access webform configuration. + $access_rules = $webform->getAccessRules(); + $access_rules['configuration']['roles'] = ['anonymous', 'authenticated']; + $webform->setAccessRules($access_rules); + $webform->save(); + + // Login out and switch to anonymous user. + $this->drupalLogout(); + + // Check anonymous access allowed to webform. + $this->drupalGet("jsonapi/webform/webform/$uuid"); + $this->assertNoRaw('"title":"Forbidden","status":"403","detail":"The current user is not allowed to GET the selected resource. Access to webform configuration is required."'); + + // Login authenticated user. + $this->drupalLogin($account); + + // Check authenticated access allowed to webform. + $this->drupalGet("jsonapi/webform/webform/$uuid"); + $this->assertNoRaw('"title":"Forbidden","status":"403","detail":"The current user is not allowed to GET the selected resource. Access to webform configuration is required."'); + } + +} diff --git a/web/modules/webform/src/Tests/Access/WebformAccessEntityPermissionsTest.php b/web/modules/webform/src/Tests/Access/WebformAccessEntityPermissionsTest.php new file mode 100644 index 0000000000..f322fee92c --- /dev/null +++ b/web/modules/webform/src/Tests/Access/WebformAccessEntityPermissionsTest.php @@ -0,0 +1,105 @@ +<?php + +namespace Drupal\webform\Tests\Access; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform entity permissions. + * + * @group Webform + */ +class WebformAccessEntityPermissionsTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['node', 'webform', 'webform_test_submissions']; + + /** + * Tests webform entity access controls. + */ + public function testAccessControlHandler() { + $own_account = $this->drupalCreateUser([ + 'access webform overview', + 'create webform', + 'edit own webform', + 'delete own webform', + ]); + $any_account = $this->drupalCreateUser([ + 'access webform overview', + 'create webform', + 'edit any webform', + 'delete any webform', + ]); + + /**************************************************************************/ + + // Login as user who can access own webform. + $this->drupalLogin($own_account); + + // Check create own webform. + $this->drupalPostForm('admin/structure/webform/add', ['id' => 'test_own', 'title' => 'test_own'], t('Save')); + + // Check webform submission overview contains own webform. + $this->drupalGet('/admin/structure/webform'); + $this->assertRaw('test_own'); + + // Add test element to own webform. + $this->drupalPostForm('/admin/structure/webform/manage/test_own', ['elements' => "test:\n '#markup': 'test'"], t('Save')); + + // Check duplicate own webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/duplicate'); + $this->assertResponse(200); + + // Check delete own webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/delete'); + $this->assertResponse(200); + + // Check access own webform submissions. + $this->drupalGet('/admin/structure/webform/manage/test_own/results/submissions'); + $this->assertResponse(200); + + // Login as user who can access any webform. + $this->drupalLogin($any_account); + + // Check duplicate any webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/duplicate'); + $this->assertResponse(200); + + // Check delete any webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/delete'); + $this->assertResponse(200); + + // Check access any webform submissions. + $this->drupalGet('/admin/structure/webform/manage/test_own/results/submissions'); + $this->assertResponse(200); + + // Change the owner of the webform to 'any' user. + $own_webform = Webform::load('test_own'); + $own_webform->setOwner($any_account)->save(); + + // Login as user who can access own webform. + $this->drupalLogin($own_account); + + // Check webform submission overview does not contains any webform. + $this->drupalGet('/admin/structure/webform'); + $this->assertNoRaw('test_own'); + + // Check duplicate denied any webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/duplicate'); + $this->assertResponse(403); + + // Check delete denied any webform. + $this->drupalGet('/admin/structure/webform/manage/test_own/delete'); + $this->assertResponse(403); + + // Check access denied any webform submissions. + $this->drupalGet('/admin/structure/webform/manage/test_own/results/submissions'); + $this->assertResponse(403); + } + +} diff --git a/web/modules/webform/src/Tests/Access/WebformAccessEntityRestTest.php b/web/modules/webform/src/Tests/Access/WebformAccessEntityRestTest.php new file mode 100644 index 0000000000..dc7104d4ca --- /dev/null +++ b/web/modules/webform/src/Tests/Access/WebformAccessEntityRestTest.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\webform\Tests\Access; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform entity REST access. + * + * @group Webform + */ +class WebformAccessEntityRestTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_test_rest']; + + /** + * Tests webform entity REST acces. + */ + public function testRestAccess() { + $webform = Webform::load('contact'); + + $account = $this->drupalCreateUser(); + + $configuration_account = $this->drupalCreateUser([ + 'access any webform configuration', + ]); + + /**************************************************************************/ + + // Check anonymous access denied to webform via _format=hal_json. + $this->drupalGet('/webform/contact', ['query' => ['_format' => 'hal_json']]); + $this->assertRaw('{"message":"Access to webform configuration is required."}'); + + // Login authenticated user. + $this->drupalLogin($account); + + // Check authenticated access allowed to webform via _format=html. + $this->drupalGet('/webform/contact'); + $this->assertFieldByName('subject'); + + // Check authenticated access denied to webform via _format=hal_json. + $this->drupalGet('/webform/contact', ['query' => ['_format' => 'hal_json']]); + $this->assertRaw('{"message":"Access to webform configuration is required."}'); + + // Login rest (permission) user. + $this->drupalLogin($configuration_account); + + // Check rest access allowed to webform via _format=hal_json. + $this->drupalGet('/webform/contact', ['query' => ['_format' => 'hal_json']]); + $this->assertNoRaw('{"message":"Access to webform configuration is required."}'); + $this->assertRaw('"id":"contact","title":"Contact"'); + + // Allow anonymous role to access webform configuration. + $access_rules = $webform->getAccessRules(); + $access_rules['configuration']['roles'] = ['anonymous', 'authenticated']; + $webform->setAccessRules($access_rules); + $webform->save(); + + // Login out and switch to anonymous user. + $this->drupalLogout(); + + // Check anonymous access allowed to webform via _format=hal_json. + $this->drupalGet('/webform/contact', ['query' => ['_format' => 'hal_json']]); + $this->assertNoRaw('{"message":"Access to webform configuration is required."}'); + + // Login authenticated user. + $this->drupalLogin($account); + + // Check authenticated access allowed to webform via _format=hal_json. + $this->drupalGet('/webform/contact', ['query' => ['_format' => 'hal_json']]); + $this->assertNoRaw('{"message":"Access to webform configuration is required."}'); + } + +} diff --git a/web/modules/webform/src/Tests/WebformEntityAccessTest.php b/web/modules/webform/src/Tests/Access/WebformAccessEntityRulesTest.php similarity index 66% rename from web/modules/webform/src/Tests/WebformEntityAccessTest.php rename to web/modules/webform/src/Tests/Access/WebformAccessEntityRulesTest.php index c74bb1bbc2..77cbbba553 100644 --- a/web/modules/webform/src/Tests/WebformEntityAccessTest.php +++ b/web/modules/webform/src/Tests/Access/WebformAccessEntityRulesTest.php @@ -1,16 +1,17 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Access; use Drupal\Component\Render\FormattableMarkup; use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; /** - * Tests for webform access controls. + * Tests for webform entity access rules. * * @group Webform */ -class WebformEntityAccessTest extends WebformTestBase { +class WebformAccessEntityRulesTest extends WebformTestBase { /** * Modules to enable. @@ -27,142 +28,106 @@ class WebformEntityAccessTest extends WebformTestBase { protected static $testWebforms = ['test_submissions']; /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - - /** - * Tests webform access rules. - */ - public function testAccessControlHandler() { - // Login as user who can access own webform. - $this->drupalLogin($this->ownWebformUser); - - // Check create own webform. - $this->drupalPostForm('admin/structure/webform/add', ['id' => 'test_own', 'title' => 'test_own'], t('Save')); - - // Add test element to own webform. - $this->drupalPostForm('/admin/structure/webform/manage/test_own', ['elements' => "test:\n '#markup': 'test'"], t('Save')); - - // Check duplicate own webform. - $this->drupalGet('admin/structure/webform/manage/test_own/duplicate'); - $this->assertResponse(200); - - // Check delete own webform. - $this->drupalGet('admin/structure/webform/manage/test_own/delete'); - $this->assertResponse(200); - - // Check access own webform submissions. - $this->drupalGet('admin/structure/webform/manage/test_own/results/submissions'); - $this->assertResponse(200); - - // Login as user who can access any webform. - $this->drupalLogin($this->anyWebformUser); - - // Check duplicate any webform. - $this->drupalGet('admin/structure/webform/manage/test_own/duplicate'); - $this->assertResponse(200); - - // Check delete any webform. - $this->drupalGet('admin/structure/webform/manage/test_own/delete'); - $this->assertResponse(200); - - // Check access any webform submissions. - $this->drupalGet('admin/structure/webform/manage/test_own/results/submissions'); - $this->assertResponse(200); - - // Change the owner of the webform to 'any' user. - $own_webform = Webform::load('test_own'); - $own_webform->setOwner($this->anyWebformUser)->save(); - - // Login as user who can access own webform. - $this->drupalLogin($this->ownWebformUser); - - // Check duplicate denied any webform. - $this->drupalGet('admin/structure/webform/manage/test_own/duplicate'); - $this->assertResponse(403); - - // Check delete denied any webform. - $this->drupalGet('admin/structure/webform/manage/test_own/delete'); - $this->assertResponse(403); - - // Check access denied any webform submissions. - $this->drupalGet('admin/structure/webform/manage/test_own/results/submissions'); - $this->assertResponse(403); - } - - /** - * Tests webform access rules. + * Tests webform entity access rules. */ public function testAccessRules() { global $base_path; + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + $default_access_rules = $access_rules_manager->getDefaultAccessRules(); + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_submissions'); /** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */ $submissions = array_values(\Drupal::entityTypeManager()->getStorage('webform_submission')->loadByProperties(['webform_id' => 'test_submissions'])); - $account = $this->drupalCreateUser(['access content']); + $account = $this->drupalCreateUser(['access content', 'edit webform source']); $webform_id = $webform->id(); $sid = $submissions[0]->id(); $uid = $account->id(); - $rid = $account->getRoles()[1]; + $rid = $account->getRoles(TRUE)[0]; + + /**************************************************************************/ + // Test. + /**************************************************************************/ + + $this->drupalLogin($account); - // Check 'test' access rule. + // Check that user cannot access test form. $this->drupalGet("webform/$webform_id/test"); $this->assertResponse(403, 'Webform setting access denied for test rule.'); + + // Assign user to 'test' access rule. $access_rules = [ 'test' => [ 'roles' => [], 'users' => [$uid], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); - $this->drupalLogin($account); + + // Check that user can access test form. $this->drupalGet("webform/$webform_id/test"); $this->assertResponse(200, 'Webform setting access for test rule.'); - $this->drupalLogout($account); - // Check 'administer' access rule. + /**************************************************************************/ + // Administer. + /**************************************************************************/ + + // Check that user cannot access form settings. + $this->drupalGet("admin/structure/webform/manage/$webform_id/settings"); + $this->assertResponse(403, 'Webform setting access denied for administer rule.'); + $this->drupalGet("admin/structure/webform/manage/$webform_id/results/submissions"); + $this->assertResponse(403, 'Webform submissions access denied for administer rule.'); + + // Assign user to 'administer' access rule. $access_rules = [ 'administer' => [ 'roles' => [], 'users' => [$uid], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); - $this->drupalLogin($account); + + // Check that user cannot access settings. $this->drupalGet("admin/structure/webform/manage/$webform_id/settings"); - $this->assertResponse(200, 'Webform setting access for administer rule.'); + $this->assertResponse(200, 'Webform setting access allowed for administer rule.'); $this->drupalGet("admin/structure/webform/manage/$webform_id/results/submissions"); - $this->assertResponse(200, 'Webform submissions access for administer rule.'); - $this->drupalLogout($account); + $this->assertResponse(200, 'Webform submissions access allowed for administer rule.'); + + /**************************************************************************/ + // Create. + /**************************************************************************/ + + $this->drupalLogout(); // Check create authenticated/anonymous access. - $webform->setAccessRules(Webform::getDefaultAccessRules())->save(); - $this->drupalGet('webform/' . $webform->id()); - $this->assertResponse(200, 'Webform create submission access for anonymous/authenticated user.'); + $webform->setAccessRules($default_access_rules)->save(); + $this->drupalGet('/webform/' . $webform->id()); + $this->assertResponse(200, 'Webform create submission access allowed for anonymous/authenticated user.'); + // Revoke create from anonymous and authenticated roles. $access_rules = [ 'create' => [ 'roles' => [], 'users' => [], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); - // Check no access. - $this->drupalGet('webform/' . $webform->id()); + // Check create access denied. + $this->drupalGet('/webform/' . $webform->id()); $this->assertResponse(403, 'Webform returns access denied'); + + /**************************************************************************/ + // Any. + /**************************************************************************/ + $any_tests = [ 'webform/{webform}' => 'create', 'admin/structure/webform/manage/{webform}/results/submissions' => 'view_any', @@ -184,6 +149,7 @@ public function testAccessRules() { $this->assertResponse(403, 'Webform returns access denied'); } + // Login. $this->drupalLogin($account); // Check that all the test paths are access denied for authenticated. @@ -195,7 +161,7 @@ public function testAccessRules() { $this->assertResponse(403, 'Webform returns access denied'); } - // Check access rules by role, user id, and permission. + // Check any access rules by role, user id, and permission. foreach ($any_tests as $path => $permission) { $path = str_replace('{webform}', $webform_id, $path); $path = str_replace('{webform_submission}', $sid, $path); @@ -207,7 +173,7 @@ public function testAccessRules() { 'users' => [], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); $this->drupalGet($path); $this->assertResponse(200, 'Webform allows access via role access rules'); @@ -219,24 +185,28 @@ public function testAccessRules() { 'users' => [$uid], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); $this->drupalGet($path); $this->assertResponse(200, 'Webform allows access via user access rules'); // Check access rule via 'access content'. $access_rules = [ - $permission => [ - 'roles' => [], - 'users' => [], - 'permissions' => ['access content'], - ], - ] + Webform::getDefaultAccessRules(); + $permission => [ + 'roles' => [], + 'users' => [], + 'permissions' => ['access content'], + ], + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); $this->drupalGet($path); $this->assertResponse(200, "Webform allows access via permission access rules"); } + /**************************************************************************/ + // Own. + /**************************************************************************/ + // Check own / user specific access rules. $access_rules = [ 'view_own' => [ @@ -254,7 +224,7 @@ public function testAccessRules() { 'users' => [], 'permissions' => [], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; $webform->setAccessRules($access_rules)->save(); // Must delete all existing anonymous submission to prevent them from @@ -267,30 +237,24 @@ public function testAccessRules() { $this->drupalLogin($account); // Check no view previous submission message. - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet('/webform/' . $webform->id()); $this->assertNoRaw('You have already submitted this webform.'); $this->assertNoRaw('View your previous submission'); $sid = $this->postSubmission($webform); // Check view previous submission message. - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet('/webform/' . $webform->id()); $this->assertRaw('You have already submitted this webform.'); $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions/{$sid}\">View your previous submission</a>."); $sid = $this->postSubmission($webform); // Check view previous submissions message. - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet('/webform/' . $webform->id()); $this->assertRaw('You have already submitted this webform.'); $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions\">View your previous submissions</a>"); - // Check disabled previous submissions messages. - $webform->setSetting('form_previous_submissions', FALSE); - $webform->save(); - $this->drupalGet('webform/' . $webform->id()); - $this->assertNoRaw('You have already submitted this webform.'); - // Check the new submission's view, update, and delete access for the user. $test_own = [ 'admin/structure/webform/manage/{webform}/results/submissions' => 403, @@ -301,6 +265,10 @@ public function testAccessRules() { 'admin/structure/webform/manage/{webform}/submission/{webform_submission}/yaml' => 403, 'admin/structure/webform/manage/{webform}/submission/{webform_submission}/edit' => 200, 'admin/structure/webform/manage/{webform}/submission/{webform_submission}/delete' => 200, + 'webform/{webform}/submissions/{webform_submission}' => 200, + 'webform/{webform}/submissions/{webform_submission}/edit' => 200, + 'webform/{webform}/submissions/{webform_submission}/duplicate' => 403, + 'webform/{webform}/submissions/{webform_submission}/delete' => 200, ]; foreach ($test_own as $path => $status_code) { $path = str_replace('{webform}', $webform_id, $path); @@ -309,6 +277,20 @@ public function testAccessRules() { $this->drupalGet($path); $this->assertResponse($status_code, new FormattableMarkup('Webform @status_code access via own access rules.', ['@status_code' => ($status_code == 403 ? 'denies' : 'allows')])); } + + // Enable submission user duplicate. + $webform->setSetting('submission_user_duplicate', TRUE); + $webform->save(); + + // Check enable user submission duplicate. + $this->drupalGet("webform/$webform_id/submissions/$sid/duplicate"); + $this->assertResponse(200); + + // Check disabled previous submissions messages. + $webform->setSetting('form_previous_submissions', FALSE); + $webform->save(); + $this->drupalGet('/webform/' . $webform->id()); + $this->assertNoRaw('You have already submitted this webform.'); } } diff --git a/web/modules/webform/src/Tests/Access/WebformAccessSubmissionPermissionsTest.php b/web/modules/webform/src/Tests/Access/WebformAccessSubmissionPermissionsTest.php new file mode 100644 index 0000000000..3020bc1c6a --- /dev/null +++ b/web/modules/webform/src/Tests/Access/WebformAccessSubmissionPermissionsTest.php @@ -0,0 +1,239 @@ +<?php + +namespace Drupal\webform\Tests\Access; + +use Drupal\user\Entity\Role; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform submission permissions. + * + * @group Webform + */ +class WebformAccessSubmissionPermissionsTest extends WebformTestBase { + + /** + * Test webform submission access permissions. + */ + public function testPermissions() { + global $base_path; + + $admin_webform_account = $this->drupalCreateUser([ + 'administer webform', + 'create webform', + ]); + + $admin_submission_account = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + $own_webform_account = $this->drupalCreateUser([ + 'edit own webform', + ]); + + $any_submission_account = $this->drupalCreateUser([ + 'view any webform submission', + 'edit any webform submission', + 'delete any webform submission', + ]); + + $own_submission_account = $this->drupalCreateUser([ + 'view own webform submission', + 'edit own webform submission', + 'delete own webform submission', + 'access webform submission user', + ]); + + $webform_id = 'contact'; + $webform = Webform::load('contact'); + + /**************************************************************************/ + // Create submission permissions (anonymous). + /**************************************************************************/ + + $edit = ['subject' => '{subject}', 'message' => '{message}']; + $sid_1 = $this->postSubmission($webform, $edit); + + // Check cannot view own submissions. + $uid = $own_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(403); + + // Check cannot view own previous submission message. + $this->drupalGet('/webform/' . $webform->id()); + $this->assertNoRaw('You have already submitted this webform.'); + + // Check cannot 'view own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}"); + $this->assertResponse(403); + + /**************************************************************************/ + // Own submission permissions (authenticated). + /**************************************************************************/ + + $this->drupalLogin($own_submission_account); + + $edit = ['subject' => '{subject}', 'message' => '{message}']; + $sid_2 = $this->postSubmission($webform, $edit); + + // Check 'access webform submission user' permission. + $uid = $own_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(200); + + // Check view own previous submission message. + $this->drupalGet('/webform/' . $webform->id()); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions/{$sid_2}\">View your previous submission</a>."); + + // Check 'view own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_2}"); + $this->assertResponse(200); + + // Check 'edit own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_2}/edit"); + $this->assertResponse(200); + + // Check 'delete own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_2}/delete"); + $this->assertResponse(200); + + $sid_3 = $this->postSubmission($webform, $edit); + + // Check view own previous submissions message. + $this->drupalGet('/webform/' . $webform->id()); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions\">View your previous submissions</a>"); + + // Check view own previous submissions. + $this->drupalGet("webform/{$webform_id}/submissions"); + $this->assertResponse(200); + $this->assertNoLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); + $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_2}"); + $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_3}"); + + // Check webform submission allowed. + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/submission/{$sid_2}"); + $this->assertResponse(200); + + // Check all results access denied. + $this->drupalGet('/admin/structure/webform/submissions/manage'); + $this->assertResponse(403); + + // Check webform results access denied. + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); + $this->assertResponse(403); + + /**************************************************************************/ + // Any submission permissions. + /**************************************************************************/ + + // Login as any user. + $this->drupalLogin($any_submission_account); + + // Check 'access webform submission user' permission. + $uid = $any_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(200); + + // Check 'access webform submission user' permission denied. + $uid = $own_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(200); + + // Check webform results access allowed. + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); + $this->assertResponse(200); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_2}"); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_3}"); + + // Check webform submission access allowed. + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/submission/{$sid_2}"); + $this->assertResponse(200); + + // Check all results access allowed. + $this->drupalGet('/admin/structure/webform/submissions/manage'); + $this->assertResponse(200); + + /**************************************************************************/ + // Own submission permissions (anonymous). + /**************************************************************************/ + + /** @var \Drupal\user\RoleInterface $anonymous_role */ + $anonymous_role = Role::load('anonymous'); + $anonymous_role->grantPermission('view own webform submission') + ->grantPermission('edit own webform submission') + ->grantPermission('delete own webform submission') + ->save(); + $this->drupalLogout(); + + $edit = ['name' => '{name}', 'email' => 'example@example.com', 'subject' => '{subject}', 'message' => '{message}']; + $sid_4 = $this->postSubmission($webform, $edit); + + // Check view own previous submission message. + $this->drupalGet('/webform/' . $webform->id()); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions/{$sid_4}\">View your previous submission</a>."); + + // Check 'view own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_4}"); + $this->assertResponse(200); + + // Check 'edit own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_4}/edit"); + $this->assertResponse(200); + + // Check 'delete own submission' permission. + $this->drupalGet("webform/{$webform_id}/submissions/{$sid_4}/delete"); + $this->assertResponse(200); + + $sid_5 = $this->postSubmission($webform, $edit); + + // Check view own previous submissions message. + $this->drupalGet('/webform/' . $webform->id()); + $this->assertRaw('You have already submitted this webform.'); + $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions\">View your previous submissions</a>"); + + // Check view own previous submissions. + $this->drupalGet("webform/{$webform_id}/submissions"); + $this->assertResponse(200); + $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_4}"); + $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_5}"); + + /**************************************************************************/ + // Administer webform or webform submission permission. + /**************************************************************************/ + + $this->drupalLogin($admin_webform_account); + $uid = $own_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(200); + + $this->drupalLogin($admin_submission_account); + $uid = $own_submission_account->id(); + $this->drupalGet("user/$uid/submissions"); + $this->assertResponse(200); + + // Check user can't see all submissions unless they are the owner. + $this->drupalLogin($own_webform_account); + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); + $this->assertResponse(403); + + // Check user can see all submissions when they are the webform owner. + $webform->setOwner($own_webform_account)->save(); + $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); + $this->assertResponse(200); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_2}"); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_3}"); + $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_4}"); + + // Check user can the submissions when they are the webform owner. + $this->drupalGet("admin/structure/webform/manage/{$webform_id}/submission/{$sid_4}"); + $this->assertResponse(200); + + } + +} diff --git a/web/modules/webform/src/Tests/Block/WebformBlockContextTest.php b/web/modules/webform/src/Tests/Block/WebformBlockContextTest.php index 1cf2ab89f1..9f2fa16149 100644 --- a/web/modules/webform/src/Tests/Block/WebformBlockContextTest.php +++ b/web/modules/webform/src/Tests/Block/WebformBlockContextTest.php @@ -32,7 +32,7 @@ public function setUp() { 'node' => '@node.node_route_context:node', ]; foreach ($contexts as $type => $context) { - $block = $this->placeBlock('webform_test_block_context_block', ['label' => '{' . $type . ' context}']); + $block = $this->drupalPlaceBlock('webform_test_block_context_block', ['label' => '{' . $type . ' context}']); $block->setVisibilityConfig('webform', [ 'id' => 'webform', 'webforms' => ['contact' => 'contact'], @@ -43,7 +43,7 @@ public function setUp() { ]); $block->save(); } - $block = $this->placeBlock('webform_test_block_context_block', ['label' => '{all contexts}']); + $block = $this->drupalPlaceBlock('webform_test_block_context_block', ['label' => '{all contexts}']); $block->setVisibilityConfig('webform', [ 'id' => 'webform', 'webforms' => ['contact' => 'contact'], @@ -61,7 +61,7 @@ public function testBlockContext() { $webform = Webform::load('contact'); // Check webform context. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('{all contexts}'); $this->assertRaw('{webform context}'); @@ -76,7 +76,7 @@ public function testBlockContext() { $node->webform->target_id = 'contact'; $node->webform->status = 1; $node->save(); - $this->drupalGet('node/' . $node->id()); + $this->drupalGet('/node/' . $node->id()); $this->assertRaw('{all contexts}'); $this->assertRaw('{node context}'); } diff --git a/web/modules/webform/src/Tests/Block/WebformBlockTest.php b/web/modules/webform/src/Tests/Block/WebformBlockTest.php index e644098ee1..1fb130c8ba 100644 --- a/web/modules/webform/src/Tests/Block/WebformBlockTest.php +++ b/web/modules/webform/src/Tests/Block/WebformBlockTest.php @@ -35,13 +35,13 @@ public function testBlock() { ]); // Check contact webform. - $this->drupalGet('<front>'); + $this->drupalGet('/<front>'); $this->assertRaw('webform-submission-contact-add-form'); // Check contact webform with default data. $block->getPlugin()->setConfigurationValue('default_data', "name: 'John Smith'"); $block->save(); - $this->drupalGet('<front>'); + $this->drupalGet('/<front>'); $this->assertRaw('webform-submission-contact-add-form'); $this->assertFieldByName('name', 'John Smith'); @@ -51,11 +51,20 @@ public function testBlock() { $this->drupalPostForm('<front>', [], t('Submit')); $this->assertRaw('This is a custom inline confirmation message.'); - // Check confirmation message webform. + // Check confirmation message webform displayed on front page. $block->getPlugin()->setConfigurationValue('webform_id', 'test_confirmation_message'); $block->save(); $this->drupalPostForm('<front>', [], t('Submit')); $this->assertRaw('This is a <b>custom</b> confirmation message.'); + $this->assertUrl('/user/login'); + + // Check confirmation message webform display on webform URL. + $block->getPlugin()->setConfigurationValue('redirect', TRUE); + $block->save(); + $this->drupalPostForm('<front>', [], t('Submit')); + $this->assertRaw('This is a <b>custom</b> confirmation message.'); + $this->assertUrl('webform/test_confirmation_message'); + } } diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositeCustomFileTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositeCustomFileTest.php new file mode 100644 index 0000000000..fa28b18c0c --- /dev/null +++ b/web/modules/webform/src/Tests/Composite/WebformCompositeCustomFileTest.php @@ -0,0 +1,62 @@ +<?php + +namespace Drupal\webform\Tests\Composite; + +use Drupal\file\Entity\File; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\Element\WebformElementManagedFileTestBase; + +/** + * Tests for custom composite element. + * + * @group Webform + */ +class WebformCompositeCustomFileTest extends WebformElementManagedFileTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_composite_custom_file']; + + /** + * Test custom composite element. + */ + public function testCustom() { + $webform = Webform::load('test_composite_custom_file'); + + $first_file = $this->files[0]; + + /**************************************************************************/ + + // Create submission with file. + $edit = [ + 'webform_custom_composite_file[items][0][_item_][textfield]' => '{textfield}', + 'files[webform_custom_composite_file_items_0__item__managed_file]' => \Drupal::service('file_system')->realpath($first_file->uri), + ]; + $sid = $this->postSubmission($webform, $edit); + $webform_submission = WebformSubmission::load($sid); + + $fid = $this->getLastFileId(); + $file = File::load($fid); + + // Check file permanent. + $this->assert($file->isPermanent(), 'Test file is permanent'); + + // Check file upload. + $element_data = $webform_submission->getElementData('webform_custom_composite_file'); + $this->assertEqual($element_data[0]['managed_file'], $fid, 'Test file was upload to the current submission'); + + // Check test file file usage. + $this->assertIdentical(['webform' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($file), 'The file has 1 usage.'); + + // Check test file uploaded file path. + $this->assertEqual($file->getFileUri(), 'private://webform/test_composite_custom_file/' . $sid . '/' . $first_file->filename); + + // Check that test file exists. + $this->assert(file_exists($file->getFileUri()), 'File exists'); + } + +} diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositeCustomTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositeCustomTest.php index 166c3d3dbc..3ebfd75657 100644 --- a/web/modules/webform/src/Tests/Composite/WebformCompositeCustomTest.php +++ b/web/modules/webform/src/Tests/Composite/WebformCompositeCustomTest.php @@ -25,12 +25,13 @@ public function testCustom() { /* Display */ - $this->drupalGet('webform/test_composite_custom'); + $this->drupalGet('/webform/test_composite_custom'); // Check basic custom composite. - $this->assertRaw('<label for="edit-webform-custom-composite-basic">webform_custom_composite_basic</label>'); - $this->assertRaw('<div id="webform_custom_composite_basic_table" class="webform-multiple-table webform-multiple-table-responsive">'); - $this->assertRaw('<th class="webform_custom_composite_basic-table--handle webform-multiple-table--handle"></th>'); + $this->assertRaw('<label>webform_custom_composite_basic</label>'); + $this->assertRaw('<div id="webform_custom_composite_basic_table">'); + $this->assertRaw('<div class="webform-multiple-table webform-multiple-table-responsive">'); + $this->assertRaw('<th class="webform_custom_composite_basic-table--handle webform-multiple-table--handle"><span class="visually-hidden">Re-order</span></th>'); $this->assertRaw('<th class="webform_custom_composite_basic-table--first_name webform-multiple-table--first_name">First name</th>'); $this->assertRaw('<th class="webform_custom_composite_basic-table--last_name webform-multiple-table--last_name">Last name</th>'); $this->assertRaw('<th class="webform_custom_composite_basic-table--weight webform-multiple-table--weight">Weight</th>'); diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositeFormatTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositeFormatTest.php index 059e2551d4..13e4bde78c 100644 --- a/web/modules/webform/src/Tests/Composite/WebformCompositeFormatTest.php +++ b/web/modules/webform/src/Tests/Composite/WebformCompositeFormatTest.php @@ -20,7 +20,7 @@ class WebformCompositeFormatTest extends WebformTestBase { * * @var array */ - public static $modules = ['filter', 'webform']; + public static $modules = ['filter', 'address', 'webform']; /** * Webforms to load. @@ -50,38 +50,26 @@ public function testFormat() { 'Likert (Value)' => '<div class="item-list"><ul><li><b>Please answer question 1?:</b> 1</li><li><b>How about now answering question 2?:</b> 1</li><li><b>Finally, here is question 3?:</b> 1</li></ul></div>', 'Likert (Raw value)' => '<div class="item-list"><ul><li><b>q1:</b> 1</li><li><b>q2:</b> 1</li><li><b>q3:</b> 1</li></ul></div>', 'Likert (List)' => '<div class="item-list"><ul><li><b>Please answer question 1?:</b> 1</li><li><b>How about now answering question 2?:</b> 1</li><li><b>Finally, here is question 3?:</b> 1</li></ul></div>', - 'Address (Value)' => '10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan<br />', - 'Address (Raw value)' => '<div class="item-list"><ul><li><b>address:</b> 10 Main Street</li><li><b>address_2:</b> 10 Main Street</li><li><b>city:</b> Springfield</li><li><b>state_province:</b> Alabama</li><li><b>postal_code:</b> Loremipsum</li><li><b>country:</b> Afghanistan</li></ul></div><br /><br />', - 'Address (List)' => '<div class="item-list"><ul><li><b>Address:</b> 10 Main Street</li><li><b>Address 2:</b> 10 Main Street</li><li><b>City/Town:</b> Springfield</li><li><b>State/Province:</b> Alabama</li><li><b>Zip/Postal Code:</b> Loremipsum</li><li><b>Country:</b> Afghanistan</li></ul></div><br /><br />', + 'Basic address (Value)' => '10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan<br />', + 'Basic address (Raw value)' => '<div class="item-list"><ul><li><b>address:</b> 10 Main Street</li><li><b>address_2:</b> 10 Main Street</li><li><b>city:</b> Springfield</li><li><b>state_province:</b> Alabama</li><li><b>postal_code:</b> 11111</li><li><b>country:</b> Afghanistan</li></ul></div><br /><br />', + 'Basic address (List)' => '<div class="item-list"><ul><li><b>Address:</b> 10 Main Street</li><li><b>Address 2:</b> 10 Main Street</li><li><b>City/Town:</b> Springfield</li><li><b>State/Province:</b> Alabama</li><li><b>ZIP/Postal Code:</b> 11111</li><li><b>Country:</b> Afghanistan</li></ul></div><br /><br />', + 'Advanced address (Value)' => '<div class="address" translate="no"><span class="given-name">John</span> <span class="family-name">Smith</span><br> +<span class="organization">Google Inc.</span><br> +<span class="address-line1">1098 Alta Ave</span><br> +<span class="locality">Mountain View</span>, <span class="administrative-area">CA</span> <span class="postal-code">94043</span><br> +<span class="country">United States</span></div>', + 'Advanced address (Raw value)' => '<div class="item-list"><ul><li><b>given_name:</b> John</li><li><b>family_name:</b> Smith</li><li><b>organization:</b> Google Inc.</li><li><b>address_line1:</b> 1098 Alta Ave</li><li><b>postal_code:</b> 94043</li><li><b>locality:</b> Mountain View</li><li><b>administrative_area:</b> CA</li><li><b>country_code:</b> US</li><li><b>langcode:</b> en</li></ul>', + 'Advanced address (List)' => '<div class="item-list"><ul><li><b>Given name:</b> John</li><li><b>Family name:</b> Smith</li><li><b>Organization:</b> Google Inc.</li><li><b>Address line 1:</b> 1098 Alta Ave</li><li><b>Postal code:</b> 94043</li><li><b>Locality:</b> Mountain View</li><li><b>Administrative area:</b> CA</li><li><b>Country code:</b> US</li><li><b>Language code:</b> en</li></ul>', 'Link (Value)' => '<a href="http://example.com">Loremipsum</a>', ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<b>' . $label . '</b><br />' . $value, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<b>' . $label . '</b><br />' . $value, $body, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); } // Check composite elements formatted as text. $body = $this->getMessageBody($submission, 'email_text'); $elements = [ 'Link (Value): Loremipsum (http://example.com)', - 'Address (Value): -10 Main Street -10 Main Street -Springfield, Alabama. Loremipsum -Afghanistan', - 'Address (Raw value): -address: 10 Main Street -address_2: 10 Main Street -city: Springfield -state_province: Alabama -postal_code: Loremipsum -country: Afghanistan', - 'Address (List): -Address: 10 Main Street -Address 2: 10 Main Street -City/Town: Springfield -State/Province: Alabama -Zip/Postal Code: Loremipsum -Country: Afghanistan', 'Likert (Value): Please answer question 1?: 1 How about now answering question 2?: 1 @@ -98,9 +86,54 @@ public function testFormat() { Please answer question 1?: 1 How about now answering question 2?: 1 Finally, here is question 3?: 1', + 'Basic address (Value): +10 Main Street +10 Main Street +Springfield, Alabama. 11111 +Afghanistan', + 'Basic address (Raw value): +address: 10 Main Street +address_2: 10 Main Street +city: Springfield +state_province: Alabama +postal_code: 11111 +country: Afghanistan', + 'Basic address (List): +Address: 10 Main Street +Address 2: 10 Main Street +City/Town: Springfield +State/Province: Alabama +ZIP/Postal Code: 11111 +Country: Afghanistan', + 'Advanced address (Value): +John Smith +Google Inc. +1098 Alta Ave +Mountain View, CA 94043 +United States', + 'Advanced address (Raw value): +given_name: John +family_name: Smith +organization: Google Inc. +address_line1: 1098 Alta Ave +postal_code: 94043 +locality: Mountain View +administrative_area: CA +country_code: US +langcode: en', + 'Advanced address (List): +Given name: John +Family name: Smith +Organization: Google Inc. +Address line 1: 1098 Alta Ave +Postal code: 94043 +Locality: Mountain View +Administrative area: CA +Country code: US +Language code: en', ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } /**************************************************************************/ @@ -121,62 +154,62 @@ public function testFormat() { $this->debug($body); $elements = [ - 'Address (Ordered list)' => '<div class="item-list"><ol><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li></ol></div>', - 'Address (Unordered list)' => '<div class="item-list"><ul><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan</li></ul></div>', - 'Address (Horizontal rule)' => '10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan<hr class="webform-horizontal-rule" />10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan<hr class="webform-horizontal-rule" />10 Main Street<br />10 Main Street<br />Springfield, Alabama. Loremipsum<br />Afghanistan', - 'Address (Table)' => '<table width="100%" cellspacing="0" cellpadding="5" border="1" class="responsive-enabled" data-striping="1"><thead><tr><th bgcolor="#eee">Address</th><th bgcolor="#eee">Address 2</th><th bgcolor="#eee">City/Town</th><th bgcolor="#eee">State/Province</th><th bgcolor="#eee">Zip/Postal Code</th><th bgcolor="#eee">Country</th></tr></thead><tbody><tr class="odd"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>Loremipsum</td><td>Afghanistan</td></tr><tr class="even"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>Loremipsum</td><td>Afghanistan</td></tr><tr class="odd"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>Loremipsum</td><td>Afghanistan</td></tr></tbody></table>', + 'Basic address (Ordered list)' => '<div class="item-list"><ol><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li></ol></div>', + 'Basic address (Unordered list)' => '<div class="item-list"><ul><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li><li>10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan</li></ul></div>', + 'Basic address (Horizontal rule)' => '10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan<hr class="webform-horizontal-rule" />10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan<hr class="webform-horizontal-rule" />10 Main Street<br />10 Main Street<br />Springfield, Alabama. 11111<br />Afghanistan', + 'Basic address (Table)' => '<table width="100%" cellspacing="0" cellpadding="5" border="1" class="responsive-enabled" data-striping="1"><thead><tr><th bgcolor="#eee">Address</th><th bgcolor="#eee">Address 2</th><th bgcolor="#eee">City/Town</th><th bgcolor="#eee">State/Province</th><th bgcolor="#eee">ZIP/Postal Code</th><th bgcolor="#eee">Country</th></tr></thead><tbody><tr class="odd"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>11111</td><td>Afghanistan</td></tr><tr class="even"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>11111</td><td>Afghanistan</td></tr><tr class="odd"><td>10 Main Street</td><td>10 Main Street</td><td>Springfield</td><td>Alabama</td><td>11111</td><td>Afghanistan</td></tr></tbody></table>', ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<b>' . $label . '</b><br />' . $value, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<b>' . $label . '</b><br />' . $value, $body, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); } // Check composite elements formatted as text. $body = $this->getMessageBody($submission, 'email_text'); $elements = [ - 'Address (Ordered list): + 'Basic address (Ordered list): 1. 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan 2. 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan 3. 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan', - 'Address (Unordered list): + 'Basic address (Unordered list): - 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan - 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan - 10 Main Street 10 Main Street - Springfield, Alabama. Loremipsum + Springfield, Alabama. 11111 Afghanistan', - 'Address (Horizontal rule): + 'Basic address (Horizontal rule): 10 Main Street 10 Main Street -Springfield, Alabama. Loremipsum +Springfield, Alabama. 11111 Afghanistan --- 10 Main Street 10 Main Street -Springfield, Alabama. Loremipsum +Springfield, Alabama. 11111 Afghanistan --- 10 Main Street 10 Main Street -Springfield, Alabama. Loremipsum +Springfield, Alabama. 11111 Afghanistan', ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } } diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositePluginFileTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositePluginFileTest.php new file mode 100644 index 0000000000..ff9da7522a --- /dev/null +++ b/web/modules/webform/src/Tests/Composite/WebformCompositePluginFileTest.php @@ -0,0 +1,103 @@ +<?php + +namespace Drupal\webform\Tests\Composite; + +use Drupal\file\Entity\File; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\Element\WebformElementManagedFileTestBase; + +/** + * Tests for composite plugin file upload. + * + * @group Webform + */ +class WebformCompositePluginFileTest extends WebformElementManagedFileTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform_test_element']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_comp_file_plugin']; + + /** + * Test composite plugin. + */ + public function testPlugin() { + $webform = Webform::load('test_element_comp_file_plugin'); + + $first_file = $this->files[0]; + $second_file = $this->files[1]; + + /**************************************************************************/ + // Single composite with file upload. + /**************************************************************************/ + + // Create submission with file. + $edit = [ + 'webform_test_composite_file[textfield]' => '{textfield}', + 'files[webform_test_composite_file_managed_file]' => \Drupal::service('file_system')->realpath($first_file->uri), + ]; + $sid = $this->postSubmission($webform, $edit); + $webform_submission = WebformSubmission::load($sid); + + $fid = $this->getLastFileId(); + $file = File::load($fid); + + // Check file permanent. + $this->assert($file->isPermanent(), 'Test file is permanent'); + + // Check file upload. + $element_data = $webform_submission->getElementData('webform_test_composite_file'); + $this->assertEqual($element_data['managed_file'], $fid, 'Test file was upload to the current submission'); + + // Check test file file usage. + $this->assertIdentical(['webform' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($file), 'The file has 1 usage.'); + + // Check test file uploaded file path. + $this->assertEqual($file->getFileUri(), 'private://webform/test_element_comp_file_plugin/' . $sid . '/' . $first_file->filename); + + // Check that test file exists. + $this->assert(file_exists($file->getFileUri()), 'File exists'); + + /**************************************************************************/ + // Multiple composite with file upload. + /**************************************************************************/ + + // Create submission with file. + $edit = [ + 'webform_test_composite_file_multiple_header[items][0][textfield]' => '{textfield}', + 'files[webform_test_composite_file_multiple_header_items_0_managed_file]' => \Drupal::service('file_system')->realpath($second_file->uri), + ]; + $sid = $this->postSubmission($webform, $edit); + $webform_submission = WebformSubmission::load($sid); + + $fid = $this->getLastFileId(); + $file = File::load($fid); + + // Check file permanent. + $this->assert($file->isPermanent(), 'Test file is permanent'); + + // Check file upload. + $element_data = $webform_submission->getElementData('webform_test_composite_file_multiple_header'); + $this->assertEqual($element_data[0]['managed_file'], $fid, 'Test file was upload to the current submission'); + + // Check test file file usage. + $this->assertIdentical(['webform' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($file), 'The file has 1 usage.'); + + // Check test file uploaded file path. + $this->assertEqual($file->getFileUri(), 'private://webform/test_element_comp_file_plugin/' . $sid . '/' . $second_file->filename); + + // Check that test file exists. + $this->assert(file_exists($file->getFileUri()), 'File exists'); + } + +} diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositePluginTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositePluginTest.php index 953e69132d..f71b51b7fc 100644 --- a/web/modules/webform/src/Tests/Composite/WebformCompositePluginTest.php +++ b/web/modules/webform/src/Tests/Composite/WebformCompositePluginTest.php @@ -32,10 +32,10 @@ public function testPlugin() { /* Display */ - $this->drupalGet('webform/test_element_composite_plugin'); + $this->drupalGet('/webform/test_element_composite_plugin'); // Check fieldset with nested elements is rendered. - $this->assertRaw('<fieldset data-drupal-selector="edit-webform-test-composite-fieldset" id="edit-webform-test-composite-fieldset" class="js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-test-composite-fieldset" id="edit-webform-test-composite-fieldset" class="js-webform-type-fieldset webform-type-fieldset js-form-item form-item js-form-wrapper form-wrapper">'); $this->assertRaw('<span class="fieldset-legend">fieldset</span>'); /* Processing */ @@ -61,7 +61,7 @@ public function testPlugin() { 'webform_test_composite[datetime][time]' => '23:19:25', 'webform_test_composite[nested_tel]' => '123-456-7890', 'webform_test_composite[nested_select]' => 'Monday', - 'webform_test_composite[nested_radios]' => 'Monday' + 'webform_test_composite[nested_radios]' => 'Monday', ]; $this->drupalPostForm('webform/test_element_composite_plugin', $edit, t('Submit')); $this->assertRaw("webform_test_composite: diff --git a/web/modules/webform/src/Tests/Composite/WebformCompositeTest.php b/web/modules/webform/src/Tests/Composite/WebformCompositeTest.php index 729ba9c8f4..f0198a729e 100644 --- a/web/modules/webform/src/Tests/Composite/WebformCompositeTest.php +++ b/web/modules/webform/src/Tests/Composite/WebformCompositeTest.php @@ -25,10 +25,11 @@ public function testComposite() { /* Display */ - $this->drupalGet('webform/test_composite'); + $this->drupalGet('/webform/test_composite'); // Check webform contact basic. - $this->assertRaw('<div id="edit-contact-basic--wrapper" class="form-composite js-form-item form-item js-form-type-webform-contact form-type-webform-contact js-form-item-contact-basic form-item-contact-basic form-no-label">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-contact-basic" id="edit-contact-basic--wrapper" class="webform-contact--wrapper fieldgroup form-composite webform-composite-hidden-title required js-webform-type-webform-contact webform-type-webform-contact js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<span class="visually-hidden fieldset-legend js-form-required form-required">Contact basic</span>'); $this->assertRaw('<label for="edit-contact-basic-name" class="js-form-required form-required">Name</label>'); $this->assertRaw('<input data-drupal-selector="edit-contact-basic-name" type="text" id="edit-contact-basic-name" name="contact_basic[name]" value="John Smith" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); @@ -45,9 +46,9 @@ public function testComposite() { $this->assertNoRaw('edit-contact-advanced-country'); // Check link multiple in table. - $this->assertRaw('<label for="edit-link-multiple">Link multiple</label>'); - $this->assertRaw('<th class="link_multiple-table--title webform-multiple-table--title">Link Title<a href="#help" title="This is link title help" data-webform-help="This is link title help" class="webform-element-help">?</a>'); - $this->assertRaw('<th class="link_multiple-table--url webform-multiple-table--url">Link URL<a href="#help" title="This is link url help" data-webform-help="This is link url help" class="webform-element-help">?</a>'); + $this->assertRaw('<label>Link multiple</label>'); + $this->assertRaw('<th class="link_multiple-table--title webform-multiple-table--title">Link Title<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Link Title</div><div class="webform-element-help--content">This is link title help</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<th class="link_multiple-table--url webform-multiple-table--url">Link URL<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Link URL</div><div class="webform-element-help--content">This is link url help</div>"><span aria-hidden="true">?</span></span>'); /* Processing */ diff --git a/web/modules/webform/src/Tests/Element/WebformElementAccessTest.php b/web/modules/webform/src/Tests/Element/WebformElementAccessTest.php index 8202987069..14dbc73731 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementAccessTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementAccessTest.php @@ -26,66 +26,78 @@ class WebformElementAccessTest extends WebformElementTestBase { */ protected static $testWebforms = ['test_element_access']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test element access. */ public function testAccess() { + $normal_user = $this->drupalCreateUser([ + 'access user profiles', + ]); + + $admin_submission_user = $this->drupalCreateUser([ + 'access user profiles', + 'administer webform submission', + ]); + + $own_submission_user = $this->drupalCreateUser([ + 'access user profiles', + 'access webform overview', + 'create webform', + 'edit own webform', + 'delete own webform', + 'view own webform submission', + 'edit own webform submission', + 'delete own webform submission', + ]); + $webform = Webform::load('test_element_access'); + /**************************************************************************/ + // Check user from USER:1 to admin submission user. $elements = $webform->get('elements'); - $elements = str_replace(' - 1', ' - ' . $this->adminSubmissionUser->id(), $elements); - $elements = str_replace('USER:1', 'USER:' . $this->adminSubmissionUser->id(), $elements); + $elements = str_replace(' - 1', ' - ' . $admin_submission_user->id(), $elements); + $elements = str_replace('USER:1', 'USER:' . $admin_submission_user->id(), $elements); $webform->set('elements', $elements); $webform->save(); // Create a webform submission. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $sid = $this->postSubmission($webform); $webform_submission = WebformSubmission::load($sid); // Check admins have 'administer webform element access' permission. $this->drupalLogin($this->rootUser); - $this->drupalGet('admin/structure/webform/manage/test_element_access/element/access_create_roles_anonymous/edit'); + $this->drupalGet('/admin/structure/webform/manage/test_element_access/element/access_create_roles_anonymous/edit'); $this->assertFieldById('edit-properties-access-create-roles-anonymous'); // Check webform builder don't have 'administer webform element access' // permission. - $this->drupalLogin($this->ownWebformUser); - $this->drupalGet('admin/structure/webform/manage/test_element_access/element/access_create_roles_anonymous/edit'); + $this->drupalLogin($own_submission_user); + $this->drupalGet('/admin/structure/webform/manage/test_element_access/element/access_create_roles_anonymous/edit'); $this->assertNoFieldById('edit-properties-access-create-roles-anonymous'); /* Create access */ // Check anonymous role access. $this->drupalLogout(); - $this->drupalGet('webform/test_element_access'); + $this->drupalGet('/webform/test_element_access'); $this->assertFieldByName('access_create_roles_anonymous'); $this->assertNoFieldByName('access_create_roles_authenticated'); $this->assertNoFieldByName('access_create_users'); $this->assertNoFieldByName('access_create_permissions'); // Check authenticated access. - $this->drupalLogin($this->normalUser); - $this->drupalGet('webform/test_element_access'); + $this->drupalLogin($normal_user); + $this->drupalGet('/webform/test_element_access'); $this->assertNoFieldByName('access_create_roles_anonymous'); $this->assertFieldByName('access_create_roles_authenticated'); $this->assertNoFieldByName('access_create_users'); $this->assertFieldByName('access_create_permissions'); // Check admin user access. - $this->drupalLogin($this->adminSubmissionUser); - $this->drupalGet('webform/test_element_access'); + $this->drupalLogin($admin_submission_user); + $this->drupalGet('/webform/test_element_access'); $this->assertNoFieldByName('access_create_roles_anonymous'); $this->assertFieldByName('access_create_roles_authenticated'); $this->assertFieldByName('access_create_users'); @@ -102,7 +114,7 @@ public function testAccess() { $this->assertNoFieldByName('access_update_permissions'); // Check authenticated role access. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $this->drupalGet("/webform/test_element_access/submissions/$sid/edit"); $this->assertNoFieldByName('access_update_roles_anonymous'); $this->assertFieldByName('access_update_roles_authenticated'); @@ -110,7 +122,7 @@ public function testAccess() { $this->assertFieldByName('access_update_permissions'); // Check admin user access. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); $this->drupalGet("/admin/structure/webform/manage/test_element_access/submission/$sid/edit"); $this->assertNoFieldByName('access_update_roles_anonymous'); $this->assertFieldByName('access_update_roles_authenticated'); @@ -133,7 +145,7 @@ public function testAccess() { $this->drupalGet($url['path'], $url['options']); $this->assertRaw('access_view_roles (anonymous)'); $this->assertNoRaw('access_view_roles (authenticated)'); - $this->assertNoRaw('access_view_users (USER:' . $this->adminSubmissionUser->id() . ')'); + $this->assertNoRaw('access_view_users (USER:' . $admin_submission_user->id() . ')'); $this->assertNoRaw('access_view_permissions (access user profiles)'); // Check authenticated role access. @@ -141,15 +153,15 @@ public function testAccess() { $this->drupalGet($url['path'], $url['options']); $this->assertNoRaw('access_view_roles (anonymous)'); $this->assertRaw('access_view_roles (authenticated)'); - $this->assertNoRaw('access_view_users (USER:' . $this->adminSubmissionUser->id() . ')'); + $this->assertNoRaw('access_view_users (USER:' . $admin_submission_user->id() . ')'); $this->assertRaw('access_view_permissions (access user profiles)'); // Check admin user access. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); $this->drupalGet($url['path'], $url['options']); $this->assertNoRaw('access_view_roles (anonymous)'); $this->assertRaw('access_view_roles (authenticated)'); - $this->assertRaw('access_view_users (USER:' . $this->adminSubmissionUser->id() . ')'); + $this->assertRaw('access_view_users (USER:' . $admin_submission_user->id() . ')'); $this->assertRaw('access_view_permissions (access user profiles)'); } @@ -172,7 +184,7 @@ public function testAccess() { $this->assertNoRaw($raw, 'Anonymous user can not access token'); // Check authenticated role access. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $this->drupalGet($url['path'], $url['options']); $this->assertNoRaw($raw, 'Authenticated user can not access token'); @@ -182,7 +194,7 @@ public function testAccess() { $this->assertRaw($raw, 'Admin webform user can access token'); // Check admin submission access. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); $this->drupalGet($url['path'], $url['options']); $this->assertRaw($raw, 'Admin submission user can access token'); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementActionsTest.php b/web/modules/webform/src/Tests/Element/WebformElementActionsTest.php index 1ef08c84c1..c0da1e048b 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementActionsTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementActionsTest.php @@ -23,13 +23,13 @@ public function testActions() { /* Test webform actions */ // Get form. - $this->drupalGet('webform/test_element_actions'); + $this->drupalGet('/webform/test_element_actions'); // Check custom actions. $this->assertRaw('<div style="border: 2px solid red; padding: 10px" data-drupal-selector="edit-actions-custom" class="form-actions webform-actions js-form-wrapper form-wrapper" id="edit-actions-custom">'); - $this->assertRaw('<input class="webform-button--draft js-webform-novalidate custom-draft button js-form-submit form-submit" style="font-weight: bold" data-custom-draft data-drupal-selector="edit-actions-custom-draft" type="submit" id="edit-actions-custom-draft" name="op" value="{Custom draft}" />'); + $this->assertRaw('<input formnovalidate="formnovalidate" class="webform-button--draft custom-draft button js-form-submit form-submit" style="font-weight: bold" data-custom-draft data-drupal-selector="edit-actions-custom-draft" type="submit" id="edit-actions-custom-draft" name="op" value="{Custom draft}" />'); $this->assertRaw('<input class="webform-button--next custom-wizard-next button js-form-submit form-submit" style="font-weight: bold" data-custom-wizard-next data-drupal-selector="edit-actions-custom-wizard-next" type="submit" id="edit-actions-custom-wizard-next" name="op" value="{Custom wizard next}" />'); - $this->assertRaw('<input class="webform-button--reset js-webform-novalidate custom-reet button js-form-submit form-submit" style="font-weight: bold" data-custom-reset data-drupal-selector="edit-actions-custom-reset" type="submit" id="edit-actions-custom-reset" name="op" value="{Custom reset}" />'); + $this->assertRaw('<input formnovalidate="formnovalidate" class="webform-button--reset custom-reet button js-form-submit form-submit" style="font-weight: bold" data-custom-reset data-drupal-selector="edit-actions-custom-reset" type="submit" id="edit-actions-custom-reset" name="op" value="{Custom reset}" />'); // Check wizard next. $this->assertRaw('id="edit-actions-wizard-next-wizard-next"'); @@ -60,24 +60,24 @@ public function testActions() { /* Test actions buttons */ $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/test_element_actions_buttons'); + $this->drupalGet('/webform/test_element_actions_buttons'); // Check draft button. - $this->assertRaw('<input class="webform-button--draft js-webform-novalidate draft_button_attributes button js-form-submit form-submit" style="color: blue" data-drupal-selector="edit-actions-draft" type="submit" id="edit-actions-draft" name="op" value="Save Draft" />'); + $this->assertRaw('<input formnovalidate="formnovalidate" class="webform-button--draft draft_button_attributes button js-form-submit form-submit" style="color: blue" data-drupal-selector="edit-actions-draft" type="submit" id="edit-actions-draft" name="op" value="Save Draft" />'); // Check next button. $this->assertRaw('<input class="webform-button--next wizard_next_button_attributes button js-form-submit form-submit" style="color: yellow" data-drupal-selector="edit-actions-wizard-next" type="submit" id="edit-actions-wizard-next" name="op" value="Next Page >" />'); $this->drupalPostForm('webform/test_element_actions_buttons', [], t('Next Page >')); // Check previous button. - $this->assertRaw('<input class="webform-button--previous js-webform-novalidate wizard_prev_button_attributes button js-form-submit form-submit" style="color: yellow" data-drupal-selector="edit-actions-wizard-prev" type="submit" id="edit-actions-wizard-prev" name="op" value="< Previous Page" />'); + $this->assertRaw('<input formnovalidate="formnovalidate" class="webform-button--previous wizard_prev_button_attributes button js-form-submit form-submit" style="color: yellow" data-drupal-selector="edit-actions-wizard-prev" type="submit" id="edit-actions-wizard-prev" name="op" value="< Previous Page" />'); // Check preview button. $this->assertRaw('<input class="webform-button--preview preview_next_button_attributes button js-form-submit form-submit" style="color: orange" data-drupal-selector="edit-actions-preview-next" type="submit" id="edit-actions-preview-next" name="op" value="Preview" />'); $this->drupalPostForm(NULL, [], t('Preview')); // Check previous button. - $this->assertRaw('<input class="webform-button--previous js-webform-novalidate preview_prev_button_attributes button js-form-submit form-submit" style="color: orange" data-drupal-selector="edit-actions-preview-prev" type="submit" id="edit-actions-preview-prev" name="op" value="< Previous" />'); + $this->assertRaw('<input formnovalidate="formnovalidate" class="webform-button--previous preview_prev_button_attributes button js-form-submit form-submit" style="color: orange" data-drupal-selector="edit-actions-preview-prev" type="submit" id="edit-actions-preview-prev" name="op" value="< Previous" />'); // Check submit button. $this->assertRaw('<input class="webform-button--submit form_submit_attributes button button--primary js-form-submit form-submit" style="color: green" data-drupal-selector="edit-actions-submit" type="submit" id="edit-actions-submit" name="op" value="Submit" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementAddressTest.php b/web/modules/webform/src/Tests/Element/WebformElementAddressTest.php new file mode 100644 index 0000000000..0c106609f2 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementAddressTest.php @@ -0,0 +1,155 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\webform\Entity\Webform; + +/** + * Tests for webform address element. + * + * @group Webform + */ +class WebformElementAddressTest extends WebformElementTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'address', 'node']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_address']; + + /** + * Tests address element. + */ + public function testAddress() { + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('test_element_address'); + + /**************************************************************************/ + // Rendering. + /**************************************************************************/ + + $this->drupalGet('/webform/test_element_address'); + + // Check basic fieldset wrapper. + $this->assertRaw('<fieldset data-drupal-selector="edit-address" id="edit-address--wrapper" class="address--wrapper fieldgroup form-composite webform-composite-hidden-title js-webform-type-address webform-type-address js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<span class="visually-hidden fieldset-legend">address_basic</span>'); + + // Check advanced fieldset, legend, help, and description. + $this->assertRaw('<fieldset data-drupal-selector="edit-address-advanced" aria-describedby="edit-address-advanced--wrapper--description" id="edit-address-advanced--wrapper" class="address--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-address webform-type-address js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<span class="fieldset-legend">address_advanced<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">address_advanced</div><div class="webform-element-help--content">This is help text</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<div class="description"><div id="edit-address-advanced--wrapper--description" class="webform-element-description">This is a description</div>'); + + /**************************************************************************/ + // Processing. + /**************************************************************************/ + + // Check submitted value. + $sid = $this->postSubmission($webform); + $this->assertRaw("address: + country_code: US + langcode: en + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + address_line2: '' + locality: 'Mountain View' + administrative_area: CA + postal_code: '94043' + additional_name: null + sorting_code: null + dependent_locality: null +address_advanced: + country_code: US + langcode: en + address_line1: '1098 Alta Ave' + address_line2: '' + locality: 'Mountain View' + administrative_area: CA + postal_code: '94043' + given_name: null + additional_name: null + family_name: null + organization: null + sorting_code: null + dependent_locality: null +address_none: null +address_multiple: + - country_code: US + langcode: en + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + address_line2: '' + locality: 'Mountain View' + administrative_area: CA + postal_code: '94043'"); + + // Check text formatting. + $this->drupalGet("/admin/structure/webform/manage/test_element_address/submission/$sid/text"); + $this->assertRaw('address_basic: +John Smith +Google Inc. +1098 Alta Ave +Mountain View, CA 94043 +United States + +address_advanced: +1098 Alta Ave +Mountain View, CA 94043 +United States + +address_none: +{Empty} + +address_multiple: +- John Smith + Google Inc. + 1098 Alta Ave + Mountain View, CA 94043 + United States'); + + /**************************************************************************/ + // Schema. + /**************************************************************************/ + + $field_storage = FieldStorageConfig::create([ + 'entity_type' => 'node', + 'field_name' => 'address', + 'type' => 'address', + ]); + $schema = $field_storage->getSchema(); + + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + /** @var \Drupal\webform\Plugin\WebformElement\Address $element_plugin */ + $element_plugin = $element_manager->getElementInstance(['#type' => 'address']); + + // Get webform address element plugin. + $element = []; + $element_plugin->initializeCompositeElements($element); + + // Check composite elements against address schema. + $composite_elements = $element['#webform_composite_elements']; + $diff_composite_elements = array_diff_key($composite_elements, $schema['columns']); + $this->debug($diff_composite_elements); + $this->assert(empty($diff_composite_elements)); + + // Check composite elements maxlength against address schema. + foreach ($schema['columns'] as $column_name => $column) { + $this->assertEqual($composite_elements[$column_name]['#maxlength'], $column['length']); + } + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementAllowsTagsTest.php b/web/modules/webform/src/Tests/Element/WebformElementAllowsTagsTest.php index 8e3702f434..ea5c752200 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementAllowsTagsTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementAllowsTagsTest.php @@ -21,15 +21,15 @@ class WebformElementAllowsTagsTest extends WebformElementTestBase { */ public function testAllowsTags() { // Check <b> tags is allowed. - $this->drupalGet('webform/test_element_allowed_tags'); - $this->assertRaw('Hello <b>...Goodbye</b>'); + $this->drupalGet('/webform/test_element_allowed_tags'); + $this->assertRaw('Hello <b>…Goodbye</b>'); // Check custom <ignored> <tag> is allowed and <b> tag removed. \Drupal::configFactory()->getEditable('webform.settings') ->set('element.allowed_tags', 'ignored tag') ->save(); - $this->drupalGet('webform/test_element_allowed_tags'); - $this->assertRaw('Hello <ignored></tag>...Goodbye'); + $this->drupalGet('/webform/test_element_allowed_tags'); + $this->assertRaw('Hello <ignored></tag>…Goodbye'); // Restore admin tags. \Drupal::configFactory()->getEditable('webform.settings') diff --git a/web/modules/webform/src/Tests/Element/WebformElementAutocompleteTest.php b/web/modules/webform/src/Tests/Element/WebformElementAutocompleteTest.php index 5435012c43..1e957d0069 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementAutocompleteTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementAutocompleteTest.php @@ -26,53 +26,53 @@ public function testAutocomplete() { /* Test #autocomplete property */ - $this->drupalGet('webform/test_element_autocomplete'); + $this->drupalGet('/webform/test_element_autocomplete'); $this->assertRaw('<input autocomplete="off" data-drupal-selector="edit-autocomplete-off" type="email" id="edit-autocomplete-off" name="autocomplete_off" value="" size="60" maxlength="254" class="form-email" />'); /* Test #autocomplete_items element property */ // Check routes data-drupal-selector. - $this->drupalGet('webform/test_element_autocomplete'); + $this->drupalGet('/webform/test_element_autocomplete'); $this->assertRaw('<input data-drupal-selector="edit-autocomplete-items" class="form-autocomplete form-text webform-autocomplete" data-autocomplete-path="' . $base_path . 'webform/test_element_autocomplete/autocomplete/autocomplete_items" type="text" id="edit-autocomplete-items" name="autocomplete_items" value="" size="60" maxlength="255" />'); // Check #autocomplete_items partial match. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'United']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'United']]); $this->assertRaw('[{"value":"United Arab Emirates","label":"United Arab Emirates"},{"value":"United Kingdom","label":"United Kingdom"},{"value":"United States","label":"United States"}]'); // Check #autocomplete_items exact match. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'United States']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'United States']]); $this->assertRaw('[{"value":"United States","label":"United States"}]'); // Check #autocomplete_items just one character. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'U']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_items', ['query' => ['q' => 'U']]); $this->assertRaw('[{"value":"Anguilla","label":"Anguilla"},{"value":"Antigua and Barbuda","label":"Antigua and Barbuda"},{"value":"Aruba","label":"Aruba"},{"value":"Australia","label":"Australia"},{"value":"Austria","label":"Austria"}]'); /* Test #autocomplete_existing element property */ // Check autocomplete is not enabled until there is a submission. - $this->drupalGet('webform/test_element_autocomplete'); + $this->drupalGet('/webform/test_element_autocomplete'); $this->assertNoRaw('<input data-drupal-selector="edit-autocomplete-existing" class="form-autocomplete form-text" data-autocomplete-path="' . $base_path . 'webform/test_element_autocomplete/autocomplete/autocomplete_existing" type="text" id="edit-autocomplete-existing" name="autocomplete_existing" value="" size="60" maxlength="255" />'); $this->assertRaw('<input data-drupal-selector="edit-autocomplete-existing" type="text" id="edit-autocomplete-existing" name="autocomplete_existing" value="" size="60" maxlength="255" class="form-text webform-autocomplete" />'); // Check #autocomplete_existing no match. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'abc']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'abc']]); $this->assertRaw('[]'); // Add #autocomplete_existing values to the submission table. $this->drupalPostForm('webform/test_element_autocomplete', ['autocomplete_existing' => 'abcdefg'], t('Submit')); // Check #autocomplete_existing enabled now that there is submission. - $this->drupalGet('webform/test_element_autocomplete'); + $this->drupalGet('/webform/test_element_autocomplete'); $this->assertRaw('<input data-drupal-selector="edit-autocomplete-existing" class="form-autocomplete form-text webform-autocomplete" data-autocomplete-path="' . $base_path . 'webform/test_element_autocomplete/autocomplete/autocomplete_existing" type="text" id="edit-autocomplete-existing" name="autocomplete_existing" value="" size="60" maxlength="255" />'); $this->assertNoRaw('<input data-drupal-selector="edit-autocomplete-existing" type="text" id="edit-autocomplete-existing" name="autocomplete_existing" value="" size="60" maxlength="255" class="form-text webform-autocomplete" />'); // Check #autocomplete_existing match. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'abc']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'abc']]); $this->assertNoRaw('[]'); $this->assertRaw('[{"value":"abcdefg","label":"abcdefg"}]'); // Check #autocomplete_existing minimum number of characters < 3. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'ab']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_existing', ['query' => ['q' => 'ab']]); $this->assertRaw('[]'); $this->assertNoRaw('[{"value":"abcdefg","label":"abcdefg"}]'); @@ -82,7 +82,7 @@ public function testAutocomplete() { $this->drupalPostForm('webform/test_element_autocomplete', ['autocomplete_both' => 'Existing Item'], t('Submit')); // Check #autocomplete_both match. - $this->drupalGet('webform/test_element_autocomplete/autocomplete/autocomplete_both', ['query' => ['q' => 'Item']]); + $this->drupalGet('/webform/test_element_autocomplete/autocomplete/autocomplete_both', ['query' => ['q' => 'Item']]); $this->assertNoRaw('[]'); $this->assertRaw('[{"value":"Example Item","label":"Example Item"},{"value":"Existing Item","label":"Existing Item"}]'); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementCaptchaTest.php b/web/modules/webform/src/Tests/Element/WebformElementCaptchaTest.php new file mode 100644 index 0000000000..c5c48f265e --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementCaptchaTest.php @@ -0,0 +1,94 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for CAPTCHA element. + * + * @group Webform + */ +class WebformElementCaptchaTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_captcha']; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'captcha']; + + /** + * Test CAPTCHA element. + */ + public function testCaptcha() { + $this->drupalGet('/webform/test_element_captcha'); + + // Check default title and description. + $this->assertRaw('<label for="edit-captcha-response" class="js-form-required form-required">Math question</label>'); + $this->assertRaw('Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.'); + + // Check CAPTCHA element custom title and description. + $this->assertRaw('<label for="edit-captcha-response--4" class="js-form-required form-required">{captcha_math_title}</label>'); + $this->assertRaw('{captcha_math_description}'); + + // Enable CAPTCHA admin mode. + \Drupal::configFactory() + ->getEditable('captcha.settings') + ->set('administration_mode', TRUE) + ->save(); + + // Login root user. + $this->drupalLogin($this->rootUser); + + // Check add CAPTCHA element text. + $this->drupalGet('/webform/contact'); + $this->assertRaw('CAPTCHA should be added as an element to this webform.'); + + // Check replace CAPTCHA element text. + $this->drupalGet('/webform/test_element_captcha'); + $this->assertNoRaw('/admin/structure/webform/manage/test_element_captcha/element/captcha/edit'); + $this->assertRaw('Untrusted users will see a CAPTCHA element on this webform.'); + + // Install the Webform UI. + \Drupal::service('module_installer')->install(['webform_ui']); + + // Check add CAPTCHA element text. + $this->drupalGet('/webform/contact'); + $this->assertRaw('Add CAPTCHA element to this webform for untrusted users.'); + + // Check replace CAPTCHA element text. + $this->drupalGet('/webform/test_element_captcha'); + $this->assertRaw('/admin/structure/webform/manage/test_element_captcha/element/captcha/edit'); + $this->assertRaw('Untrusted users will see a CAPTCHA element on this webform.'); + + // Disable replace CAPTCHA admin mode. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('third_party_settings.captcha.replace_administration_mode', FALSE) + ->save(); + + // Check add CAPTCHA not replaced. + $this->drupalGet('/webform/contact'); + $this->assertNoRaw('Add CAPTCHA element to this webform for untrusted users.'); + $this->assertRaw('Place a CAPTCHA here for untrusted users.'); + + // Enabled replace CAPTCHA admin mode and exclude the CAPTCHA element. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('element.excluded_elements', ['captcha' => 'captcha']) + ->set('third_party_settings.captcha.replace_administration_mode', FALSE) + ->save(); + + // Check add CAPTCHA is still not replaced. + $this->drupalGet('/webform/contact'); + $this->assertNoRaw('Add CAPTCHA element to this webform for untrusted users.'); + $this->assertRaw('Place a CAPTCHA here for untrusted users.'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementCheckboxesTest.php b/web/modules/webform/src/Tests/Element/WebformElementCheckboxesTest.php new file mode 100644 index 0000000000..2cec2d3c74 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementCheckboxesTest.php @@ -0,0 +1,49 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\webform\Entity\Webform; + +/** + * Tests for webform checkboxes element. + * + * @group Webform + */ +class WebformElementCheckboxesTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_checkboxes']; + + /** + * Tests checkbox and checkboxes element. + */ + public function testCheckboxes() { + $webform = Webform::load('test_element_checkboxes'); + + // Check exclude empty is not visible. + $edit = [ + 'checkboxes_required_conditions[Yes]' => TRUE, + 'checkboxes_other_required_conditions[checkboxes][Yes]' => TRUE, + ]; + $this->postSubmission($webform, $edit, t('Preview')); + $this->assertNoRaw('<label>checkbox_exclude_empty</label>'); + + // Uncheck #exclude_empty. + $webform->setElementProperties('checkbox_exclude_empty', ['#type' => 'checkbox', '#title' => 'checkbox_exclude_empty']); + $webform->save(); + + // Check exclude empty is visible. + $edit = [ + 'checkboxes_required_conditions[Yes]' => TRUE, + 'checkboxes_other_required_conditions[checkboxes][Yes]' => TRUE, + ]; + $this->postSubmission($webform, $edit, t('Preview')); + $this->assertRaw('<label>checkbox_exclude_empty</label>'); + + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementCodeMirrorTest.php b/web/modules/webform/src/Tests/Element/WebformElementCodeMirrorTest.php index 862e68964f..38108b36d9 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementCodeMirrorTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementCodeMirrorTest.php @@ -26,16 +26,21 @@ public function testCodeMirror() { /**************************************************************************/ // Check Text. - $this->drupalGet('webform/test_element_codemirror'); + $this->drupalGet('/webform/test_element_codemirror'); $this->assertRaw('<label for="edit-text-basic">text_basic</label>'); $this->assertRaw('<textarea data-drupal-selector="edit-text-basic" class="js-webform-codemirror webform-codemirror text form-textarea resize-vertical" data-webform-codemirror-mode="text/plain" id="edit-text-basic" name="text_basic" rows="5" cols="60">Hello</textarea>'); + // Check Text with no wrap. + $this->drupalGet('/webform/test_element_codemirror'); + $this->assertRaw('<label for="edit-text-basic-no-wrap">text_basic_no_wrap</label>'); + $this->assertRaw('<textarea data-drupal-selector="edit-text-basic-no-wrap" wrap="off" class="js-webform-codemirror webform-codemirror text form-textarea resize-vertical" data-webform-codemirror-mode="text/plain" id="edit-text-basic-no-wrap" name="text_basic_no_wrap" rows="5" cols="60">'); + /**************************************************************************/ // code:yaml /**************************************************************************/ // Check YAML. - $this->drupalGet('webform/test_element_codemirror'); + $this->drupalGet('/webform/test_element_codemirror'); $this->assertRaw('<label for="edit-yaml-basic">yaml_basic</label>'); $this->assertRaw('<textarea data-drupal-selector="edit-yaml-basic" class="js-webform-codemirror webform-codemirror yaml form-textarea resize-vertical" data-webform-codemirror-mode="text/x-yaml" id="edit-yaml-basic" name="yaml_basic" rows="5" cols="60">test: hello</textarea>'); @@ -65,7 +70,7 @@ public function testCodeMirror() { /**************************************************************************/ // Check HTML. - $this->drupalGet('webform/test_element_codemirror'); + $this->drupalGet('/webform/test_element_codemirror'); $this->assertRaw('<label for="edit-html-basic">html_basic</label>'); $this->assertRaw('<textarea data-drupal-selector="edit-html-basic" class="js-webform-codemirror webform-codemirror html form-textarea resize-vertical" data-webform-codemirror-mode="text/html" id="edit-html-basic" name="html_basic" rows="5" cols="60"><b>Hello</b></textarea>'); @@ -90,7 +95,7 @@ public function testCodeMirror() { /**************************************************************************/ // Check disabled Twig editor. - $this->drupalGet('webform/test_element_codemirror'); + $this->drupalGet('/webform/test_element_codemirror'); $this->assertRaw('<label for="edit-twig-basic">twig_basic</label>'); $this->assertRaw('<textarea data-drupal-selector="edit-twig-basic" disabled="disabled" class="js-webform-codemirror webform-codemirror twig form-textarea resize-vertical" data-webform-codemirror-mode="twig" id="edit-twig-basic" name="twig_basic" rows="5" cols="60"> {% set value = "Hello" %} @@ -101,7 +106,7 @@ public function testCodeMirror() { $this->drupalLogin($this->rootUser); // Check enabled Twig editor. - $this->drupalGet('webform/test_element_codemirror'); + $this->drupalGet('/webform/test_element_codemirror'); $this->assertRaw('<textarea data-drupal-selector="edit-twig-basic" class="js-webform-codemirror webform-codemirror twig form-textarea resize-vertical" data-webform-codemirror-mode="twig" id="edit-twig-basic" name="twig_basic" rows="5" cols="60"> {% set value = "Hello" %} {{ value }} diff --git a/web/modules/webform/src/Tests/Element/WebformElementCompositeTest.php b/web/modules/webform/src/Tests/Element/WebformElementCompositeTest.php index 44909d6a2e..130367d20a 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementCompositeTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementCompositeTest.php @@ -16,12 +16,20 @@ class WebformElementCompositeTest extends WebformElementTestBase { * * @var array */ - protected static $testWebforms = ['test_element_composite']; + protected static $testWebforms = [ + 'test_element_composite', + 'test_element_composite_wrapper', + ]; /** * Test composite (builder). */ public function testComposite() { + + /**************************************************************************/ + // Builder. + /**************************************************************************/ + $webform = Webform::load('test_element_composite'); // Check processing for user who can't edit source. @@ -29,12 +37,12 @@ public function testComposite() { $this->assertRaw("webform_element_composite_basic: first_name: '#type': textfield - '#title': 'First name' '#required': true + '#title': 'First name' last_name: '#type': textfield - '#title': 'Last name' '#required': true + '#title': 'Last name' webform_element_composite_advanced: first_name: '#type': textfield @@ -69,12 +77,12 @@ public function testComposite() { $this->assertRaw("webform_element_composite_basic: first_name: '#type': textfield - '#title': 'First name' '#required': true + '#title': 'First name' last_name: '#type': textfield - '#title': 'Last name' '#required': true + '#title': 'Last name' webform_element_composite_advanced: first_name: '#type': textfield @@ -102,6 +110,38 @@ public function testComposite() { '#field_suffix': ' yrs. old' '#min': 1 '#max': 125"); + + /**************************************************************************/ + // Wrapper. + /**************************************************************************/ + + $this->drupalGet('/webform/test_element_composite_wrapper'); + + // Check fieldset wrapper. + $this->assertRaw('<fieldset data-drupal-selector="edit-radios-wrapper-fieldset" id="edit-radios-wrapper-fieldset--wrapper" class="radios--wrapper fieldgroup form-composite webform-composite-visible-title required js-webform-type-radios webform-type-radios js-form-item form-item js-form-wrapper form-wrapper">'); + + // Check fieldset wrapper with hidden title. + $this->assertRaw('<fieldset data-drupal-selector="edit-radios-wrapper-fieldset-hidden-title" id="edit-radios-wrapper-fieldset-hidden-title--wrapper" class="radios--wrapper fieldgroup form-composite webform-composite-hidden-title required js-webform-type-radios webform-type-radios js-form-item form-item js-form-wrapper form-wrapper">'); + + // Check form element wrapper. + $this->assertRaw('<div class="js-form-item form-item js-form-type-radios form-type-radios js-form-item-radios-wrapper-form-element form-item-radios-wrapper-form-element">'); + + // Check container wrapper. + $this->assertRaw('<div data-drupal-selector="edit-radios-wrapper-container" id="edit-radios-wrapper-container--wrapper" class="radios--wrapper fieldgroup form-composite js-form-wrapper form-wrapper">'); + + // Check radios 'aria-describedby' with wrapper description. + $this->assertRaw('<input data-drupal-selector="edit-radios-wrapper-fieldset-description-one" aria-describedby="edit-radios-wrapper-fieldset-description--wrapper--description" type="radio" id="edit-radios-wrapper-fieldset-description-one" name="radios_wrapper_fieldset_description" value="One" class="form-radio" />'); + $this->assertRaw('<div class="description"><div id="edit-radios-wrapper-fieldset-description--wrapper--description" class="webform-element-description">This is a description</div>'); + + // Check wrapper with #states. + $this->assertRaw('<fieldset data-drupal-selector="edit-states-fieldset" id="edit-states-fieldset--wrapper" class="radios--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-radios webform-type-radios js-form-item form-item js-form-wrapper form-wrapper" data-drupal-states="{"visible":{".webform-submission-test-element-composite-wrapper-add-form :input[name=\u0022states_checkbox\u0022]":{"checked":true}}}">'); + $this->assertRaw('<div data-drupal-states="{"visible":{".webform-submission-test-element-composite-wrapper-add-form :input[name=\u0022states_checkbox\u0022]":{"checked":true}}}" class="js-form-item form-item js-form-type-radios form-type-radios js-form-item-states-form-item form-item-states-form-item">'); + $this->assertRaw('<div data-drupal-selector="edit-states-container" id="edit-states-container--wrapper" class="radios--wrapper fieldgroup form-composite js-form-wrapper form-wrapper" data-drupal-states="{"visible":{".webform-submission-test-element-composite-wrapper-add-form :input[name=\u0022states_checkbox\u0022]":{"checked":true}}}">'); + + // Below tests are only failing on Drupal.org and pass locally. + // Check radios 'aria-describedby' with individual descriptions. + // $this->assertRaw('<input data-drupal-selector="edit-radios-wrapper-fieldset-element-descriptions-one" aria-describedby="edit-radios-wrapper-fieldset-element-descriptions-one--description" type="radio" id="edit-radios-wrapper-fieldset-element-descriptions-one" name="radios_wrapper_fieldset_element_descriptions" value="One" class="form-radio" />'); + // $this->assertRaw('<div id="edit-radios-wrapper-fieldset-element-descriptions-one--description" class="webform-element-description">This is a radio description</div>'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementComputedTest.php b/web/modules/webform/src/Tests/Element/WebformElementComputedTest.php index 36ccb88fd7..62fce7514d 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementComputedTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementComputedTest.php @@ -24,7 +24,11 @@ class WebformElementComputedTest extends WebformElementTestBase { * * @var array */ - protected static $testWebforms = ['test_element_computed_token', 'test_element_computed_twig']; + protected static $testWebforms = [ + 'test_element_computed_token', + 'test_element_computed_twig', + 'test_element_computed_ajax', + ]; /** * {@inheritdoc} @@ -64,7 +68,7 @@ public function testComputedElement() { $this->assertRaw('<b class="webform_computed_token_html">xss:</b> <script>alert("XSS");</script><br />'); // Check token plain text rendering. - $this->assertRaw('<div id="test_element_computed_token--webform_computed_token_text" class="webform-element webform-element-type-webform-computed-token js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-text form-item-webform-computed-token-text">'); + $this->assertRaw('<div class="webform-element webform-element-type-webform-computed-token js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-text form-item-webform-computed-token-text" id="test_element_computed_token--webform_computed_token_text">'); $this->assertRaw('<label>webform_computed_token_text</label>'); $this->assertRaw('simple string: This is a string<br />'); $this->assertRaw('complex string : This is a <strong>complex</strong> string, which contains "double" and 'single' quotes with special characters like >, <, ><, and <>.<br />'); @@ -76,7 +80,8 @@ public function testComputedElement() { $data = $webform_submission->getData(); // Check value stored in the database. - $this->assertEqual($data['webform_computed_token_store'], 'This is a string'); + $this->debug($data['webform_computed_token_store']); + $this->assertEqual($data['webform_computed_token_store'], "sid: $sid"); // Check values not stored in the database. $this->assert(!isset($data['webform_computed_token_auto'])); @@ -85,6 +90,15 @@ public function testComputedElement() { /* Twig */ + // Get computed Twig form. + $this->drupalGet('/webform/test_element_computed_twig'); + + // Check Twig trim. + $this->assertFieldByName('webform_computed_twig_trim', '<em>This is trimmed</em> <br/>'); + + // Check Twig spaceless. + $this->assertFieldByName('webform_computed_twig_spaceless', '<em>This is spaceless</em><br/>'); + // Get computed Twig preview. $this->drupalPostForm('webform/test_element_computed_twig', [], t('Preview')); @@ -114,6 +128,54 @@ public function testComputedElement() { $this->assertRaw('<b class="webform_computed_twig_data">complex string:</b> This is a <strong>complex</strong> string, which contains "double" and 'single' quotes with special characters like >, <, ><, and <>.<br />'); $this->assertRaw('<b class="webform_computed_twig_data">text_format:</b> <p>This is a <strong>text format</strong> string.</p>'); $this->assertRaw('<b class="webform_computed_twig_data">xss:</b> <script>alert("XSS");</script><br />'); + + /* Ajax */ + + // Get computed ajax form. + $this->drupalGet('/webform/test_element_computed_ajax'); + + // Check that a and b are hidden via #hide_empty. + $this->assertRaw('<div style="display:none" class="js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-a form-item-webform-computed-token-a">'); + $this->assertRaw('<div style="display:none" class="js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-b form-item-webform-computed-token-b">'); + + // Check a, b, computed default values. + $this->assertFieldByName('webform_computed_token_a', ''); + $this->assertFieldByName('webform_computed_token_b', ''); + $this->assertFieldByName('webform_computed_twig', 'Please enter a value for a and b.'); + $this->assertFieldByName('webform_computed_twig_token', 'Please enter a value for a and b.'); + + // Calculate 2 + 4 = 6. + $edit = ['a[select]' => 2, 'b' => 4]; + + // Check a is updated. + $this->drupalPostAjaxForm(NULL, $edit, 'webform-computed-webform_computed_token_a-button'); + $this->assertNoRaw('<div style="display:none" class="js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-a form-item-webform-computed-token-a">'); + $this->assertFieldByName('webform_computed_token_a', '2'); + $this->assertFieldByName('webform_computed_token_b', ''); + $this->assertFieldByName('webform_computed_twig', 'Please enter a value for a and b.'); + $this->assertFieldByName('webform_computed_twig_token', 'Please enter a value for a and b.'); + + // Check b is updated. + $this->drupalPostAjaxForm(NULL, $edit, 'webform-computed-webform_computed_token_b-button'); + $this->assertNoRaw('<div style="display:none" class="js-form-item form-item js-form-type-item form-type-item js-form-item-webform-computed-token-b form-item-webform-computed-token-b">'); + $this->assertFieldByName('webform_computed_token_a', '2'); + $this->assertFieldByName('webform_computed_token_b', '4'); + $this->assertFieldByName('webform_computed_twig', 'Please enter a value for a and b.'); + $this->assertFieldByName('webform_computed_twig_token', 'Please enter a value for a and b.'); + + // Check twig is updated. + $this->drupalPostAjaxForm(NULL, $edit, 'webform-computed-webform_computed_twig-button'); + $this->assertFieldByName('webform_computed_token_a', '2'); + $this->assertFieldByName('webform_computed_token_b', '4'); + $this->assertFieldByName('webform_computed_twig', '2 + 4 = 6'); + $this->assertFieldByName('webform_computed_twig_token', 'Please enter a value for a and b.'); + + // Check twig with token is updated. + $this->drupalPostAjaxForm(NULL, $edit, 'webform-computed-webform_computed_twig_token-button'); + $this->assertFieldByName('webform_computed_token_a', '2'); + $this->assertFieldByName('webform_computed_token_b', '4'); + $this->assertFieldByName('webform_computed_twig', '2 + 4 = 6'); + $this->assertFieldByName('webform_computed_twig_token', '2 + 4 = 6'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementCounterTest.php b/web/modules/webform/src/Tests/Element/WebformElementCounterTest.php new file mode 100644 index 0000000000..bd0a0aec05 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementCounterTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for webform (text) counter. + * + * @group Webform + */ +class WebformElementCounterTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_counter']; + + /** + * Tests text elements. + */ + public function testCounter() { + + // Check counters. + $this->drupalGet('/webform/test_element_counter'); + $this->assertRaw('<input data-counter-type="character" data-counter-minimum="5" data-counter-minimum-message="%d character(s) entered. This is custom text" class="js-webform-counter webform-counter form-text" data-drupal-selector="edit-counter-characters-min-message" type="text" id="edit-counter-characters-min-message" name="counter_characters_min_message" value="" size="60" maxlength="255" />'); + $this->assertRaw('<input data-counter-type="character" data-counter-maximum="10" data-counter-maximum-message="%d character(s) remaining. This is custom text" class="js-webform-counter webform-counter form-text" data-drupal-selector="edit-counter-characters-max-message" type="text" id="edit-counter-characters-max-message" name="counter_characters_max_message" value="" size="60" maxlength="10" />'); + $this->assertRaw('<textarea data-counter-type="word" data-counter-minimum="5" data-counter-minimum-message="%d word(s) entered. This is custom text" class="js-webform-counter webform-counter form-textarea resize-vertical" data-drupal-selector="edit-counter-words-min-message" id="edit-counter-words-min-message" name="counter_words_min_message" rows="5" cols="60"></textarea>'); + $this->assertRaw('<textarea data-counter-type="word" data-counter-maximum="10" data-counter-maximum-message="%d character(s) remaining. This is custom text" class="js-webform-counter webform-counter form-textarea resize-vertical" data-drupal-selector="edit-counter-words-max-message" id="edit-counter-words-max-message" name="counter_words_max_message" rows="5" cols="60"></textarea>'); + + // Check counter min/max validation error (min: 5 / max: 10). + $edit = [ + 'counter_characters_min' => '123', + 'counter_characters_max' => '1234567890xxx', + 'counter_words_min' => 'one two three', + 'counter_words_max' => 'one two three four five six seven eight nine ten eleven', + ]; + $this->drupalPostForm('webform/test_element_counter', $edit, t('Submit')); + $this->assertRaw('counter_characters_min (5) must be longer than <em class="placeholder">5</em> characters but is currently <em class="placeholder">3</em> characters long.'); + $this->assertRaw('counter_characters_max (10) cannot be longer than <em class="placeholder">10</em> characters but is currently <em class="placeholder">13</em> characters long.'); + $this->assertRaw('counter_words_min (5) must be longer than <em class="placeholder">5</em> words but is currently <em class="placeholder">3</em> words long.'); + $this->assertRaw('counter_words_max (10) cannot be longer than <em class="placeholder">10</em> words but is currently <em class="placeholder">11</em> words long.'); + + // Check counter validation passes (min: 5 / max: 10). + $edit = [ + 'counter_characters_min' => '12345', + 'counter_characters_max' => '1234567890', + 'counter_words_min' => 'one two three four five', + 'counter_words_max' => 'one two three four five six seven eight nine ten', + ]; + $this->drupalPostForm('webform/test_element_counter', $edit, t('Submit')); + $this->assertNoRaw('counter_characters_min (5) must be longer than <em class="placeholder">5</em> characters'); + $this->assertNoRaw('counter_characters_max (10) cannot be longer than <em class="placeholder">10</em> characters'); + $this->assertNoRaw('counter_words_min (5) must be longer than <em class="placeholder">5</em> words'); + $this->assertNoRaw('counter_words_max (10) cannot be longer than <em class="placeholder">10</em> words'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php b/web/modules/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php index c9098976fb..df98980866 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php @@ -36,7 +36,7 @@ public function testCustomProperties() { $name_element = [ '#type' => 'textfield', '#title' => 'Your Name', - '#default_value' => '[webform-authenticated-user:display-name]', + '#default_value' => '[current-user:display-name]', '#required' => TRUE, ]; @@ -45,7 +45,7 @@ public function testCustomProperties() { $this->assertEqual($webform->getElementDecoded('name'), $name_element); // Check that name input does not contain custom data. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('<input data-drupal-selector="edit-name" type="text" id="edit-name" name="name" value="' . htmlentities($admin_user->label()) . '" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); // Submit empty custom property and data. @@ -79,7 +79,7 @@ public function testCustomProperties() { $this->assertEqual($webform->getElementDecoded('name'), $name_element); // Check that name input does contain custom data. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('<input data-custom="custom-data" data-drupal-selector="edit-name" type="text" id="edit-name" name="name" value="' . htmlentities($admin_user->label()) . '" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementDateListTest.php b/web/modules/webform/src/Tests/Element/WebformElementDateListTest.php index 29bd1c3f39..b398662d8d 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementDateListTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementDateListTest.php @@ -25,19 +25,42 @@ class WebformElementDateListTest extends WebformElementTestBase { public function testDateListElement() { $webform = Webform::load('test_element_datelist'); + // Check posted submission values. + $this->postSubmission($webform); + $this->assertRaw("datelist_default: '2009-08-18T16:00:00+1000' +datelist_no_abbreviate: '2009-08-18T16:00:00+1000' +datelist_text_parts: '2009-08-18T16:00:00+1000' +datelist_datetime: '2009-08-18T16:00:00+1000' +datelist_date: '2009-08-18T00:00:00+1000' +datelist_min_max: '2009-08-18T00:00:00+1000' +datelist_min_max_time: '2009-01-01T09:00:00+1100' +datelist_date_year_range_reverse: '' +datelist_required_error: '2009-08-18T16:00:00+1000' +datelist_multiple: + - '2009-08-18T16:00:00+1000' +datelist_custom_composite: + - datelist: '2009-08-18T16:00:00+1000'"); + + $this->drupalGet('/webform/test_element_datelist'); + + // Check datelist label has not for attributes. + $this->assertRaw('<label>datelist_default</label>'); + // Check '#format' values. - $this->drupalGet('webform/test_element_datelist'); $this->assertFieldByName('datelist_default[month]', '8'); + // Check '#date_abbreviate': false. + $this->assertRaw('<select data-drupal-selector="edit-datelist-no-abbreviate-month" title="Month" id="edit-datelist-no-abbreviate-month" name="datelist_no_abbreviate[month]" class="form-select"><option value="">Month</option><option value="1">January</option>'); + // Check date year range reverse. - $this->drupalGet('webform/test_element_datelist'); + $this->drupalGet('/webform/test_element_datelist'); $this->assertRaw('<select data-drupal-selector="edit-datelist-date-year-range-reverse-year" title="Year" id="edit-datelist-date-year-range-reverse-year" name="datelist_date_year_range_reverse[year]" class="form-select"><option value="" selected="selected">Year</option><option value="2010">2010</option><option value="2009">2009</option><option value="2008">2008</option><option value="2007">2007</option><option value="2006">2006</option><option value="2005">2005</option></select>'); // Check 'datelist' and 'datetime' #default_value. $form = $webform->getSubmissionForm(); $this->assert($form['elements']['datelist_default']['#default_value'] instanceof DrupalDateTime, 'datelist_default #default_value instance of \Drupal\Core\Datetime\DrupalDateTime.'); - // Check datelist #max validation. + // Check datelist #date_date_max validation. $edit = [ 'datelist_min_max[year]' => '2010', 'datelist_min_max[month]' => '8', @@ -46,7 +69,7 @@ public function testDateListElement() { $this->drupalPostForm('webform/test_element_datelist', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">datelist_min_max</em> must be on or before <em class="placeholder">2009-12-31</em>.'); - // Check datelist #min validation. + // Check datelist #date_date_min validation. $edit = [ 'datelist_min_max[year]' => '2006', 'datelist_min_max[month]' => '8', @@ -55,6 +78,26 @@ public function testDateListElement() { $this->drupalPostForm('webform/test_element_datelist', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">datelist_min_max</em> must be on or after <em class="placeholder">2009-01-01</em>.'); + // Check datelist #date_max validation. + $edit = [ + 'datelist_min_max_time[year]' => '2009', + 'datelist_min_max_time[month]' => '12', + 'datelist_min_max_time[day]' => '31', + 'datelist_min_max_time[hour]' => '18', + ]; + $this->drupalPostForm('webform/test_element_datelist', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datelist_min_max_time</em> must be on or before <em class="placeholder">2009-12-31 17:00:00</em>.'); + + // Check datelist #date_min validation. + $edit = [ + 'datelist_min_max_time[year]' => '2009', + 'datelist_min_max_time[month]' => '1', + 'datelist_min_max_time[day]' => '1', + 'datelist_min_max_time[hour]' => '8', + ]; + $this->drupalPostForm('webform/test_element_datelist', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datelist_min_max_time</em> must be on or after <em class="placeholder">2009-01-01 09:00:00</em>.'); + // Check custom required error. $edit = [ 'datelist_required_error[year]' => '', diff --git a/web/modules/webform/src/Tests/Element/WebformElementDateTest.php b/web/modules/webform/src/Tests/Element/WebformElementDateTest.php index 77524757b5..aabdc45187 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementDateTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementDateTest.php @@ -28,7 +28,7 @@ public function testDateElement() { // Render date elements. /**************************************************************************/ - $this->drupalGet('webform/test_element_date'); + $this->drupalGet('/webform/test_element_date'); // Check '#format' values. $this->assertFieldByName('date_default', '2009-08-18'); @@ -56,7 +56,7 @@ public function testDateElement() { $this->assertRaw('<em class="placeholder">date_min_max</em> must be on or after <em class="placeholder">2009-01-01</em>.'); // Check dynamic date. - $this->drupalGet('webform/test_element_date'); + $this->drupalGet('/webform/test_element_date'); $min = \Drupal::service('date.formatter')->format(strtotime('-1 year'), 'html_date'); $min_year = date('Y', strtotime('-1 year')); $max = \Drupal::service('date.formatter')->format(strtotime('+1 year'), 'html_date'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementDateTimeTest.php b/web/modules/webform/src/Tests/Element/WebformElementDateTimeTest.php index fa980de317..d8cf2e82e9 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementDateTimeTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementDateTimeTest.php @@ -25,7 +25,19 @@ class WebformElementDateTimeTest extends WebformElementTestBase { */ public function testDateTime() { $webform = Webform::load('test_element_datetime'); - $this->drupalGet('webform/test_element_datetime'); + + // Check posted submission values. + $this->postSubmission($webform); + $this->assertRaw("datetime_default: '2009-08-18T16:00:00+1000'"); + $this->assertRaw("datetime_multiple: + - '2009-08-18T16:00:00+1000'"); + $this->assertRaw("datetime_custom_composite: + - datetime: '2009-08-18T16:00:00+1000'"); + + $this->drupalGet('/webform/test_element_datetime'); + + // Check datetime label has not for attributes. + $this->assertRaw('<label>datetime_default</label>'); // Check '#format' values. $this->assertFieldByName('datetime_default[date]', '2009-08-18'); @@ -36,28 +48,50 @@ public function testDateTime() { $this->assertRaw('<input data-drupal-selector="edit-datetime-datepicker-timepicker-date" title="Date (e.g. ' . $now_date . ')" type="text" min="Mon, 01/01/1900" max="Sat, 12/31/2050" data-drupal-date-format="D, m/d/Y" id="edit-datetime-datepicker-timepicker-date" name="datetime_datepicker_timepicker[date]" value="Tue, 08/18/2009" size="15" maxlength="128" class="form-text" />'); $this->assertRaw('<input data-drupal-selector="edit-datetime-datepicker-timepicker-time"'); // Skip time which can change during the tests. - $this->assertRaw('type="text" step="1" data-webform-time-format="g:i A" id="edit-datetime-datepicker-timepicker-time" name="datetime_datepicker_timepicker[time]" value="4:00 PM" size="12" maxlength="128" class="form-text" />'); + $this->assertRaw('type="text" step="1" data-webform-time-format="g:i A" id="edit-datetime-datepicker-timepicker-time" name="datetime_datepicker_timepicker[time]" value="4:00 PM" size="12" maxlength="12" class="form-time webform-time" />'); // Check time with custom min/max/step attributes. $this->assertRaw('<input min="2009-01-01" data-min-year="2009" max="2009-12-31" data-max-year="2009" data-drupal-selector="edit-datetime-time-min-max-date"'); - $this->assertRaw('<input min="09:00:00" data-min-year="2009" max="17:00:00" data-max-year="2009" data-drupal-selector="edit-datetime-time-min-max-time"'); + $this->assertRaw('<input min="09:00:00" max="17:00:00" data-drupal-selector="edit-datetime-time-min-max-time"'); $this->assertRaw('<input min="Thu, 01/01/2009" data-min-year="2009" max="Thu, 12/31/2009" data-max-year="2009" data-drupal-selector="edit-datetime-datepicker-timepicker-time-min-max-date"'); - $this->assertRaw('<input min="09:00:00" data-min-year="2009" max="17:00:00" data-max-year="2009" data-drupal-selector="edit-datetime-datepicker-timepicker-time-min-max-time"'); + $this->assertRaw('<input min="09:00:00" max="17:00:00" data-drupal-selector="edit-datetime-datepicker-timepicker-time-min-max-time"'); // Check 'datelist' and 'datetime' #default_value. $form = $webform->getSubmissionForm(); $this->assert($form['elements']['datetime_default']['#default_value'] instanceof DrupalDateTime, 'datetime_default #default_value instance of \Drupal\Core\Datetime\DrupalDateTime.'); - // Check datetime #max validation. + // Check datetime #date_date_max validation. $edit = ['datetime_min_max[date]' => '2010-08-18']; $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">datetime_min_max</em> must be on or before <em class="placeholder">2009-12-31</em>.'); - // Check datetime #min validation. + // Check datetime #date_date_min validation. $edit = ['datetime_min_max[date]' => '2006-08-18']; $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">datetime_min_max</em> must be on or after <em class="placeholder">2009-01-01</em>.'); + // Check datetime #date_max date validation. + $edit = ['datetime_min_max_time[date]' => '2009-12-31', 'datetime_min_max_time[time]' => '19:00:00']; + $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datetime_min_max_time</em> must be on or before <em class="placeholder">2009-12-31 17:00:00</em>.'); + + // Check datetime #date_min date validation. + $edit = ['datetime_min_max_time[date]' => '2009-01-01', 'datetime_min_max_time[time]' => '08:00:00']; + $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datetime_min_max_time</em> must be on or after <em class="placeholder">2009-01-01 09:00:00</em>.'); + + // Check datetime #date_max time validation. + $edit = ['datetime_min_max_time[time]' => '01:00:00']; + $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datetime_min_max_time: Time</em> must be on or after <em class="placeholder">09:00:00</em>.'); + $this->assertRaw('<em class="placeholder">datetime_min_max_time</em> must be on or after <em class="placeholder">2009-01-01 09:00:00</em>.'); + + // Check datetime #date_min time validation. + $edit = ['datetime_min_max_time[time]' => '01:00:00']; + $this->drupalPostForm('webform/test_element_datetime', $edit, t('Submit')); + $this->assertRaw('<em class="placeholder">datetime_min_max_time: Time</em> must be on or after <em class="placeholder">09:00:00</em>.'); + $this->assertRaw('<em class="placeholder">datetime_min_max_time</em> must be on or after <em class="placeholder">2009-01-01 09:00:00</em>.'); + // Check: Issue #2723159: Datetime form element cannot validate when using a // format without seconds. $sid = $this->postSubmission($webform); diff --git a/web/modules/webform/src/Tests/Element/WebformElementDetailsTest.php b/web/modules/webform/src/Tests/Element/WebformElementDetailsTest.php new file mode 100644 index 0000000000..ea4967b2ea --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementDetailsTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for details element. + * + * @group Webform + */ +class WebformElementDetailsTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_details']; + + /** + * Test details element. + */ + public function testDetails() { + $this->drupalGet('/webform/test_element_details'); + + // Check details with help, field prefix, field suffix, description, + // and more. Also, check that invalid 'required' and 'aria-required' + // attributes are removed. + $this->assertRaw('<details data-webform-key="details" data-drupal-selector="edit-details" aria-describedby="edit-details--description" id="edit-details" class="js-form-wrapper form-wrapper required" open="open">'); + $this->assertRaw('<summary role="button" aria-controls="edit-details" aria-expanded="true" aria-pressed="true" class="js-form-required form-required">details<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">details</div><div class="webform-element-help--content">This is help text.</div>"><span aria-hidden="true">?</span>'); + $this->assertRaw('<div class="details-description"><div id="edit-details--description" class="webform-element-description">This is a description.</div>'); + $this->assertRaw('<div id="edit-details--more" class="js-webform-element-more webform-element-more">'); + + // Check details title_display: invisible. + $this->assertRaw('<summary role="button" aria-controls="edit-details-title-invisible" aria-expanded="false" aria-pressed="false"><span class="visually-hidden">Details title invisible</span></summary>'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementEmailTest.php b/web/modules/webform/src/Tests/Element/WebformElementEmailTest.php index 3ae864ea75..be3b22db27 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementEmailTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementEmailTest.php @@ -26,7 +26,7 @@ public function testEmail() { /**************************************************************************/ // Check basic email multiple. - $this->drupalGet('webform/test_element_email'); + $this->drupalGet('/webform/test_element_email'); $this->assertRaw('<label for="edit-email-multiple-basic">Multiple email addresses (basic)</label>'); $this->assertRaw('<input data-drupal-selector="edit-email-multiple-basic" aria-describedby="edit-email-multiple-basic--description" type="text" id="edit-email-multiple-basic" name="email_multiple_basic" value="" size="60" class="form-text webform-email-multiple" />'); $this->assertRaw('Multiple email addresses may be separated by commas.'); @@ -63,10 +63,11 @@ public function testEmail() { // email_confirm /**************************************************************************/ - $this->drupalGet('webform/test_element_email'); + $this->drupalGet('/webform/test_element_email'); // Check basic email confirm. - $this->assertRaw('<div id="edit-email-confirm-basic" class="js-form-item form-item js-form-type-webform-email-confirm form-type-webform-email-confirm js-form-item-email-confirm-basic form-item-email-confirm-basic form-no-label">'); + $this->assertRaw('<fieldset id="edit-email-confirm-basic--wrapper" class="webform-email-confirm--wrapper fieldgroup form-composite webform-composite-hidden-title js-webform-type-webform-email-confirm webform-type-webform-email-confirm js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<span class="visually-hidden fieldset-legend">Email confirm basic</span>'); $this->assertRaw('<div class="js-form-item form-item js-form-type-email form-type-email js-form-item-email-confirm-basic-mail-1 form-item-email-confirm-basic-mail-1">'); $this->assertRaw('<label for="edit-email-confirm-basic-mail-1">Email confirm basic</label>'); $this->assertRaw('<input data-drupal-selector="edit-email-confirm-basic-mail-1" class="webform-email form-email" type="email" id="edit-email-confirm-basic-mail-1" name="email_confirm_basic[mail_1]" value="" size="60" maxlength="254" />'); @@ -75,7 +76,8 @@ public function testEmail() { $this->assertRaw('<input data-drupal-selector="edit-email-confirm-basic-mail-2" class="webform-email-confirm form-email" type="email" id="edit-email-confirm-basic-mail-2" name="email_confirm_basic[mail_2]" value="" size="60" maxlength="254" />'); // Check advanced email confirm w/ custom label. - $this->assertRaw('<div id="edit-email-confirm-advanced" class="js-form-item form-item js-form-type-webform-email-confirm form-type-webform-email-confirm js-form-item-email-confirm-advanced form-item-email-confirm-advanced form-no-label">'); + $this->assertRaw('<fieldset id="edit-email-confirm-advanced--wrapper" class="webform-email-confirm--wrapper fieldgroup form-composite webform-composite-hidden-title js-webform-type-webform-email-confirm webform-type-webform-email-confirm js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<span class="visually-hidden fieldset-legend">Email confirm advanced</span>'); $this->assertRaw('<div class="js-form-item form-item js-form-type-email form-type-email js-form-item-email-confirm-advanced-mail-1 form-item-email-confirm-advanced-mail-1">'); $this->assertRaw('<label for="edit-email-confirm-advanced-mail-1">Email confirm advanced</label>'); $this->assertRaw('<input data-drupal-selector="edit-email-confirm-advanced-mail-1" aria-describedby="edit-email-confirm-advanced-mail-1--description" class="webform-email form-email" type="email" id="edit-email-confirm-advanced-mail-1" name="email_confirm_advanced[mail_1]" value="" size="60" maxlength="254" placeholder="Enter email address" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementEntityReferenceTest.php b/web/modules/webform/src/Tests/Element/WebformElementEntityReferenceTest.php index 0838ec58a6..16ad63446e 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementEntityReferenceTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementEntityReferenceTest.php @@ -32,7 +32,7 @@ public function testEntityReferenceTest() { $webform = Webform::load('test_element_entity_reference'); // Check render entity_autocomplete. - $this->drupalGet('webform/test_element_entity_reference'); + $this->drupalGet('/webform/test_element_entity_reference'); $this->assertFieldByName('entity_autocomplete_user_default', 'admin (1)'); $this->assertFieldByName('entity_autocomplete_user_tags', 'admin (1)'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementFieldsetTest.php b/web/modules/webform/src/Tests/Element/WebformElementFieldsetTest.php new file mode 100644 index 0000000000..9fdc13992c --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementFieldsetTest.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for fieldset element. + * + * @group Webform + */ +class WebformElementFieldsetTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_fieldset']; + + /** + * Test fieldset element. + */ + public function testFieldset() { + $this->drupalGet('/webform/test_element_fieldset'); + + // Check fieldset with help, field prefix, field suffix, description, + // and more. Also, check that invalid 'required' and 'aria-required' + // attributes are removed. + $this->assertRaw('<fieldset class="webform-has-field-prefix webform-has-field-suffix required js-webform-type-fieldset webform-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" data-drupal-selector="edit-fieldset" aria-describedby="edit-fieldset--description" id="edit-fieldset">'); + $this->assertRaw('<span class="fieldset-legend js-form-required form-required">fieldset<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">fieldset</div><div class="webform-element-help--content">This is help text.</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<span class="field-prefix">prefix</span>'); + $this->assertRaw('<span class="field-suffix">suffix</span>'); + $this->assertRaw('<div class="description">'); + $this->assertRaw('<div id="edit-fieldset--description" class="webform-element-description">This is a description.</div>'); + $this->assertRaw('<div id="edit-fieldset--more" class="js-webform-element-more webform-element-more">'); + + // Check fieldset title_display: invisible. + $this->assertRaw('<span class="visually-hidden fieldset-legend">fieldset_title_invisible</span>'); + + // Check fieldset description_display: before. + $this->assertRaw('<span class="field-prefix">prefix<div id="edit-fieldset-description-before--description" class="webform-element-description">This is a description before.</div>'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementFormatCustomTest.php b/web/modules/webform/src/Tests/Element/WebformElementFormatCustomTest.php index a9962b49db..41947f82f3 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementFormatCustomTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementFormatCustomTest.php @@ -59,6 +59,16 @@ public function testFormatCustom() { $this->assertRaw('<label>textfield_custom</label>'); $this->assertRaw('<em>{textfield_custom}</em>'); + // Check basic custom token HTML format. + $this->assertRaw('<label>textfield_custom_token</label>'); + $this->assertRaw('<em>{textfield_custom_token}</em>'); + + // Check caught exception is displayed to users with update access. + // @see \Drupal\webform\Twig\TwigExtension::renderTwigTemplate + $this->assertRaw('("The "[webform_submission:values:textfield_custom_token_exception]" token is being called recursively.")'); + $this->assertRaw('<label>textfield_custom_token_exception</label>'); + $this->assertRaw('<em>EXCEPTION</em>'); + // Check multiple custom HTML format. $this->assertRaw('<label>textfield_custom</label>'); $this->assertRaw('<table>'); @@ -89,8 +99,19 @@ public function testFormatCustom() { $this->assertRaw('element.postal_code: {postal_code}<br/>'); $this->assertRaw('element.country: {country}<br/>'); + // Check composite multiple custom HTML format. + $this->assertRaw('<label>address_multiple_custom</label>'); + $this->assertRaw('<div>*****</div> +element.address: {02-address}<br/> +element.address_2: {02-address_2}<br/> +element.city: {02-city}<br/> +element.state_province: {02-state_province}<br/> +element.postal_code: {02-postal_code}<br/> +element.country: {02-country}<br/> +<div>*****</div>'); + // Check fieldset displayed as details. - $this->assertRaw('<details data-webform-element-id="test_element_format_custom--fieldset_custom" class="webform-container webform-container-type-details js-form-wrapper form-wrapper" id="test_element_format_custom--fieldset_custom" open="open">'); + $this->assertRaw('<details class="webform-container webform-container-type-details js-form-wrapper form-wrapper" data-webform-element-id="test_element_format_custom--fieldset_custom" id="test_element_format_custom--fieldset_custom" open="open">'); $this->assertRaw('<summary role="button" aria-controls="test_element_format_custom--fieldset_custom" aria-expanded="true" aria-pressed="true">fieldset_custom</summary>'); // Check container custom HTML format. @@ -102,6 +123,8 @@ public function testFormatCustom() { $this->drupalGet("admin/structure/webform/manage/test_element_format_custom/submission/$sid/text"); $this->assertRaw("textfield_custom: /{textfield_custom}/ +textfield_custom_token: /{textfield_custom_token}/ +textfield_custom_token_exception: /EXCEPTION/ textfield_custom: ⦿ One ⦿ Two @@ -126,13 +149,34 @@ public function testFormatCustom() { element.postal_code: {postal_code} element.country: {country} +address_multiple_custom: +***** +element.address: {01-address} +element.address_2: {01-address_2} +element.city: {01-city} +element.state_province: {01-state_province} +element.postal_code: {01-postal_code} +element.country: {01-country} +***** +***** +element.address: {02-address} +element.address_2: {02-address_2} +element.city: {02-city} +element.state_province: {02-state_province} +element.postal_code: {02-postal_code} +element.country: {02-country} +***** + + fieldset_custom --------------- fieldset_custom_textfield: {fieldset_custom_textfield} fieldset_custom_children ------------------------ -fieldset_custom_children_textfield: {fieldset_custom_children_textfield}"); +fieldset_custom_children_textfield: {fieldset_custom_children_textfield} + +"); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementFormatTest.php b/web/modules/webform/src/Tests/Element/WebformElementFormatTest.php index 774d821af8..3a6be5b905 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementFormatTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementFormatTest.php @@ -79,11 +79,11 @@ public function testFormat() { // 'Entity autocomplete (Label (ID))' => 'admin (1)', ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<b>' . $label . '</b><br />' . $value, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<b>' . $label . '</b><br />' . $value, $body, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); } // Check code format. - $this->assertContains($body, '<pre class="js-webform-codemirror-runmode webform-codemirror-runmode" data-webform-codemirror-mode="text/x-yaml">message: \'Hello World\'</pre>'); + $this->assertContains('<pre class="js-webform-codemirror-runmode webform-codemirror-runmode" data-webform-codemirror-mode="text/x-yaml">message: \'Hello World\'</pre>', $body); // Check elements formatted as text. $body = $this->getMessageBody($submission, 'email_text'); @@ -110,7 +110,7 @@ public function testFormat() { 'Time (Raw value): 09:00:00', ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } /**************************************************************************/ @@ -136,7 +136,7 @@ public function testFormat() { 'File (URL)' => $this->getSubmissionFileUrl($submission, 'managed_file_url'), ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<b>' . $label . '</b><br />' . $value, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<b>' . $label . '</b><br />' . $value, $body, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); } // Check managed file element formatted as text. @@ -145,13 +145,13 @@ public function testFormat() { 'File (Value): ' . $this->getSubmissionFileUrl($submission, 'managed_file_value'), 'File (Raw value): ' . $this->getSubmissionFileUrl($submission, 'managed_file_raw'), 'File (File): ' . $this->getSubmissionFileUrl($submission, 'managed_file_file'), - 'File (Link): ' . $this->getSubmissionFileUrl($submission, 'managed_file_link'), + 'File (Link): ' . $this->getSubmissionFileUrl($submission, 'managed_file_link'), 'File (File ID): ' . $submission->getElementData('managed_file_id'), 'File (File name): managed_file_name.txt', 'File (URL): ' . $this->getSubmissionFileUrl($submission, 'managed_file_url'), ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } /**************************************************************************/ @@ -178,7 +178,10 @@ public function testFormat() { 'Checkboxes (Unordered list)' => '<div class="item-list"><ul><li>One</li><li>Two</li><li>Three</li></ul>', ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<b>' . $label . '</b><br />' . $value, new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<b>' . $label . '</b><br />' . $value, $body, new FormattableMarkup('Found @label: @value', [ + '@label' => $label, + '@value' => $value, + ])); } // Check elements formatted as text. @@ -208,7 +211,7 @@ public function testFormat() { - Three', ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } /**************************************************************************/ @@ -232,7 +235,10 @@ public function testFormat() { 'raw:' => '1, 2, 3', ]; foreach ($elements as $label => $value) { - $this->assertContains($body, '<h3>' . $label . '</h3>' . $value . '<hr />', new FormattableMarkup('Found @label: @value', ['@label' => $label, '@value' => $value])); + $this->assertContains('<h3>' . $label . '</h3>' . $value . '<hr />', $body, new FormattableMarkup('Found @label: @value', [ + '@label' => $label, + '@value' => $value, + ])); } // Check elements tokens formatted as text. @@ -247,7 +253,7 @@ public function testFormat() { "raw:\n1, 2, 3", ]; foreach ($elements as $value) { - $this->assertContains($body, $value, new FormattableMarkup('Found @value', ['@value' => $value])); + $this->assertContains($value, $body, new FormattableMarkup('Found @value', ['@value' => $value])); } // Check element default format item global setting. @@ -255,14 +261,14 @@ public function testFormat() { ->set('format.checkboxes.item', 'raw') ->save(); $body = $this->getMessageBody($webform_format_token_submission, 'email_text'); - $this->assertContains($body, "default:\n1, 2, 3"); + $this->assertContains("default:\n1, 2, 3", $body); // Check element default format items global setting. \Drupal::configFactory()->getEditable('webform.settings') ->set('format.checkboxes.items', 'and') ->save(); $body = $this->getMessageBody($webform_format_token_submission, 'email_text'); - $this->assertContains($body, "default:\n1, 2, and 3"); + $this->assertContains("default:\n1, 2, and 3", $body); } /** diff --git a/web/modules/webform/src/Tests/Element/WebformElementHelpTest.php b/web/modules/webform/src/Tests/Element/WebformElementHelpTest.php index c13532788a..20e183f5db 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementHelpTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementHelpTest.php @@ -20,32 +20,38 @@ class WebformElementHelpTest extends WebformElementTestBase { * Test element help. */ public function testHelp() { - $this->drupalGet('webform/test_element_help'); + $this->drupalGet('/webform/test_element_help'); // Check basic help. - $this->assertRaw('<a href="#help" title="{This is an example of help}" data-webform-help="{This is an example of help}" class="webform-element-help">?</a>'); + $this->assertRaw('<label for="edit-help">help<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help</div><div class="webform-element-help--content">{This is an example of help}</div>"><span aria-hidden="true">?</span></span>'); + + // Check help with required. + $this->assertRaw('<label for="edit-help-required" class="js-form-required form-required">help_required<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_required</div><div class="webform-element-help--content">{This is an example of help for a required element}</div>"><span aria-hidden="true">?</span></span>'); + + // Check help with custom title. + $this->assertRaw('<label for="edit-help-title">help_title<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">{Help custom title}</div><div class="webform-element-help--content">{This is an example of help with a custom help title}</div>"><span aria-hidden="true">?</span></span>'); // Check help with HTML markup. - $this->assertRaw('<a href="#help" title="{This is an example of help with HTML markup}" data-webform-help="{This is an example of help with <b>HTML markup</b>}" class="webform-element-help">?</a>'); + $this->assertRaw('<label for="edit-help-html">help_html<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_html</div><div class="webform-element-help--content">{This is an example of help with <b>HTML markup</b>}</div>"><span aria-hidden="true">?</span></span>'); // Check help with XSS. - $this->assertRaw('<a href="#help" title="{This is an example of help with XSS alert("XSS")}" data-webform-help="{This is an example of help with <b>XSS alert("XSS")</b>}" class="webform-element-help">?</a>'); + $this->assertRaw('<label for="edit-help-xss">help_xss<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_xss</div><div class="webform-element-help--content">{This is an example of help with <b>XSS alert("XSS")</b>}</div>"><span aria-hidden="true">?</span></span>'); // Check help with inline title. - $this->assertRaw('<a href="#help" title="{This is an example of help with an inline title}" data-webform-help="{This is an example of help with an inline title}" class="webform-element-help">?</a> -help_inline</label>'); + $this->assertRaw('<label for="edit-help-checkbox" class="option">help_checkbox<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_checkbox</div><div class="webform-element-help--content">{This is an example of help}</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<label for="edit-help-inline"><span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_inline</div><div class="webform-element-help--content">{This is an example of help with an inline title}</div>"><span aria-hidden="true">?</span></span>'); // Check radios (fieldset). - $this->assertRaw('<a href="#help" title="{This is an example of help for radio buttons}" data-webform-help="{This is an example of help for radio buttons}" class="webform-element-help">?</a>'); + $this->assertRaw('<span class="fieldset-legend">help_radios<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_radios</div><div class="webform-element-help--content">{This is an example of help for radio buttons}</div>"><span aria-hidden="true">?</span></span>'); // Check fieldset. - $this->assertRaw('<a href="#help" title="{This is an example of help for a fieldset}" data-webform-help="{This is an example of help for a fieldset}" class="webform-element-help">?</a>'); + $this->assertRaw('<span class="fieldset-legend">help_radios<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_radios</div><div class="webform-element-help--content">{This is an example of help for radio buttons}</div>"><span aria-hidden="true">?</span></span>'); // Check details. - $this->assertRaw('<a href="#help" title="{This is an example of help for a details element}" data-webform-help="{This is an example of help for a details element}" class="webform-element-help">?</a>'); + $this->assertRaw('<summary role="button" aria-controls="edit-help-details" aria-expanded="false" aria-pressed="false">help_details<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_details</div><div class="webform-element-help--content">{This is an example of help for a details element}</div>"><span aria-hidden="true">?</span>'); // Check section. - $this->assertRaw('<a href="#help" title="{This is an example of help for a section element}" data-webform-help="{This is an example of help for a section element}" class="webform-element-help">?</a>'); + $this->assertRaw('<h2 class="webform-section-title">help_section<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">help_section</div><div class="webform-element-help--content">{This is an example of help for a section element}</div>"><span aria-hidden="true">?</span></span>'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementHorizontalRuleTest.php b/web/modules/webform/src/Tests/Element/WebformElementHorizontalRuleTest.php index b767394eec..75051d3db0 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementHorizontalRuleTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementHorizontalRuleTest.php @@ -20,7 +20,7 @@ class WebformElementHorizontalRuleTest extends WebformElementTestBase { * Test horizontal rule element. */ public function testHorizontalRule() { - $this->drupalGet('webform/test_element_horizontal_rule'); + $this->drupalGet('/webform/test_element_horizontal_rule'); // Check rendering. $this->assertRaw('<hr data-drupal-selector="edit-horizontal-rule" id="edit-horizontal-rule" class="webform-horizontal-rule" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementHtmlEditorTest.php b/web/modules/webform/src/Tests/Element/WebformElementHtmlEditorTest.php index 3ee8e98667..61d40dab39 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementHtmlEditorTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementHtmlEditorTest.php @@ -42,6 +42,8 @@ public function setUp() { public function testHtmlEditor() { $this->drupalLogin($this->rootUser); + /* Element text format */ + $webform = Webform::load('test_element_html_editor'); // Check required validation. @@ -56,11 +58,14 @@ public function testHtmlEditor() { $this->assertRaw('webform_html_editor (disable) field is required.'); $this->assertRaw('webform_html_editor (format) field is required.'); $this->assertRaw('webform_html_editor_codemirror (none) field is required.'); - - $this->drupalGet('webform/test_element_html_editor'); + + $this->drupalGet('/webform/test_element_html_editor'); // Check that HTML editor is enabled. - $this->assertRaw('<textarea class="js-html-editor form-textarea required resize-vertical" data-drupal-selector="edit-webform-html-editor-value" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60" required="required" aria-required="true">Hello <b>World!!!</b></textarea>'); + $this->assertRaw('<textarea data-drupal-selector="edit-webform-html-editor-value" class="js-html-editor form-textarea required resize-vertical" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60" required="required" aria-required="true">Hello <b>World!!!</b></textarea>'); + + // Check that HTML editor is disabled. + $this->assertRaw('<textarea class="custom-disabled js-html-editor form-textarea required resize-vertical" data-drupal-selector="edit-webform-html-editor-disable-value" id="edit-webform-html-editor-disable-value" name="webform_html_editor_disable[value]" rows="5" cols="60" required="required" aria-required="true">Hello <b>World!!!</b></textarea>'); // Check that CodeMirror is displayed when #format: FALSE. $this->assertRaw('<textarea data-drupal-selector="edit-webform-html-editor-codemirror-value" class="js-webform-codemirror webform-codemirror html required form-textarea resize-vertical" required="required" aria-required="true" data-webform-codemirror-mode="text/html" id="edit-webform-html-editor-codemirror-value" name="webform_html_editor_codemirror[value]" rows="5" cols="60">Hello <b>World!!!</b></textarea>'); @@ -69,26 +74,29 @@ public function testHtmlEditor() { $this->drupalPostForm('admin/structure/webform/config/elements', ['html_editor[disabled]' => TRUE], t('Save configuration')); // Check that HTML editor is removed and replaced by CodeMirror HTML editor. - $this->drupalGet('webform/test_element_html_editor'); + $this->drupalGet('/webform/test_element_html_editor'); $this->assertNoRaw('<textarea class="js-html-editor form-textarea required resize-vertical" data-drupal-selector="edit-webform-html-editor-value" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60" required="required" aria-required="true">Hello <b>World!!!</b></textarea>'); $this->assertRaw('<textarea data-drupal-selector="edit-webform-html-editor-value" class="js-webform-codemirror webform-codemirror html required form-textarea resize-vertical" required="required" aria-required="true" data-webform-codemirror-mode="text/html" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60">Hello <b>World!!!</b></textarea>'); - // Enable HTML editor and text format. + // Enable HTML editor and element text format. $edit = [ 'html_editor[disabled]' => FALSE, - 'html_editor[format]' => 'basic_html', + 'html_editor[element_format]' => 'basic_html', ]; $this->drupalPostForm('admin/structure/webform/config/elements', $edit, t('Save configuration')); // Check that Text format is disabled. - $this->drupalGet('webform/test_element_html_editor'); + $this->drupalGet('/webform/test_element_html_editor'); $this->assertNoRaw('<textarea class="js-html-editor form-textarea resize-vertical" data-drupal-selector="edit-webform-html-editor-value" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60">Hello <b>World!!!</b></textarea>'); $this->assertNoRaw('<textarea data-drupal-selector="edit-webform-html-editor-value" class="js-webform-codemirror webform-codemirror html required form-textarea resize-vertical" required="required" aria-required="true" data-webform-codemirror-mode="text/html" id="edit-webform-html-editor-value" name="webform_html_editor[value]" rows="5" cols="60">Hello <b>World!!!</b></textarea>'); $this->assertRaw('<textarea data-drupal-selector="edit-webform-html-editor-value-value" id="edit-webform-html-editor-value-value" name="webform_html_editor[value][value]" rows="5" cols="60" class="form-textarea required resize-vertical" required="required" aria-required="true">Hello <b>World!!!</b></textarea>'); $this->assertRaw('<h4 class="label">Basic HTML</h4>'); - // Disable text format. - $this->drupalPostForm('admin/structure/webform/config/elements', ['html_editor[format]' => ''], t('Save configuration')); + // Disable element text format. + $edit = [ + 'html_editor[element_format]' => '', + ]; + $this->drupalPostForm('admin/structure/webform/config/elements', $edit, t('Save configuration')); // Check that tidy removed <p> tags. $build = WebformHtmlEditor::checkMarkup('<p>Some text</p>'); @@ -106,6 +114,31 @@ public function testHtmlEditor() { // Check that tidy is disabled. $build = WebformHtmlEditor::checkMarkup('<p>Some text</p>'); $this->assertEqual(\Drupal::service('renderer')->renderPlain($build), '<p>Some text</p>'); + + /* Email text format */ + // Disable HTML editor. + $edit = [ + 'html_editor[disabled]' => FALSE, + 'html_editor[element_format]' => '', + 'html_editor[mail_format]' => '', + ]; + $this->drupalPostForm('admin/structure/webform/config/elements', $edit, t('Save configuration')); + + // Check that HTML editor is used. + $this->drupalGet('/admin/structure/webform/manage/contact/handlers/email_confirmation/edit'); + $this->assertRaw('<textarea data-drupal-selector="edit-settings-body-custom-html-value" class="js-html-editor form-textarea resize-vertical" id="edit-settings-body-custom-html-value" name="settings[body_custom_html][value]" rows="5" cols="60">'); + + // Enable mail text format. + $edit = [ + 'html_editor[mail_format]' => 'basic_html', + ]; + $this->drupalPostForm('admin/structure/webform/config/elements', $edit, t('Save configuration')); + + // Check mail text format is used. + $this->drupalGet('/admin/structure/webform/manage/contact/handlers/email_confirmation/edit'); + $this->assertNoRaw('<textarea data-drupal-selector="edit-settings-body-custom-html-value" class="js-html-editor form-textarea resize-vertical" id="edit-settings-body-custom-html-value" name="settings[body_custom_html][value]" rows="5" cols="60">'); + $this->assertRaw('<textarea data-drupal-selector="edit-settings-body-custom-html-value-value" id="edit-settings-body-custom-html-value-value" name="settings[body_custom_html][value][value]" rows="5" cols="60" class="form-textarea resize-vertical">'); + $this->assertRaw('<div class="filter-wrapper js-form-wrapper form-wrapper" data-drupal-selector="edit-settings-body-custom-html-value-format" id="edit-settings-body-custom-html-value-format">'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementIcheckTest.php b/web/modules/webform/src/Tests/Element/WebformElementIcheckTest.php index 112d14699a..24a2c9d927 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementIcheckTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementIcheckTest.php @@ -31,7 +31,7 @@ public function setUp() { */ public function testIcheckElement() { - $this->drupalGet('webform/test_element_icheck'); + $this->drupalGet('/webform/test_element_icheck'); // Check custom iCheck style set to 'flat'. $this->assertRaw('<div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-checkbox-custom form-item-checkbox-custom">'); @@ -50,7 +50,7 @@ public function testIcheckElement() { ->set('element.default_icheck', 'minimal') ->save(); - $this->drupalGet('webform/test_element_icheck'); + $this->drupalGet('/webform/test_element_icheck'); // Check custom iCheck style still set to 'flat'. $this->assertRaw('<div class="js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-checkbox-custom form-item-checkbox-custom">'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementImageResolutionTest.php b/web/modules/webform/src/Tests/Element/WebformElementImageResolutionTest.php new file mode 100644 index 0000000000..eff6bd3f2c --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementImageResolutionTest.php @@ -0,0 +1,44 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for webform image resolution element. + * + * @group Webform + */ +class WebformElementImageResolutionTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_image_resolution']; + + /** + * Tests image resolution element. + */ + public function testImageResolution() { + + $this->drupalGet('/webform/test_element_image_resolution'); + + // Check rendering. + $this->assertRaw('<label for="edit-webform-image-resolution-advanced">webform_image_resolution_advanced</label>'); + $this->assertRaw('<label for="edit-webform-image-resolution-advanced-x" class="visually-hidden">{width_title}</label>'); + $this->assertRaw('<input data-drupal-selector="edit-webform-image-resolution-advanced-x" type="number" id="edit-webform-image-resolution-advanced-x" name="webform_image_resolution_advanced[x]" value="300" step="1" min="1" class="form-number" />'); + $this->assertRaw('<label for="edit-webform-image-resolution-advanced-y" class="visually-hidden">{height_title}</label>'); + $this->assertRaw('<input data-drupal-selector="edit-webform-image-resolution-advanced-y" type="number" id="edit-webform-image-resolution-advanced-y" name="webform_image_resolution_advanced[y]" value="400" step="1" min="1" class="form-number" />'); + $this->assertRaw('{description}'); + + // Check validation. + $this->drupalPostForm('webform/test_element_image_resolution', ['webform_image_resolution[x]' => '100'], t('Submit')); + $this->assertRaw('Both a height and width value must be specified in the webform_image_resolution field.'); + + // Check processing. + $this->drupalPostForm('webform/test_element_image_resolution', [], t('Submit')); + $this->assertRaw("webform_image_resolution: '' +webform_image_resolution_advanced: 300x400"); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementInputMaskTest.php b/web/modules/webform/src/Tests/Element/WebformElementInputMaskTest.php new file mode 100644 index 0000000000..0cc910463b --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementInputMaskTest.php @@ -0,0 +1,96 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\webform\Entity\Webform; + +/** + * Tests for element input mask. + * + * @group Webform + */ +class WebformElementInputMaskTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_input_mask']; + + /** + * Test element input mask. + */ + public function testInputMask() { + $webform = Webform::load('test_element_input_mask'); + + // Check default values. + $this->postSubmission($webform); + $this->assertRaw("currency: '' +datetime: '' +decimal: '' +email: '' +ip: '' +license_plate: '' +mac: '' +percentage: '' +phone: '' +ssn: '' +vin: '' +zip: '' +uppercase: '' +lowercase: '' +custom: ''"); + + // Check patterns. + $edit = [ + 'email' => 'example@example.com', + 'datetime' => '2007-06-09\'T\'17:46:21', + 'decimal' => '9.9', + 'ip' => '255.255.255.255', + 'currency' => '$ 9.99', + 'percentage' => '99 %', + 'phone' => '(999) 999-9999', + 'license_plate' => '9-AAA-999', + 'mac' => '99-99-99-99-99-99', + 'ssn' => '999-99-9999', + 'vin' => 'JA3AY11A82U020534', + 'zip' => '99999-9999', + 'uppercase' => 'UPPERCASE', + 'lowercase' => 'lowercase', + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw("currency: '$ 9.99' +datetime: '2007-06-09''T''17:46:21' +decimal: '9.9' +email: example@example.com +ip: 255.255.255.255 +license_plate: 9-AAA-999 +mac: 99-99-99-99-99-99 +percentage: '99 %' +phone: '(999) 999-9999' +ssn: 999-99-9999 +vin: JA3AY11A82U020534 +zip: 99999-9999 +uppercase: UPPERCASE +lowercase: lowercase +custom: ''"); + + // Check pattern validation error messages. + $edit = [ + 'currency' => '$ 9.9_', + 'decimal' => '9._', + 'ip' => '255.255.255.__', + 'mac' => '99-99-99-99-99-_)', + 'percentage' => '_ %', + 'phone' => '(999) 999-999_', + 'ssn' => '999-99-999_', + 'zip' => '99999-999_', + ]; + $this->postSubmission($webform, $edit); + foreach ($edit as $name => $value) { + $this->assertRaw('<em class="placeholder">' . $name . '</em> field is not in the right format.'); + } + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementLikertTest.php b/web/modules/webform/src/Tests/Element/WebformElementLikertTest.php index 758d75c774..0a1d7c7ef2 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementLikertTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementLikertTest.php @@ -21,33 +21,33 @@ class WebformElementLikertTest extends WebformElementTestBase { */ public function testLikertElement() { - $this->drupalGet('webform/test_element_likert'); + $this->drupalGet('/webform/test_element_likert'); // Check default likert element. - $this->assertRaw('<table class="webform-likert-table responsive-enabled" data-likert-answers-count="3" data-drupal-selector="edit-likert-default-table" id="edit-likert-default-table" data-striping="1">'); - $this->assertPattern('#<tr>\s+<th></th>\s+<th>Option 1</th>\s+<th>Option 2</th>\s+<th>Option 3</th>\s+</tr>#'); - $this->assertRaw('<label for="edit-likert-default-table-q1-likert-question">Question 1</label>'); + $this->assertRaw('<table class="webform-likert-table sticky-enabled responsive-enabled" data-likert-answers-count="3" data-drupal-selector="edit-likert-default-table" id="edit-likert-default-table" data-striping="1">'); + $this->assertPattern('#<tr>\s+<th><span class="visually-hidden">Questions</span></th>\s+<th>Option 1</th>\s+<th>Option 2</th>\s+<th>Option 3</th>\s+</tr>#'); + $this->assertRaw('<label>Question 1</label>'); $this->assertRaw('<td><div class="js-form-item form-item js-form-type-radio form-type-radio js-form-item-likert-default-q1 form-item-likert-default-q1">'); - $this->assertRaw('<input data-drupal-selector="edit-likert-default-q1" type="radio" id="edit-likert-default-q1" name="likert_default[q1]" value="1" class="form-radio" />'); - $this->assertRaw('<label for="edit-likert-default-q1" class="option"><span class="webform-likert-label">Option 1</span></label>'); + $this->assertRaw('<input aria-labelledby="edit-likert-default-table-q1-likert-question" data-drupal-selector="edit-likert-default-q1" type="radio" id="edit-likert-default-q1" name="likert_default[q1]" value="1" class="form-radio" />'); + $this->assertRaw('<label for="edit-likert-default-q1" class="option"><span class="webform-likert-label visually-hidden">Option 1</span></label>'); // Check advanced likert element with N/A. - $this->assertPattern('#<tr>\s+<th></th>\s+<th>Option 1</th>\s+<th>Option 2</th>\s+<th>Option 3</th>\s+<th>Not applicable</th>\s+</tr>#'); + $this->assertPattern('#<tr>\s+<th><span class="visually-hidden">Questions</span></th>\s+<th>Option 1</th>\s+<th>Option 2</th>\s+<th>Option 3</th>\s+<th>Not applicable</th>\s+</tr>#'); $this->assertRaw('<td><div class="js-form-item form-item js-form-type-radio form-type-radio js-form-item-likert-advanced-q1 form-item-likert-advanced-q1">'); - $this->assertRaw('<input data-drupal-selector="edit-likert-advanced-q1" type="radio" id="edit-likert-advanced-q1--4" name="likert_advanced[q1]" value="N/A" class="form-radio" />'); - $this->assertRaw('<label for="edit-likert-advanced-q1--4" class="option"><span class="webform-likert-label">Not applicable</span></label>'); + $this->assertRaw('<input aria-labelledby="edit-likert-advanced-table-q1-likert-question" data-drupal-selector="edit-likert-advanced-q1" type="radio" id="edit-likert-advanced-q1--4" name="likert_advanced[q1]" value="N/A" class="form-radio" />'); + $this->assertRaw('<label for="edit-likert-advanced-q1--4" class="option"><span class="webform-likert-label visually-hidden">Not applicable</span></label>'); // Check likert with description. $this->assertRaw('<th>Option 1<div class="description">This is a description</div>'); - $this->assertRaw('<label for="edit-likert-description-table-q1-likert-question">Question 1</label>'); - $this->assertRaw('<div id="edit-likert-description-table-q1-likert-question--description" class="description">'); - $this->assertRaw('<label for="edit-likert-description-q1" class="option"><span class="webform-likert-label">Option 1</span></label>'); - $this->assertRaw('<span class="webform-likert-description">This is a description</span>'); + $this->assertRaw('<label>Question 1</label>'); + $this->assertRaw('<div id="edit-likert-description-table-q1-likert-question--description" class="webform-element-description">'); + $this->assertRaw('<label for="edit-likert-description-q1" class="option"><span class="webform-likert-label visually-hidden">Option 1</span></label>'); + $this->assertRaw('<span class="webform-likert-description hidden">This is a description</span>'); // Check likert with help. - $this->assertRaw('<th>Option 1<a href="#help" title="This is a help text" data-webform-help="This is a help text" class="webform-element-help">?</a>'); - $this->assertRaw('<label for="edit-likert-help-table-q1-likert-question">Question 1<a href="#help" title="This is a help text" data-webform-help="This is a help text" class="webform-element-help">?</a>'); - $this->assertRaw('<label for="edit-likert-help-q1--2" class="option"><span class="webform-likert-label">Option 2<a href="#help" title="This is a help text" data-webform-help="This is a help text" class="webform-element-help">?</a>'); + $this->assertRaw('<th>Option 1<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Option 1</div><div class="webform-element-help--content">This is help text</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<label>Question 1<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Question 1</div><div class="webform-element-help--content">This is help text</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<label for="edit-likert-help-q1--2" class="option"><span class="webform-likert-label visually-hidden">Option 2<span class="webform-likert-help hidden"><span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Option 2</div><div class="webform-element-help--content">This is help text</div>"><span aria-hidden="true">?</span></span>'); // Check likert required. $this->drupalPostForm('webform/test_element_likert', [], t('Submit')); diff --git a/web/modules/webform/src/Tests/Element/WebformElementLocationPlacesTest.php b/web/modules/webform/src/Tests/Element/WebformElementLocationPlacesTest.php new file mode 100644 index 0000000000..47eab668a4 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementLocationPlacesTest.php @@ -0,0 +1,78 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\webform\Entity\Webform; + +/** + * Tests for location (Algolia) places element. + * + * @group Webform + */ +class WebformElementLocationPlacesTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_loc_places']; + + /** + * Test location (Algolia) places element. + */ + public function testLocationPlaces() { + $webform = Webform::load('test_element_loc_places'); + + $this->drupalGet('/webform/test_element_loc_places'); + + // Check hidden attributes. + $this->assertRaw('<input class="webform-location-places form-text" data-drupal-selector="edit-location-default-value" type="text" id="edit-location-default-value" name="location_default[value]" value="" size="60" maxlength="255" placeholder="Enter a location" />'); + $this->assertRaw('<input data-webform-location-places-attribute="lat" data-drupal-selector="edit-location-default-lat" type="hidden" name="location_default[lat]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="lng" data-drupal-selector="edit-location-default-lng" type="hidden" name="location_default[lng]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="name" data-drupal-selector="edit-location-default-name" type="hidden" name="location_default[name]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="city" data-drupal-selector="edit-location-default-city" type="hidden" name="location_default[city]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="country" data-drupal-selector="edit-location-default-country" type="hidden" name="location_default[country]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="country_code" data-drupal-selector="edit-location-default-country-code" type="hidden" name="location_default[country_code]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="administrative" data-drupal-selector="edit-location-default-administrative" type="hidden" name="location_default[administrative]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="county" data-drupal-selector="edit-location-default-county" type="hidden" name="location_default[county]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="suburb" data-drupal-selector="edit-location-default-suburb" type="hidden" name="location_default[suburb]" value="" />'); + $this->assertRaw('<input data-webform-location-places-attribute="postcode" data-drupal-selector="edit-location-default-postcode" type="hidden" name="location_default[postcode]" value="" />'); + + // Check visible attributes. + $this->assertRaw('<input class="webform-location-places form-text" data-drupal-selector="edit-location-attributes-value" type="text" id="edit-location-attributes-value" name="location_attributes[value]" value="" size="60" maxlength="255" />'); + $this->assertRaw('<input data-webform-location-places-attribute="lat" data-drupal-selector="edit-location-attributes-lat" type="text" id="edit-location-attributes-lat" name="location_attributes[lat]" value="" size="60" maxlength="255" class="form-text" />'); + + // Check invalid validation. + $edit = [ + 'location_attributes_required[value]' => 'test', + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw('The location_attributes_required is not valid.'); + + // Check valid validation with lat(itude). + $edit = [ + 'location_attributes_required[value]' => 'test', + 'location_attributes_required[lat]' => 1, + ]; + $this->postSubmission($webform, $edit); + $this->assertNoRaw('The location_attributes_required is not valid.'); + + // Check application id and API key is missing. + $this->assertNoRaw('"app_id"'); + $this->assertNoRaw('"api_key"'); + + // Set application id and API key. + \Drupal::configFactory()->getEditable('webform.settings') + ->set('element.default_algolia_places_app_id', '{default_algolia_places_app_id}') + ->set('element.default_algolia_places_api_key', '{default_algolia_places_api_key}') + ->save(); + + // Check application id and API key is set. + $this->drupalGet('/webform/test_element_loc_places'); + $this->assertRaw('"app_id"'); + $this->assertRaw('"api_key"'); + $this->assertRaw('"webform":{"location":{"places":{"app_id":"{default_algolia_places_app_id}","api_key":"{default_algolia_places_api_key}"}}}'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFileImageTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFileImageTest.php new file mode 100644 index 0000000000..846ae67ccd --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFileImageTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\webform\Entity\Webform; + +/** + * Test for webform element managed file image handling. + * + * @group Webform + */ +class WebformElementManagedFileImageTest extends WebformElementManagedFileTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['file', 'image', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_image_file']; + + /** + * Test image file upload. + */ + public function testImageFileUpload() { + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('test_element_image_file'); + + // Get test image. + $images = $this->drupalGetTestFiles('image'); + $image = reset($images); + + // Check image max resolution validation is being applied. + $edit = [ + 'files[webform_image_file_advanced]' => \Drupal::service('file_system')->realpath($image->uri), + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw('The image was resized to fit within the maximum allowed dimensions of <em class="placeholder">20x20</em> pixels.'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFileLimitTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFileLimitTest.php new file mode 100644 index 0000000000..901fb11427 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFileLimitTest.php @@ -0,0 +1,84 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +use Drupal\webform\Entity\Webform; + +/** + * Test for webform element managed file limit. + * + * @group Webform + */ +class WebformElementManagedFileLimitTest extends WebformElementManagedFileTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['file', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_managed_file_limit']; + + /** + * Test file limit. + */ + public function testLimits() { + $this->drupalLogin($this->rootUser); + + // Get a 1 MB text file. + $files = $this->getTestFiles('text', '1024'); + $file = reset($files); + $bytes = filesize($file->uri); + $this->debug($bytes); + + $webform = Webform::load('test_element_managed_file_limit'); + + // Check form file limit. + $this->drupalGet('/webform/test_element_managed_file_limit'); + $this->assertRaw('1 MB limit per form.'); + + // Check empty form file limit. + $webform->setSetting('form_file_limit', '')->save(); + $this->drupalGet('/webform/test_element_managed_file_limit'); + $this->assertNoRaw('1 MB limit per form.'); + + // Check default form file limit. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('settings.default_form_file_limit', '2 MB') + ->save(); + $this->drupalGet('/webform/test_element_managed_file_limit'); + $this->assertRaw('2 MB limit per form.'); + + // Set limit to 2 files. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('settings.default_form_file_limit', ($bytes * 2) . ' bytes') + ->save(); + $this->drupalGet('/webform/test_element_managed_file_limit'); + $this->assertRaw(format_size($bytes * 2) . ' limit per form.'); + + // Check valid file upload. + $edit = [ + 'files[managed_file_01]' => \Drupal::service('file_system')->realpath($file->uri), + ]; + $sid = $this->postSubmission($webform, $edit); + $this->assertNotNull($sid); + + // Check invalid file upload. + $edit = [ + 'files[managed_file_01]' => \Drupal::service('file_system')->realpath($file->uri), + 'files[managed_file_02]' => \Drupal::service('file_system')->realpath($file->uri), + 'files[managed_file_03]' => \Drupal::service('file_system')->realpath($file->uri), + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw('This form\'s file upload quota of <em class="placeholder">2 KB</em> has been exceeded. Please remove some files.'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFilePreviewTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePreviewTest.php new file mode 100644 index 0000000000..9a6008fc6c --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePreviewTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Test for webform element managed file preview. + * + * @group Webform + */ +class WebformElementManagedFilePreviewTest extends WebformElementManagedFileTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['file', 'image', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_managed_file_prev']; + + /** + * Test image file upload. + */ + public function testImageFileUpload() { + global $base_url; + + // Check that anonymous users can not preview files. + $this->drupalGet('/webform/test_element_managed_file_prev/test'); + $this->assertRaw('<span data-drupal-selector="edit-webform-image-file-file-1-filename" class="file file--mime-image-gif file--image"> webform_image_file.gif</span>'); + $this->assertRaw('<span data-drupal-selector="edit-webform-audio-file-file-3-filename" class="file file--mime-audio-mpeg file--audio"> webform_audio_file.mp3</span>'); + $this->assertRaw('<span data-drupal-selector="edit-webform-video-file-file-5-filename" class="file file--mime-video-mp4 file--video"> webform_video_file.mp4</span>'); + $this->assertRaw('<span data-drupal-selector="edit-webform-document-file-file-7-filename" class="file file--mime-text-plain file--text"> webform_document_file.txt</span>'); + + // Login admin user. + $this->drupalLogin($this->rootUser); + + // Check that authenticated users can preview files. + $this->drupalGet('/webform/test_element_managed_file_prev/test'); + + $this->assertRaw('<div class="webform-managed-file-preview webform-image-file-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-webform-image-file-file-9-filename" id="edit-webform-image-file-file-9-filename">'); + $this->assertRaw('<a href="' . $base_url . '/system/files/webform/test_element_managed_file_prev/_sid_/webform_image_file_0.gif" class="js-webform-image-file-modal webform-image-file-modal">'); + + $this->assertRaw('<div class="webform-managed-file-preview webform-audio-file-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-webform-audio-file-file-11-filename" id="edit-webform-audio-file-file-11-filename">'); + $this->assertRaw('<source src="' . $base_url . '/system/files/webform/test_element_managed_file_prev/_sid_/webform_audio_file_0.mp3" type="audio/mpeg">'); + + $this->assertRaw('<div class="webform-managed-file-preview webform-video-file-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-webform-video-file-file-13-filename" id="edit-webform-video-file-file-13-filename">'); + $this->assertRaw('<source src="' . $base_url . '/system/files/webform/test_element_managed_file_prev/_sid_/webform_video_file_0.mp4" type="video/mp4">'); + + $this->assertRaw('<div class="webform-managed-file-preview webform-document-file-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-webform-document-file-file-15-filename" id="edit-webform-document-file-file-15-filename">'); + $this->assertRaw($base_url . '/system/files/webform/test_element_managed_file_prev/_sid_/webform_document_file_0.txt'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFilePrivateTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePrivateTest.php index fd30c71dff..8a829e30ca 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementManagedFilePrivateTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePrivateTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Url; use Drupal\file\Entity\File; +use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; /** @@ -13,22 +14,37 @@ */ class WebformElementManagedFilePrivateTest extends WebformElementManagedFileTestBase { + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_managed_file']; + /** * Test private files. */ public function testPrivateFiles() { - $elements = $this->webform->getElementsDecoded(); + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + $webform = Webform::load('test_element_managed_file'); + + /**************************************************************************/ + + $elements = $webform->getElementsDecoded(); $elements['managed_file_single']['#uri_scheme'] = 'private'; - $this->webform->setElements($elements); - $this->webform->save(); + $webform->setElements($elements); + $webform->save(); - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); // Upload private file as authenticated user. $edit = [ 'files[managed_file_single]' => \Drupal::service('file_system')->realpath($this->files[0]->uri), ]; - $sid = $this->postSubmission($this->webform, $edit); + $sid = $this->postSubmission($webform, $edit); /** @var \Drupal\webform\WebformSubmissionInterface $submission */ $submission = WebformSubmission::load($sid); @@ -59,17 +75,19 @@ public function testPrivateFiles() { $destination_url = Url::fromUri('base://system/files', [ 'query' => [ 'file' => 'webform/test_element_managed_file/' . $sid . '/' . $this->files[0]->filename, - ] + ], + ]); + $this->assertUrl('user/login', [ + 'query' => [ + 'destination' => $destination_url->toString(), + ], ]); - $this->assertUrl('user/login', ['query' => [ - 'destination' => $destination_url->toString(), - ]]); // Upload private file and preview as anonymous user. $edit = [ 'files[managed_file_single]' => \Drupal::service('file_system')->realpath($this->files[1]->uri), ]; - $this->drupalPostForm('webform/' . $this->webform->id(), $edit, t('Preview')); + $this->drupalPostForm('webform/' . $webform->id(), $edit, t('Preview')); $temp_file_uri = file_create_url('private://webform/test_element_managed_file/_sid_/' . basename($this->files[1]->uri)); @@ -82,7 +100,7 @@ public function testPrivateFiles() { $this->assertResponse(403); // Check that authenticated user can't access temp file. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); $this->drupalGet($temp_file_uri); $this->assertResponse(403); diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFilePublicTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePublicTest.php index cbcc511bf5..f471aa5251 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementManagedFilePublicTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFilePublicTest.php @@ -48,7 +48,7 @@ public function testPublicUpload() { $this->drupalLogin($this->rootUser); // Check element webform warning message for public files. - $this->drupalGet('admin/structure/webform/manage/test_element_managed_file/element/managed_file_single/edit'); + $this->drupalGet('/admin/structure/webform/manage/test_element_managed_file/element/managed_file_single/edit'); $this->assertRaw('Public files upload destination is dangerous for webforms that are available to anonymous and/or untrusted users.'); $this->assertFieldById('edit-properties-uri-scheme-public'); @@ -56,7 +56,7 @@ public function testPublicUpload() { \Drupal::configFactory()->getEditable('webform.settings') ->set('file.file_public', FALSE) ->save(); - $this->drupalGet('admin/structure/webform/manage/test_element_managed_file/element/managed_file_single/edit'); + $this->drupalGet('/admin/structure/webform/manage/test_element_managed_file/element/managed_file_single/edit'); $this->assertNoRaw('Public files upload destination is dangerous for webforms that are available to anonymous and/or untrusted users.'); $this->assertNoFieldById('edit-properties-uri-scheme-public'); @@ -66,7 +66,7 @@ public function testPublicUpload() { /**************************************************************************/ // Check managed_file element is enabled. - $this->drupalGet('admin/structure/webform/manage/test_element_managed_file/element/add'); + $this->drupalGet('/admin/structure/webform/manage/test_element_managed_file/element/add'); $this->assertRaw('>File<'); // Disable managed file element. @@ -75,11 +75,11 @@ public function testPublicUpload() { ->save(); // Check disabled managed_file element remove from add element dialog. - $this->drupalGet('admin/structure/webform/manage/test_element_managed_file/element/add'); + $this->drupalGet('/admin/structure/webform/manage/test_element_managed_file/element/add'); $this->assertNoRaw('>File<'); // Check disabled managed_file element warning. - $this->drupalGet('admin/structure/webform/manage/test_element_managed_file'); + $this->drupalGet('/admin/structure/webform/manage/test_element_managed_file'); $this->assertRaw('<em class="placeholder">managed_file_single</em> is a <em class="placeholder">File</em> element, which has been disabled and will not be rendered.'); $this->assertRaw('<em class="placeholder">managed_file_multiple</em> is a <em class="placeholder">File</em> element, which has been disabled and will not be rendered.'); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFileTest.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFileTest.php index b25a732bfe..5cdeb6b591 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementManagedFileTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFileTest.php @@ -31,12 +31,39 @@ class WebformElementManagedFileTest extends WebformElementManagedFileTestBase { 'test_element_managed_file_name', ]; + /** + * The 'test_element_managed_file' webform. + * + * @var \Drupal\webform\WebformInterface + */ + protected $webform; + + /** + * Admin submission user. + * + * @var \Drupal\user\Entity\User + */ + protected $adminSubmissionUser; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->webform = Webform::load('test_element_managed_file'); + + $this->adminSubmissionUser = $this->drupalCreateUser([ + 'administer webform submission', + ]); + } + /** * Test single and multiple file upload. */ - public function _testFileUpload() { + public function testFileUpload() { /* Element rendering */ - $this->drupalGet('webform/test_element_managed_file'); + $this->drupalGet('/webform/test_element_managed_file'); // Check single file upload button. $this->assertRaw('<label for="edit-managed-file-single-button-upload-button--2" class="button button-action webform-file-button">Choose file</label>'); @@ -51,6 +78,20 @@ public function _testFileUpload() { $this->checkFileUpload('single', $this->files[0], $this->files[1]); $this->checkFileUpload('multiple', $this->files[2], $this->files[3]); + + /* File placeholder */ + + // Check placeholder is displayed. + $this->drupalGet('/webform/test_element_managed_file'); + $this->assertRaw('<div class="webform-managed-file-placeholder managed-file-placeholder js-form-wrapper form-wrapper" data-drupal-selector="edit-managed-file-single-placeholder-file-placeholder" id="edit-managed-file-single-placeholder-file-placeholder">This is the single file upload placeholder</div>'); + $this->assertRaw('<div class="webform-managed-file-placeholder managed-file-placeholder js-form-wrapper form-wrapper" data-drupal-selector="edit-managed-file-multiple-placeholder-file-placeholder" id="edit-managed-file-multiple-placeholder-file-placeholder">This is the multiple file upload placeholder</div>'); + + $this->drupalLogin($this->rootUser); + + // Check placeholder is not displayed when files are uploaded. + $this->drupalGet('/webform/test_element_managed_file/test'); + $this->assertNoRaw('<div class="webform-managed-file-placeholder managed-file-placeholder js-form-wrapper form-wrapper" data-drupal-selector="edit-managed-file-single-placeholder-file-placeholder" id="edit-managed-file-single-placeholder-file-placeholder">This is the single file upload placeholder</div>'); + $this->assertNoRaw('<div class="webform-managed-file-placeholder managed-file-placeholder js-form-wrapper form-wrapper" data-drupal-selector="edit-managed-file-multiple-placeholder-file-placeholder" id="edit-managed-file-multiple-placeholder-file-placeholder">This is the multiple file upload placeholder</div>'); } /** @@ -58,7 +99,7 @@ public function _testFileUpload() { * * The property #file_name_pattern is tested. */ - protected function testFileRename() { + public function testFileRename() { $webform = Webform::load('test_element_managed_file_name'); $source_for_filename = $this->randomMachineName(); @@ -97,7 +138,7 @@ protected function testFileRename() { /** * Test file management. */ - protected function testFileManagement() { + public function testFileManagement() { $this->drupalLogin($this->rootUser); $webform = Webform::load('test_element_managed_file'); @@ -164,7 +205,7 @@ protected function testFileManagement() { /** * Test file upload with disabled results. */ - protected function testFileUploadWithDisabledResults() { + public function testFileUploadWithDisabledResults() { $this->drupalLogin($this->rootUser); $webform = Webform::load('test_element_managed_file_dis'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementManagedFileTestBase.php b/web/modules/webform/src/Tests/Element/WebformElementManagedFileTestBase.php index cf5a608a9a..2387214ba9 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementManagedFileTestBase.php +++ b/web/modules/webform/src/Tests/Element/WebformElementManagedFileTestBase.php @@ -3,7 +3,6 @@ namespace Drupal\webform\Tests\Element; use Drupal\file\Entity\File; -use Drupal\webform\Entity\Webform; /** * Base class for testing webform element managed file handling. @@ -17,13 +16,6 @@ abstract class WebformElementManagedFileTestBase extends WebformElementTestBase */ public static $modules = ['file', 'webform']; - /** - * Webforms to load. - * - * @var array - */ - protected static $testWebforms = ['test_element_managed_file']; - /** * File usage manager. * @@ -31,13 +23,6 @@ abstract class WebformElementManagedFileTestBase extends WebformElementTestBase */ protected $fileUsage; - /** - * The 'test_element_managed_file' webform. - * - * @var \Drupal\webform\WebformInterface - */ - protected $webform; - /** * An array of plain text test files. * @@ -51,11 +36,7 @@ abstract class WebformElementManagedFileTestBase extends WebformElementTestBase public function setUp() { parent::setUp(); - // Create users. - $this->createUsers(); - $this->fileUsage = $this->container->get('file.usage'); - $this->webform = Webform::load('test_element_managed_file'); $this->files = $this->drupalGetTestFiles('text'); $this->verbose('<pre>' . print_r($this->files, TRUE) . '</pre>'); @@ -71,10 +52,11 @@ protected function getLastFileId() { /** * Load an uncached file entity. * - * @param $fid + * @param string $fid * A file id. * * @return \Drupal\file\FileInterface + * An uncached file object */ protected function fileLoad($fid) { \Drupal::entityTypeManager()->getStorage('file')->resetCache(); diff --git a/web/modules/webform/src/Tests/Element/WebformElementMappingTest.php b/web/modules/webform/src/Tests/Element/WebformElementMappingTest.php index e4086e01db..5ab667fa9b 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMappingTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMappingTest.php @@ -20,20 +20,20 @@ class WebformElementMappingTest extends WebformElementTestBase { * Test mapping element. */ public function testMappingElement() { - $this->drupalGet('webform/test_element_mapping'); + $this->drupalGet('/webform/test_element_mapping'); // Check default element. - $this->assertRaw('<th width="50%">Source →</th>'); - $this->assertRaw('<th width="50%">Destination</th>'); + $this->assertRaw('<th>Source →</th>'); + $this->assertRaw('<th>Destination</th>'); $this->assertRaw('<select data-drupal-selector="edit-webform-mapping-one" id="edit-webform-mapping-one" name="webform_mapping[one]" class="form-select"><option value="" selected="selected">- Select -</option><option value="four">Four</option><option value="five">Five</option><option value="six">Six</option></select>'); // Check custom element. - $this->assertRaw('<th width="50%">{Custom source} »</th>'); - $this->assertRaw('<th width="50%">{Destination source}</th>'); + $this->assertRaw('<th>{Custom source} »</th>'); + $this->assertRaw('<th>{Destination source}</th>'); $this->assertRaw('<select data-drupal-selector="edit-webform-mapping-one" id="edit-webform-mapping-one" name="webform_mapping[one]" class="form-select"><option value="" selected="selected">- Select -</option><option value="four">Four</option><option value="five">Five</option><option value="six">Six</option></select>'); // Check custom select other element type. - $this->assertRaw('<input data-drupal-selector="edit-webform-mapping-select-other-one-other" type="text" id="edit-webform-mapping-select-other-one-other" name="webform_mapping_select_other[one][other]" value="" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); + $this->assertRaw('<input data-drupal-selector="edit-webform-mapping-select-other-one-other" type="text" id="edit-webform-mapping-select-other-one-other" name="webform_mapping_select_other[one][other]" value="" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); // Check custom textfield #size property. $this->assertRaw('<input data-drupal-selector="edit-webform-mapping-textfield-one" type="text" id="edit-webform-mapping-textfield-one" name="webform_mapping_textfield[one]" value="" size="10" maxlength="128" class="form-text" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementMarkupTest.php b/web/modules/webform/src/Tests/Element/WebformElementMarkupTest.php index a3e694b51f..8f790b9081 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMarkupTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMarkupTest.php @@ -9,6 +9,13 @@ */ class WebformElementMarkupTest extends WebformElementTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_test_markup']; + /** * Webforms to load. * @@ -20,13 +27,23 @@ class WebformElementMarkupTest extends WebformElementTestBase { * Test markup element. */ public function testMarkup() { - $this->drupalGet('webform/test_element_markup'); + // Check markup display on form. + $this->drupalGet('/webform/test_element_markup'); + $this->assertRaw('<div id="edit-markup" class="js-form-item form-item js-form-type-webform-markup form-type-webform-markup js-form-item-markup form-item-markup form-no-label">'); $this->assertRaw('<p>This is normal markup</p>'); $this->assertRaw('<p>This is only displayed on the form view.</p>'); $this->assertNoRaw('<p>This is only displayed on the submission view.</p>'); $this->assertRaw('<p>This is displayed on the both the form and submission view.</p>'); + $this->assertRaw('<p>This is displayed on the both the form and submission view.</p>'); + + // Check markup alter via preprocessing. + // @see webform_test_markup_preprocess_webform_html_editor_markup() + $this->drupalGet('/webform/test_element_markup'); + $this->assertNoRaw('<p>Alter this markup.</p>'); + $this->assertRaw('<p><em>Alter this markup.</em> <strong>This markup was altered.</strong></p>'); + // Check markup display on view. $this->drupalPostForm('webform/test_element_markup', [], t('Preview')); $this->assertNoRaw('<p>This is normal markup</p>'); $this->assertNoRaw('<p>This is only displayed on the form view.</p>'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementMediaFileTest.php b/web/modules/webform/src/Tests/Element/WebformElementMediaFileTest.php index d4df72c492..00f5cdb16d 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMediaFileTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMediaFileTest.php @@ -33,7 +33,7 @@ public function testMediaFileUpload() { /* Element render */ // Get test webform. - $this->drupalGet('webform/test_element_media_file'); + $this->drupalGet('/webform/test_element_media_file'); // Check document file. $this->assertRaw('<input data-drupal-selector="edit-document-file-upload" type="file" id="edit-document-file-upload" name="files[document_file]" size="22" class="js-form-file form-file" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementMessageTest.php b/web/modules/webform/src/Tests/Element/WebformElementMessageTest.php index 7b65a76532..90e9d75c55 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMessageTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMessageTest.php @@ -31,7 +31,7 @@ class WebformElementMessageTest extends WebformElementTestBase { public function testMessage() { $webform = Webform::load('test_element_message'); - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); // Check basic message. $this->assertRaw('<div data-drupal-selector="edit-message-info" class="webform-message js-webform-message js-form-wrapper form-wrapper" id="edit-message-info">'); @@ -54,7 +54,7 @@ public function testMessage() { // Check that close links are not enabled for 'user' or 'state' storage // for anonymous users. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->assertRaw('href="#close"'); $this->assertNoRaw('data-message-storage="user"'); $this->assertNoRaw('data-message-storage="state"'); @@ -63,7 +63,7 @@ public function testMessage() { $this->drupalLogin($this->drupalCreateUser()); // Check that close links are enabled. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->assertNoRaw('href="#close"'); $this->assertRaw('data-drupal-selector="edit-message-close-storage-user"'); $this->assertRaw('data-message-storage="user"'); @@ -73,11 +73,11 @@ public function testMessage() { $this->assertRaw('data-message-storage="custom"'); // Close message using 'user' storage. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->clickLink('×', 0); // Check that 'user' storage message is removed. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->assertNoRaw('data-drupal-selector="edit-message-close-storage-user"'); $this->assertNoRaw('data-message-storage="user"'); $this->assertRaw('data-drupal-selector="edit-message-close-storage-state"'); @@ -86,11 +86,11 @@ public function testMessage() { $this->assertRaw('data-message-storage="custom"'); // Close message using 'state' storage. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->clickLink('×', 0); // Check that 'state' and 'user' storage message is removed. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->assertNoRaw('data-drupal-selector="edit-message-close-storage-user"'); $this->assertNoRaw('data-message-storage="user"'); $this->assertNoRaw('data-drupal-selector="edit-message-close-storage-state"'); @@ -99,11 +99,11 @@ public function testMessage() { $this->assertRaw('data-message-storage="custom"'); // Close message using 'custom' storage. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->clickLink('×', 0); // Check that 'state' and 'user' storage message is removed. - $this->drupalGet('webform/test_element_message'); + $this->drupalGet('/webform/test_element_message'); $this->assertNoRaw('data-drupal-selector="edit-message-close-storage-user"'); $this->assertNoRaw('data-message-storage="user"'); $this->assertNoRaw('data-drupal-selector="edit-message-close-storage-state"'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementMoreTest.php b/web/modules/webform/src/Tests/Element/WebformElementMoreTest.php index 3fa2ddeed1..783d7b21e8 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMoreTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMoreTest.php @@ -20,46 +20,42 @@ class WebformElementMoreTest extends WebformElementTestBase { * Test element more. */ public function testMore() { - $this->drupalGet('webform/test_element_more'); + $this->drupalGet('/webform/test_element_more'); // Check default more. - $this->assertRaw('<div id="edit-more--description" class="description">'); $this->assertRaw('<div id="edit-more--more" class="js-webform-element-more webform-element-more">'); - $this->assertRaw('<div class="webform-element-more--link"><a href="#more">More</a></div>'); + $this->assertRaw('<div class="webform-element-more--link"><a role="button" href="#more">More</a></div>'); // Check more with custom title. - $this->assertRaw('<div id="edit-more-title--description" class="description">'); $this->assertRaw('<div id="edit-more-title--more" class="js-webform-element-more webform-element-more">'); - $this->assertRaw('<div class="webform-element-more--link"><a href="#more">{Custom more title}</a></div>'); + $this->assertRaw('<div class="webform-element-more--link"><a role="button" href="#more">{Custom more title}</a></div>'); // Check more with HTML markup. $this->assertRaw('<div id="edit-more-html--more" class="js-webform-element-more webform-element-more">'); - $this->assertRaw('<div class="webform-element-more--content">{This is an example of more with <b>HTML markup</b>}</div>'); + $this->assertRaw('<div id="edit-more-html--more--content" class="webform-element-more--content">{This is an example of more with <b>HTML markup</b>}</div>'); // Check more with description. - $this->assertRaw('<div id="edit-more-title-description--description" class="description">'); - $this->assertRaw('{This is an example of a description}'); + $this->assertRaw('<div id="edit-more-title-description--description" class="webform-element-description">{This is an example of a description}</div>'); $this->assertRaw('<div id="edit-more-title-description--more" class="js-webform-element-more webform-element-more">'); // Check more with hidden description. - $this->assertRaw('<div id="edit-more-title-description-hidden--description" class="description">'); - $this->assertRaw('<div class="visually-hidden">{This is an example of a hidden description}</div>'); + $this->assertRaw('<div id="edit-more-title-description-hidden--description" class="webform-element-description visually-hidden">{This is an example of a hidden description}</div>'); $this->assertRaw('<div id="edit-more-title-description-hidden--more" class="js-webform-element-more webform-element-more">'); // Check datetime more. $this->assertRaw('<div id="edit-more-datetime--more" class="js-webform-element-more webform-element-more">'); // Check fieldset more. - $this->assertRaw('<div id="edit-more-fieldset--description" class="description">{This is a description}'); + $this->assertRaw('<div id="edit-more-fieldset--description" class="webform-element-description">{This is a description}</div>'); $this->assertRaw('<div id="edit-more-fieldset--more" class="js-webform-element-more webform-element-more">'); // Check details more. $this->assertRaw('<div id="edit-more-details--more" class="js-webform-element-more webform-element-more">'); - $this->assertRaw('<div class="webform-element-more--link"><a href="#more">More</a></div>'); + $this->assertRaw('<div class="webform-element-more--link"><a role="button" href="#more">More</a></div>'); // Check tooltip ignored more. - $this->assertRaw('<div id="edit-more-tooltip--description" class="description visually-hidden">'); - $this->assertNoRaw('<div id="edit-more-tooltip--more" class="js-webform-element-more webform-element-more">'); + $this->assertRaw('<div id="edit-more-tooltip--description" class="webform-element-description visually-hidden">{This is a description}</div>'); + $this->assertRaw('<div id="edit-more-tooltip--more" class="js-webform-element-more webform-element-more">'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementMultipleTest.php b/web/modules/webform/src/Tests/Element/WebformElementMultipleTest.php index cd6b8cac6b..0891ff8c75 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementMultipleTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementMultipleTest.php @@ -2,6 +2,8 @@ namespace Drupal\webform\Tests\Element; +use Drupal\webform\Entity\Webform; + /** * Tests for webform element multiple. * @@ -25,6 +27,8 @@ public function testMultiple() { // Processing. /**************************************************************************/ + $webform = Webform::load('test_element_multiple'); + // Check processing for all elements. $this->drupalPostForm('webform/test_element_multiple', [], t('Submit')); $this->assertRaw("webform_multiple_default: @@ -39,6 +43,14 @@ public function testMultiple() { - One - Two - Three +webform_multiple_no_add_more: + - One + - Two + - Three +webform_multiple_custom_label: + - One + - Two + - Three webform_multiple_email_five: - example@example.com - test@test.com @@ -91,19 +103,20 @@ public function testMultiple() { description: 'This is the number 1.' - value: two text: Two - description: 'This is the number 2.'"); + description: 'This is the number 2.' +webform_multiple_no_items: { }"); /**************************************************************************/ // Rendering. /**************************************************************************/ - $this->drupalGet('webform/test_element_multiple'); + $this->drupalGet('/webform/test_element_multiple'); // Check first tr. $this->assertRaw('<tr class="draggable odd" data-drupal-selector="edit-webform-multiple-default-items-0">'); $this->assertRaw('<td><div class="js-form-item form-item js-form-type-textfield form-type-textfield js-form-item-webform-multiple-default-items-0--item- form-item-webform-multiple-default-items-0--item- form-no-label">'); $this->assertRaw('<label for="edit-webform-multiple-default-items-0-item-" class="visually-hidden">Item value</label>'); - $this->assertRaw('<input data-drupal-selector="edit-webform-multiple-default-items-0-item-" type="text" id="edit-webform-multiple-default-items-0-item-" name="webform_multiple_default[items][0][_item_]" value="One" size="60" maxlength="128" placeholder="Enter value" class="form-text" />'); + $this->assertRaw('<input data-drupal-selector="edit-webform-multiple-default-items-0-item-" type="text" id="edit-webform-multiple-default-items-0-item-" name="webform_multiple_default[items][0][_item_]" value="One" size="60" maxlength="128" placeholder="Enter value…" class="form-text" />'); $this->assertRaw('<td class="webform-multiple-table--weight"><div class="webform-multiple-table--weight js-form-item form-item js-form-type-number form-type-number js-form-item-webform-multiple-default-items-0-weight form-item-webform-multiple-default-items-0-weight form-no-label">'); $this->assertRaw('<label for="edit-webform-multiple-default-items-0-weight" class="visually-hidden">Item weight</label>'); $this->assertRaw('<input class="webform-multiple-sort-weight form-number" data-drupal-selector="edit-webform-multiple-default-items-0-weight" type="number" id="edit-webform-multiple-default-items-0-weight" name="webform_multiple_default[items][0][weight]" value="0" step="1" size="10" />'); @@ -114,9 +127,20 @@ public function testMultiple() { $this->assertNoRaw('<tr class="draggable odd" data-drupal-selector="edit-webform-multiple-no-sorting-items-0">'); $this->assertRaw('<tr data-drupal-selector="edit-webform-multiple-no-sorting-items-0" class="odd">'); + // Check that add more is removed. + $this->assertFieldByName('webform_multiple_no_operations[add][more_items]', '1'); + $this->assertNoFieldByName('webform_multiple_no_add_more[add][more_items]', '1'); + + // Check custom labels. + $this->assertRaw('<input data-drupal-selector="edit-webform-multiple-custom-label-add-submit" formnovalidate="formnovalidate" type="submit" id="edit-webform-multiple-custom-label-add-submit" name="webform_multiple_custom_label_table_add" value="{add_more_button_label}" class="button js-form-submit form-submit" />'); + $this->assertRaw('<span class="field-suffix">{add_more_input_label}</span>'); + // Check that operations is disabled. $this->assertNoRaw('data-drupal-selector="edit-webform-multiple-no-operations-items-0-operations-remove"'); + // Check no items message. + $this->assertRaw('No items entered. Please add items below.'); + /**************************************************************************/ // Validation. /**************************************************************************/ @@ -175,6 +199,19 @@ public function testMultiple() { $this->assertFieldByName('webform_multiple_default[items][1][_item_]', 'Three'); $this->assertFieldByName('webform_multiple_default[items][2][_item_]', 'Four'); + // Add one options to 'webform_multiple_no_items'. + $this->drupalPostAjaxForm(NULL, $edit, 'webform_multiple_no_items_table_add'); + $this->assertNoRaw('No items entered. Please add items below.'); + $this->assertFieldByName('webform_multiple_no_items[items][0][_item_]'); + + // Check no items message is never displayed when #required. + $webform->setElementProperties('webform_multiple_no_items', ['#type' => 'webform_multiple', '#title' => 'webform_multiple_no_items', '#required' => TRUE]); + $webform->save(); + $this->drupalGet('/webform/test_element_multiple'); + $this->assertNoRaw('No items entered. Please add items below.'); + $this->drupalPostAjaxForm(NULL, $edit, 'webform_multiple_default_table_remove_0'); + $this->assertNoRaw('No items entered. Please add items below.'); + /**************************************************************************/ // Property (#multiple). /**************************************************************************/ diff --git a/web/modules/webform/src/Tests/Element/WebformElementOptionsTest.php b/web/modules/webform/src/Tests/Element/WebformElementOptionsTest.php index 2a613839da..d3169acce8 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementOptionsTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementOptionsTest.php @@ -21,9 +21,9 @@ class WebformElementOptionsTest extends WebformElementTestBase { */ public function testElementOptions() { // Check options maxlength. - $this->drupalGet('webform/test_element_options'); - $this->assertRaw('<input class="js-webform-options-value form-text" data-drupal-selector="edit-webform-options-maxlength-options-items-0-value" type="text" id="edit-webform-options-maxlength-options-items-0-value" name="webform_options_maxlength[options][items][0][value]" value="one" size="60" maxlength="20" placeholder="Enter value" />'); - $this->assertRaw('<input data-drupal-selector="edit-webform-options-maxlength-options-items-0-text" type="text" id="edit-webform-options-maxlength-options-items-0-text" name="webform_options_maxlength[options][items][0][text]" value="One" size="60" maxlength="20" placeholder="Enter text" class="form-text" />'); + $this->drupalGet('/webform/test_element_options'); + $this->assertRaw('<input class="js-webform-options-sync form-text" data-drupal-selector="edit-webform-options-maxlength-options-items-0-value" type="text" id="edit-webform-options-maxlength-options-items-0-value" name="webform_options_maxlength[options][items][0][value]" value="one" size="60" maxlength="20" placeholder="Enter value…" />'); + $this->assertRaw('<input data-drupal-selector="edit-webform-options-maxlength-options-items-0-text" type="text" id="edit-webform-options-maxlength-options-items-0-text" name="webform_options_maxlength[options][items][0][text]" value="One" size="60" maxlength="20" placeholder="Enter text…" class="form-text" />'); // Check default value handling. $this->drupalPostForm('webform/test_element_options', [], t('Submit')); @@ -52,6 +52,14 @@ public function testElementOptions() { // Check default value handling. $this->drupalPostForm('webform/test_element_options', ['webform_element_options_custom[options]' => 'yes_no'], t('Submit')); $this->assertRaw("webform_element_options_custom: yes_no"); + + // Check unique option value validation. + $edit = [ + 'webform_options[options][items][0][value]' => 'test', + 'webform_options[options][items][1][value]' => 'test', + ]; + $this->drupalPostForm('webform/test_element_options', $edit, t('Submit')); + $this->assertRaw('The <em class="placeholder">Option value</em> \'test\' is already in use. It must be unique.'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementOtherTest.php b/web/modules/webform/src/Tests/Element/WebformElementOtherTest.php index 0ce872f3e4..2f0fdd049a 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementOtherTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementOtherTest.php @@ -22,30 +22,31 @@ class WebformElementOtherTest extends WebformElementTestBase { * Tests options with other elements. */ public function testBuildingOtherElements() { - $this->drupalGet('webform/test_element_other'); + $this->drupalGet('/webform/test_element_other'); /**************************************************************************/ // select_other /**************************************************************************/ // Check basic select_other. + $this->assertRaw('<fieldset data-drupal-selector="edit-select-other-basic" class="js-webform-select-other webform-select-other webform-select-other--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-webform-select-other webform-type-webform-select-other js-form-item form-item js-form-wrapper form-wrapper" id="edit-select-other-basic">'); + $this->assertRaw('<span class="fieldset-legend">Select other basic</span>'); $this->assertRaw('<select data-drupal-selector="edit-select-other-basic-select" id="edit-select-other-basic-select" name="select_other_basic[select]" class="form-select">'); - $this->assertRaw('<input data-drupal-selector="edit-select-other-basic-other" type="text" id="edit-select-other-basic-other" name="select_other_basic[other]" value="Four" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); - $this->assertRaw('<option value="_other_" selected="selected">Other...</option>'); + $this->assertRaw('<input data-drupal-selector="edit-select-other-basic-other" type="text" id="edit-select-other-basic-other" name="select_other_basic[other]" value="Four" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); + $this->assertRaw('<option value="_other_" selected="selected">Other…</option>'); // Check advanced select_other w/ custom label. $this->assertRaw('<span class="fieldset-legend js-form-required form-required">Select other advanced</span>'); $this->assertRaw('<select data-drupal-selector="edit-select-other-advanced-select" id="edit-select-other-advanced-select" name="select_other_advanced[select]" class="form-select required" required="required" aria-required="true">'); $this->assertRaw('<option value="_other_" selected="selected">Is there another option you wish to enter?</option>'); $this->assertRaw('<label for="edit-select-other-advanced-other">Other</label>'); - $this->assertRaw('<input data-drupal-selector="edit-select-other-advanced-other" aria-describedby="edit-select-other-advanced-other--description" type="text" id="edit-select-other-advanced-other" name="select_other_advanced[other]" value="Four" size="20" maxlength="20" placeholder="What is this other option" class="form-text" />'); - $this->assertRaw('<div id="edit-select-other-advanced-other--description" class="description">'); - $this->assertRaw('Other select description'); + $this->assertRaw('<input data-counter-type="character" data-counter-minimum="4" data-counter-maximum="10" class="js-webform-counter webform-counter form-text" data-drupal-selector="edit-select-other-advanced-other" aria-describedby="edit-select-other-advanced-other--description" type="text" id="edit-select-other-advanced-other" name="select_other_advanced[other]" value="Four" size="20" maxlength="10" placeholder="What is this other option" />'); + $this->assertRaw('<div id="edit-select-other-advanced-other--description" class="webform-element-description">Other select description</div>'); // Check multiple select_other. $this->assertRaw('<span class="fieldset-legend">Select other multiple</span>'); $this->assertRaw('<select data-drupal-selector="edit-select-other-multiple-select" multiple="multiple" name="select_other_multiple[select][]" id="edit-select-other-multiple-select" class="form-select">'); - $this->assertRaw('<input data-drupal-selector="edit-select-other-multiple-other" type="text" id="edit-select-other-multiple-other" name="select_other_multiple[other]" value="Four" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); + $this->assertRaw('<input data-drupal-selector="edit-select-other-multiple-other" type="text" id="edit-select-other-multiple-other" name="select_other_multiple[other]" value="Four" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); /**************************************************************************/ // checkboxes_other @@ -54,15 +55,15 @@ public function testBuildingOtherElements() { // Check basic checkboxes. $this->assertRaw('<span class="fieldset-legend">Checkboxes other basic</span>'); $this->assertRaw('<input data-drupal-selector="edit-checkboxes-other-basic-checkboxes-other-" type="checkbox" id="edit-checkboxes-other-basic-checkboxes-other-" name="checkboxes_other_basic[checkboxes][_other_]" value="_other_" checked="checked" class="form-checkbox" />'); - $this->assertRaw('<label for="edit-checkboxes-other-basic-checkboxes-other-" class="option">Other...</label>'); - $this->assertRaw('<input data-drupal-selector="edit-checkboxes-other-basic-other" type="text" id="edit-checkboxes-other-basic-other" name="checkboxes_other_basic[other]" value="Four" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); + $this->assertRaw('<label for="edit-checkboxes-other-basic-checkboxes-other-" class="option">Other…</label>'); + $this->assertRaw('<input data-drupal-selector="edit-checkboxes-other-basic-other" type="text" id="edit-checkboxes-other-basic-other" name="checkboxes_other_basic[other]" value="Four" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); // Check advanced checkboxes. $this->assertRaw('<div id="edit-checkboxes-other-advanced-checkboxes" class="js-webform-checkboxes webform-options-display-two-columns form-checkboxes">'); $this->assertRaw('<span class="fieldset-legend js-form-required form-required">Checkboxes other advanced</span>'); - $this->assertRaw('<input data-drupal-selector="edit-checkboxes-other-advanced-other" aria-describedby="edit-checkboxes-other-advanced-other--description" type="text" id="edit-checkboxes-other-advanced-other" name="checkboxes_other_advanced[other]" value="Four" size="60" maxlength="128" placeholder="What is this other option" class="form-text" />'); - $this->assertRaw('<div id="edit-checkboxes-other-advanced-other--description" class="description">'); - $this->assertRaw('Other checkbox description'); + $this->assertRaw('<input data-drupal-selector="edit-checkboxes-other-advanced-other" aria-describedby="edit-checkboxes-other-advanced-other--description" type="text" id="edit-checkboxes-other-advanced-other" name="checkboxes_other_advanced[other]" value="Four" size="60" maxlength="255" placeholder="What is this other option" class="form-text" />'); + $this->assertRaw('<div id="edit-checkboxes-other-advanced-other--description" class="webform-element-description">Other checkbox description</div>'); + $this->assertRaw('<label for="edit-checkboxes-other-advanced-checkboxes-one" class="option">One<span class="webform-element-help"'); /**************************************************************************/ // radios_other @@ -71,15 +72,15 @@ public function testBuildingOtherElements() { // Check basic radios_other. $this->assertRaw('<span class="fieldset-legend">Radios other basic</span>'); $this->assertRaw('<input data-drupal-selector="edit-radios-other-basic-radios-other-" type="radio" id="edit-radios-other-basic-radios-other-" name="radios_other_basic[radios]" value="_other_" checked="checked" class="form-radio" />'); - $this->assertRaw('<label for="edit-radios-other-basic-radios-other-" class="option">Other...</label>'); - $this->assertRaw('<input data-drupal-selector="edit-radios-other-basic-other" type="text" id="edit-radios-other-basic-other" name="radios_other_basic[other]" value="Four" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); + $this->assertRaw('<label for="edit-radios-other-basic-radios-other-" class="option">Other…</label>'); + $this->assertRaw('<input data-drupal-selector="edit-radios-other-basic-other" type="text" id="edit-radios-other-basic-other" name="radios_other_basic[other]" value="Four" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); // Check advanced radios_other w/ custom label. $this->assertRaw('<span class="fieldset-legend js-form-required form-required">Radios other advanced</span>'); $this->assertRaw('<input data-drupal-selector="edit-radios-other-advanced-radios-other-" type="radio" id="edit-radios-other-advanced-radios-other-" name="radios_other_advanced[radios]" value="_other_" checked="checked" class="form-radio" />'); - $this->assertRaw('<input data-drupal-selector="edit-radios-other-advanced-other" aria-describedby="edit-radios-other-advanced-other--description" type="text" id="edit-radios-other-advanced-other" name="radios_other_advanced[other]" value="Four" size="60" maxlength="128" placeholder="What is this other option" class="form-text" />'); - $this->assertRaw('<div id="edit-radios-other-advanced-other--description" class="description">'); - $this->assertRaw('Other radio description'); + $this->assertRaw('<input data-drupal-selector="edit-radios-other-advanced-other" aria-describedby="edit-radios-other-advanced-other--description" type="text" id="edit-radios-other-advanced-other" name="radios_other_advanced[other]" value="Four" size="60" maxlength="255" placeholder="What is this other option" class="form-text" />'); + $this->assertRaw('<div id="edit-radios-other-advanced-other--description" class="webform-element-description">Other radio description</div>'); + $this->assertRaw('<label for="edit-radios-other-advanced-radios-one" class="option">One<span class="webform-element-help"'); /**************************************************************************/ // buttons_other @@ -89,14 +90,23 @@ public function testBuildingOtherElements() { $this->assertRaw('<span class="fieldset-legend">Buttons other basic</span>'); $this->assertRaw('<input data-drupal-selector="edit-buttons-other-basic-buttons-one" type="radio" id="edit-buttons-other-basic-buttons-one" name="buttons_other_basic[buttons]" value="One" class="form-radio" />'); $this->assertRaw('<label for="edit-buttons-other-basic-buttons-one" class="option">One</label>'); - $this->assertRaw('<input data-drupal-selector="edit-buttons-other-basic-other" type="text" id="edit-buttons-other-basic-other" name="buttons_other_basic[other]" value="Four" size="60" maxlength="128" placeholder="Enter other..." class="form-text" />'); + $this->assertRaw('<input data-drupal-selector="edit-buttons-other-basic-other" type="text" id="edit-buttons-other-basic-other" name="buttons_other_basic[other]" value="Four" size="60" maxlength="255" placeholder="Enter other…" class="form-text" />'); // Check advanced buttons_other w/ custom label. $this->assertRaw('<span class="fieldset-legend js-form-required form-required">Buttons other advanced</span>'); $this->assertRaw('<input data-drupal-selector="edit-buttons-other-advanced-buttons-one" type="radio" id="edit-buttons-other-advanced-buttons-one" name="buttons_other_advanced[buttons]" value="One" class="form-radio" />'); - $this->assertRaw('<input data-drupal-selector="edit-buttons-other-advanced-other" aria-describedby="edit-buttons-other-advanced-other--description" type="text" id="edit-buttons-other-advanced-other" name="buttons_other_advanced[other]" value="Four" size="60" maxlength="128" placeholder="What is this other option" class="form-text" />'); - $this->assertRaw('<div id="edit-buttons-other-advanced-other--description" class="description">'); - $this->assertRaw('Other button description'); + $this->assertRaw('<input data-drupal-selector="edit-buttons-other-advanced-other" aria-describedby="edit-buttons-other-advanced-other--description" type="text" id="edit-buttons-other-advanced-other" name="buttons_other_advanced[other]" value="Four" size="60" maxlength="255" placeholder="What is this other option" class="form-text" />'); + $this->assertRaw('<div id="edit-buttons-other-advanced-other--description" class="webform-element-description">Other button description</div>'); + + /**************************************************************************/ + // wrapper_type + /**************************************************************************/ + + // Check form_item wrapper type. + $this->assertRaw('<div class="js-webform-select-other webform-select-other js-form-item form-item js-form-type-webform-select-other form-type-webform-select-other js-form-item-wrapper-other-form-element form-item-wrapper-other-form-element" id="edit-wrapper-other-form-element">'); + + // Check container wrapper type. + $this->assertRaw('<div data-drupal-selector="edit-wrapper-other-container" class="js-webform-select-other webform-select-other webform-select-other--wrapper fieldgroup form-composite js-form-wrapper form-wrapper" id="edit-wrapper-other-container">'); } /** @@ -133,6 +143,14 @@ public function testProcessingOtherElements() { $this->drupalPostForm('webform/test_element_other', $edit, t('Submit')); $this->assertRaw('Select other advanced field is required.'); + // Check select other processing w/ other min/max character validation. + $edit = [ + 'select_other_advanced[select]' => '_other_', + 'select_other_advanced[other]' => 'X', + ]; + $this->drupalPostForm('webform/test_element_other', $edit, t('Submit')); + $this->assertRaw('Other must be longer than <em class="placeholder">4</em> characters but is currently <em class="placeholder">1</em> characters long.'); + // Check select other processing w/ other. $edit = [ 'select_other_advanced[select]' => '_other_', diff --git a/web/modules/webform/src/Tests/Element/WebformElementPatternTest.php b/web/modules/webform/src/Tests/Element/WebformElementPatternTest.php new file mode 100644 index 0000000000..4a5f62ad11 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementPatternTest.php @@ -0,0 +1,52 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for webform pattern validation. + * + * @group Webform + */ +class WebformElementPatternTest extends WebformElementTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_pattern']; + + /** + * Tests pattern validation. + */ + public function testPattern() { + // Check rendering. + $this->drupalGet('/webform/test_element_pattern'); + $this->assertRaw('<input pattern="Hello" data-drupal-selector="edit-pattern" aria-describedby="edit-pattern--description" type="text" id="edit-pattern" name="pattern" value="" size="60" maxlength="255" class="form-text" />'); + $this->assertRaw('<input pattern="Hello" data-webform-pattern-error="You did not enter 'Hello'" data-drupal-selector="edit-pattern-error" aria-describedby="edit-pattern-error--description" type="text" id="edit-pattern-error" name="pattern_error" value="" size="60" maxlength="255" class="form-text" />'); + $this->assertRaw('<input pattern="\u2E8F" data-drupal-selector="edit-pattern-unicode" aria-describedby="edit-pattern-unicode--description" type="text" id="edit-pattern-unicode" name="pattern_unicode" value="" size="60" maxlength="255" class="form-text" />'); + + // Check validation. + $edit = [ + 'pattern' => 'GoodBye', + 'pattern_error' => 'GoodBye', + 'pattern_unicode' => 'Unicode', + ]; + $this->drupalPostForm('webform/test_element_pattern', $edit, t('Submit')); + $this->assertRaw('<li class="messages__item"><em class="placeholder">pattern</em> field is not in the right format.</li>'); + $this->assertRaw('<li class="messages__item">You did not enter 'Hello'</li>'); + $this->assertRaw('<li class="messages__item"><em class="placeholder">pattern_unicode</em> field is not in the right format.</li>'); + + // Check validation. + $edit = [ + 'pattern' => 'Hello', + 'pattern_error' => 'Hello', + 'pattern_unicode' => '⺏', + ]; + $this->drupalPostForm('webform/test_element_pattern', $edit, t('Submit')); + $this->assertNoRaw('<li class="messages__item"><em class="placeholder">pattern</em> field is not in the right format.</li>'); + $this->assertNoRaw('<li class="messages__item">You did not enter 'Hello'</li>'); + $this->assertNoRaw('<li class="messages__item"><em class="placeholder">pattern_unicode</em> field is not in the right format.</li>'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementPluginTest.php b/web/modules/webform/src/Tests/Element/WebformElementPluginTest.php index ec16bc7050..436b3eaf2c 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementPluginTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementPluginTest.php @@ -38,7 +38,7 @@ public function testElementPlugin() { // Check that managed_file and webform_term-select are not available when // dependent modules are not installed. - $this->drupalGet('admin/structure/webform/plugins/elements'); + $this->drupalGet('/admin/reports/webform-plugins/elements'); $this->assertNoRaw('<td><div class="webform-form-filter-text-source">managed_file</div></td>'); $this->assertNoRaw('<td><div class="webform-form-filter-text-source">webform_term_select</div></td>'); @@ -47,7 +47,7 @@ public function testElementPlugin() { // Check that managed_file and webform_term-select are available when // dependent modules are installed. - $this->drupalGet('admin/structure/webform/plugins/elements'); + $this->drupalGet('/admin/reports/webform-plugins/elements'); $this->assertRaw('<td><div class="webform-form-filter-text-source">managed_file</div></td>'); $this->assertRaw('<td><div class="webform-form-filter-text-source">webform_term_select</div></td>'); @@ -59,7 +59,7 @@ public function testElementPlugin() { $webform_plugin_test = Webform::load('test_element_plugin'); // Check prepare and setDefaultValue(). - $this->drupalGet('webform/test_element_plugin'); + $this->drupalGet('/webform/test_element_plugin'); $this->assertRaw('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement:preCreate'); $this->assertRaw('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement:postCreate'); $this->assertRaw('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement:prepare'); @@ -100,7 +100,7 @@ public function testElementPlugin() { $this->drupalPostForm('/admin/structure/webform/manage/test_element_plugin/submission/' . $sid . '/delete', [], t('Delete')); $this->assertRaw('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement:preDelete'); $this->assertRaw('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement:postDelete'); - $this->assertRaw('Test: Element: Test (plugin): Submission #' . $webform_submission->serial() . ' has been deleted.'); + $this->assertRaw('<em class="placeholder">Test: Element: Test (plugin): Submission #' . $webform_submission->serial() . '</em> has been deleted.'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementPrepopulateTest.php b/web/modules/webform/src/Tests/Element/WebformElementPrepopulateTest.php index f2a4fabc20..60fa1f3f3e 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementPrepopulateTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementPrepopulateTest.php @@ -35,17 +35,17 @@ public function testElementPrepopulate() { $files = $this->drupalGetTestFiles('text'); // Check prepopulation of an element. - $this->drupalGet('webform/test_element_prepopulate'); + $this->drupalGet('/webform/test_element_prepopulate'); $this->assertFieldByName('textfield', ''); $this->assertFieldByName('textfield_prepopulate', ''); $this->assertFieldByName('files[managed_file_prepopulate]', ''); // Check 'textfield' can not be prepopulated. - $this->drupalGet('webform/test_element_prepopulate', ['query' => ['textfield' => 'value']]); + $this->drupalGet('/webform/test_element_prepopulate', ['query' => ['textfield' => 'value']]); $this->assertNoFieldByName('textfield', 'value'); // Check 'textfield_prepopulate' can be prepopulated. - $this->drupalGet('webform/test_element_prepopulate', ['query' => ['textfield_prepopulate' => 'value']]); + $this->drupalGet('/webform/test_element_prepopulate', ['query' => ['textfield_prepopulate' => 'value']]); $this->assertFieldByName('textfield_prepopulate', 'value'); // Check 'managed_file_prepopulate' can not be prepopulated. @@ -57,7 +57,7 @@ public function testElementPrepopulate() { $sid = $this->postSubmission($webform, $edit); $webform_submission = WebformSubmission::load($sid); $fid = $webform_submission->getElementData('managed_file_prepopulate'); - $this->drupalGet('webform/test_element_prepopulate', ['query' => ['managed_file_prepopulate' => $fid]]); + $this->drupalGet('/webform/test_element_prepopulate', ['query' => ['managed_file_prepopulate' => $fid]]); $this->assertFieldByName('files[managed_file_prepopulate]', ''); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementPrivateTest.php b/web/modules/webform/src/Tests/Element/WebformElementPrivateTest.php index 21d50960ba..03a86431a6 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementPrivateTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementPrivateTest.php @@ -18,34 +18,29 @@ class WebformElementPrivateTest extends WebformElementTestBase { */ protected static $testWebforms = ['test_element_private']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test element access. */ public function testElementAccess() { + $normal_user = $this->drupalCreateUser(); + $webform = Webform::load('test_element_private'); + /**************************************************************************/ + + $this->drupalLogin($normal_user); + // Create a webform submission. - $this->drupalLogin($this->normalUser); $this->postSubmission($webform); // Check element with #private property hidden for normal user. - $this->drupalLogin($this->normalUser); - $this->drupalGet('webform/test_element_private'); + $this->drupalGet('/webform/test_element_private'); $this->assertNoFieldByName('private', ''); - // Check element with #private property visible for admin user. $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/test_element_private'); + + // Check element with #private property visible for admin user. + $this->drupalGet('/webform/test_element_private'); $this->assertFieldByName('private', ''); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementRadiosTest.php b/web/modules/webform/src/Tests/Element/WebformElementRadiosTest.php index 3c66982c6b..99cccbbc88 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementRadiosTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementRadiosTest.php @@ -3,7 +3,7 @@ namespace Drupal\webform\Tests\Element; /** - * Tests for webform element radioss. + * Tests for webform element radios. * * @group Webform */ @@ -22,16 +22,16 @@ class WebformElementRadiosTest extends WebformElementTestBase { public function testElementRadios() { $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/test_element_radios'); + $this->drupalGet('/webform/test_element_radios'); // Check radios with description display. $this->assertRaw('<input data-drupal-selector="edit-radios-description-one" aria-describedby="edit-radios-description-one--description" type="radio" id="edit-radios-description-one" name="radios_description" value="one" class="form-radio" />'); $this->assertRaw('<label for="edit-radios-description-one" class="option">One</label>'); - $this->assertRaw('<div id="edit-radios-description-one--description" class="description">'); + $this->assertRaw('<div id="edit-radios-description-one--description" class="webform-element-description">This is a description</div>'); // Check radios with help text display. $this->assertRaw('<input data-drupal-selector="edit-radios-help-one" type="radio" id="edit-radios-help-one" name="radios_help" value="one" class="form-radio" />'); - $this->assertRaw('<label for="edit-radios-help-one" class="option">One<a href="#help" title="This is a description" data-webform-help="This is a description" class="webform-element-help">?</a>'); + $this->assertRaw('<label for="edit-radios-help-one" class="option">One<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">One</div><div class="webform-element-help--content">This is a description</div>"><span aria-hidden="true">?</span></span>'); // Check radios results does not include description. $edit = [ diff --git a/web/modules/webform/src/Tests/Element/WebformElementRangeTest.php b/web/modules/webform/src/Tests/Element/WebformElementRangeTest.php index 242a9e91f2..f89fba0739 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementRangeTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementRangeTest.php @@ -20,7 +20,7 @@ class WebformElementRangeTest extends WebformElementTestBase { * Test range element. */ public function testRating() { - $this->drupalGet('webform/test_element_range'); + $this->drupalGet('/webform/test_element_range'); // Check basic range element. $this->assertRaw('<input data-drupal-selector="edit-range" type="range" id="edit-range" name="range" value="" step="1" min="0" max="100" class="form-range" />'); @@ -39,9 +39,13 @@ public function testRating() { // Check output left range element. $this->assertRaw('<span class="field-prefix"><div class="js-form-item form-item js-form-type-number form-type-number js-form-item-range-output-left__output form-item-range-output-left__output form-no-label">'); + $this->assertRaw('<label for="range_output_left__output" class="visually-hidden">range_output_left</label>'); + $this->assertRaw('<input style="background-color: yellow;width:6em" type="number" id="range_output_left__output" step="100" min="0" max="10000" class="form-number" />'); // Check output right range element. $this->assertRaw('<span class="field-suffix"><span class="webform-range-output-delimiter"></span><div class="js-form-item form-item js-form-type-number form-type-number js-form-item-range-output-disabled__output form-item-range-output-disabled__output form-no-label form-disabled">'); + $this->assertRaw('<label for="range_output_right__output" class="visually-hidden">range_output_right</label>'); + $this->assertRaw('<input style="width:4em" type="number" id="range_output_right__output" step="1" min="0" max="100" class="form-number" />'); // Check processing. $this->drupalPostForm('webform/test_element_range', [], t('Submit')); diff --git a/web/modules/webform/src/Tests/Element/WebformElementRatingTest.php b/web/modules/webform/src/Tests/Element/WebformElementRatingTest.php index b7c161e3cd..4bf0d17604 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementRatingTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementRatingTest.php @@ -20,24 +20,37 @@ class WebformElementRatingTest extends WebformElementTestBase { * Test rating element. */ public function testRating() { - $this->drupalGet('webform/test_element_rating'); + $this->drupalGet('/webform/test_element_rating'); // Check basic rating display. - $this->assertRaw('<label for="edit-rating-basic">Rating basic</label>'); + $this->assertRaw('<label for="edit-rating-basic">rating_basic</label>'); $this->assertRaw('<input data-drupal-selector="edit-rating-basic" type="range" id="edit-rating-basic" name="rating_basic" value="0" step="1" min="0" max="5" class="form-webform-rating" />'); - $this->assertRaw('<div class="rateit svg rateit-medium" data-rateit-min="0" data-rateit-max="5" data-rateit-step="1" data-rateit-resetable="false" data-rateit-readonly="false" data-rateit-backingfld="#edit-rating-basic" data-rateit-value="" data-rateit-starheight="24" data-rateit-starwidth="24">'); + $this->assertRaw('<div class="rateit svg rateit-medium" data-rateit-min="0" data-rateit-max="5" data-rateit-step="1" data-rateit-resetable="false" data-rateit-readonly="false" data-rateit-backingfld="[data-drupal-selector="edit-rating-basic"]" data-rateit-value="" data-rateit-starheight="24" data-rateit-starwidth="24">'); // Check advanced rating display. - $this->assertRaw('<label for="edit-rating-advanced">Rating advanced</label>'); + $this->assertRaw('<label for="edit-rating-advanced">rating_advanced</label>'); $this->assertRaw('<input data-drupal-selector="edit-rating-advanced" type="range" id="edit-rating-advanced" name="rating_advanced" value="0" step="0.1" min="0" max="10" class="form-webform-rating" />'); - $this->assertRaw('<div class="rateit svg rateit-large" data-rateit-min="0" data-rateit-max="10" data-rateit-step="0.1" data-rateit-resetable="true" data-rateit-readonly="false" data-rateit-backingfld="#edit-rating-advanced" data-rateit-value="" data-rateit-starheight="32" data-rateit-starwidth="32">'); + $this->assertRaw('<div class="rateit svg rateit-large" data-rateit-min="0" data-rateit-max="10" data-rateit-step="0.1" data-rateit-resetable="true" data-rateit-readonly="false" data-rateit-backingfld="[data-drupal-selector="edit-rating-advanced"]" data-rateit-value="" data-rateit-starheight="32" data-rateit-starwidth="32">'); + + // Check required rating display. + $this->assertRaw('<label for="edit-rating-required" class="js-form-required form-required">rating_required</label>'); + $this->assertRaw('<input data-drupal-selector="edit-rating-required" type="range" id="edit-rating-required" name="rating_required" value="0" step="1" min="0" max="5" class="form-webform-rating required" required="required" aria-required="true" />'); + $this->assertRaw('<div class="rateit svg rateit-medium" data-rateit-min="0" data-rateit-max="5" data-rateit-step="1" data-rateit-resetable="false" data-rateit-readonly="false" data-rateit-backingfld="[data-drupal-selector="edit-rating-required"]" data-rateit-value="" data-rateit-starheight="24" data-rateit-starwidth="24"></div>'); // Check processing. $edit = [ - 'rating_basic' => '4', + 'rating_basic' => '1', + 'rating_advanced' => '2', + 'rating_required' => '3', ]; $this->drupalPostForm('webform/test_element_rating', $edit, t('Submit')); - $this->assertRaw("rating_basic: '4'"); + $this->assertRaw("rating_basic: '1' +rating_advanced: '2' +rating_required: '3'"); + + // Check required validation. + $this->drupalPostForm('webform/test_element_rating', [], t('Submit')); + $this->assertRaw('rating_required field is required.'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementReadonlyTest.php b/web/modules/webform/src/Tests/Element/WebformElementReadonlyTest.php index debd284e4c..c42b321ac1 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementReadonlyTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementReadonlyTest.php @@ -20,7 +20,7 @@ class WebformElementReadonlyTest extends WebformElementTestBase { * Tests element readonly. */ public function testReadonly() { - $this->drupalGet('webform/test_element_readonly'); + $this->drupalGet('/webform/test_element_readonly'); $this->assertRaw('<div class="webform-readonly js-form-item form-item js-form-type-textfield form-type-textfield js-form-item-textfield form-item-textfield">'); $this->assertRaw('<input readonly="readonly" data-drupal-selector="edit-textfield" type="text" id="edit-textfield" name="textfield" value="" size="60" maxlength="255" class="form-text" />'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementSectionTest.php b/web/modules/webform/src/Tests/Element/WebformElementSectionTest.php index 332c776acd..9b60c96450 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementSectionTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementSectionTest.php @@ -20,33 +20,29 @@ class WebformElementSectionTest extends WebformElementTestBase { * Test element section. */ public function testSection() { - $this->drupalGet('webform/test_element_section'); + $this->drupalGet('/webform/test_element_section'); // Check section element. - $this->assertRaw('<section data-drupal-selector="edit-webform-section" aria-describedby="edit-webform-section--description" id="edit-webform-section" class="js-form-item form-item js-form-wrapper form-wrapper webform-section">'); - - // Check section help. - $this->assertRaw('<h2 class="webform-section-title">webform_section<a href="#help" title="{This is help text}" data-webform-help="{This is help text}" class="webform-element-help">?</a>'); - - // Check section description. - $this->assertRaw('<div id="edit-webform-section--description" class="description">{This is a description}</div>'); - - // Check section required. - $this->assertRaw('<section data-drupal-selector="edit-webform-section-required" id="edit-webform-section-required" class="required js-form-item form-item js-form-wrapper form-wrapper webform-section" required="required" aria-required="true">'); - $this->assertRaw('<h2 class="webform-section-title js-form-required form-required">webform_section_required</h2>'); + $this->assertRaw('<section data-drupal-selector="edit-webform-section" aria-describedby="edit-webform-section--description" id="edit-webform-section" class="required js-form-item form-item js-form-wrapper form-wrapper webform-section" required="required" aria-required="true">'); + $this->assertRaw('<h2 class="webform-section-title js-form-required form-required">webform_section<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">webform_section</div><div class="webform-element-help--content">This is help text.</div>"><span aria-hidden="true">?</span></span>'); + $this->assertRaw('<div class="description"><div id="edit-webform-section--description" class="webform-element-description">This is a description.</div>'); + $this->assertRaw('<div id="edit-webform-section--more" class="js-webform-element-more webform-element-more">'); // Check custom h5 title tag. $this->assertRaw('<section data-drupal-selector="edit-webform-section-title-custom" id="edit-webform-section-title-custom" class="js-form-item form-item js-form-wrapper form-wrapper webform-section">'); $this->assertRaw('<h5 style="color: red" class="webform-section-title">webform_section_title_custom</h5>'); + // Check section title_display: invisible. + $this->assertRaw('<h2 class="visually-hidden webform-section-title">webform_section_title_invisible</h2>'); + // Check change default title tag. \Drupal::configFactory()->getEditable('webform.settings') ->set('element.default_section_title_tag', 'address') ->save(); - $this->drupalGet('webform/test_element_section'); - $this->assertNoRaw('<h2 class="webform-section-title">'); - $this->assertRaw('<address class="webform-section-title">'); + $this->drupalGet('/webform/test_element_section'); + $this->assertNoRaw('<h2 class="webform-section-title js-form-required form-required">'); + $this->assertRaw('<address class="webform-section-title js-form-required form-required">'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementSelectTest.php b/web/modules/webform/src/Tests/Element/WebformElementSelectTest.php index ac591492ad..e67083ac7b 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementSelectTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementSelectTest.php @@ -21,7 +21,7 @@ class WebformElementSelectTest extends WebformElementTestBase { */ public function testSelectElement() { // Check default empty option always included. - $this->drupalGet('webform/test_element_select'); + $this->drupalGet('/webform/test_element_select'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-optional" id="edit-select-empty-option-optional" name="select_empty_option_optional" class="form-select"><option value="" selected="selected">- None -</option>'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-optional-default-value" id="edit-select-empty-option-optional-default-value" name="select_empty_option_optional_default_value" class="form-select"><option value="">- None -</option>'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-required" id="edit-select-empty-option-required" name="select_empty_option_required" class="form-select required" required="required" aria-required="true"><option value="" selected="selected">- Select -</option>'); @@ -32,7 +32,7 @@ public function testSelectElement() { ->save(); // Check default empty option is not always included. - $this->drupalGet('webform/test_element_select'); + $this->drupalGet('/webform/test_element_select'); $this->assertNoRaw('<select data-drupal-selector="edit-select-empty-option-optional" id="edit-select-empty-option-optional" name="select_empty_option_optional" class="form-select"><option value="" selected="selected">- None -</option>'); $this->assertNoRaw('<select data-drupal-selector="edit-select-empty-option-optional-default-value" id="edit-select-empty-option-optional-default-value" name="select_empty_option_optional_default_value" class="form-select"><option value="">- None -</option>'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-required" id="edit-select-empty-option-required" name="select_empty_option_required" class="form-select required" required="required" aria-required="true"><option value="" selected="selected">- Select -</option>'); @@ -45,7 +45,7 @@ public function testSelectElement() { ->save(); // Check customize empty option displayed. - $this->drupalGet('webform/test_element_select'); + $this->drupalGet('/webform/test_element_select'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-optional" id="edit-select-empty-option-optional" name="select_empty_option_optional" class="form-select"><option value="" selected="selected">{optional}</option>'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-optional-default-value" id="edit-select-empty-option-optional-default-value" name="select_empty_option_optional_default_value" class="form-select"><option value="">{optional}</option>'); $this->assertRaw('<select data-drupal-selector="edit-select-empty-option-required" id="edit-select-empty-option-required" name="select_empty_option_required" class="form-select required" required="required" aria-required="true"><option value="" selected="selected">{required}</option>'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementSignatureTest.php b/web/modules/webform/src/Tests/Element/WebformElementSignatureTest.php index 44228be7c1..470b95b504 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementSignatureTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementSignatureTest.php @@ -31,13 +31,12 @@ public function testSignature() { $signature_directory = 'public://webform/test_element_signature/signature'; // Check signature display. - $this->drupalGet('webform/test_element_signature'); + $this->drupalGet('/webform/test_element_signature'); $this->assertRaw('<input data-drupal-selector="edit-signature" aria-describedby="edit-signature--description" type="hidden" name="signature" value="" class="js-webform-signature form-webform-signature" />'); $this->assertRaw('<input type="submit" name="op" value="Reset" class="button js-form-submit form-submit" />'); $this->assertRaw('<canvas></canvas>'); $this->assertRaw('</div>'); - $this->assertRaw('<div id="edit-signature--description" class="description">'); - $this->assertRaw('Sign above'); + $this->assertRaw('<div id="edit-signature--description" class="webform-element-description">Sign above</div>'); // Check signature preview image. $this->postSubmissionTest($webform, [], t('Preview')); diff --git a/web/modules/webform/src/Tests/Element/WebformElementStatesSelectorsTest.php b/web/modules/webform/src/Tests/Element/WebformElementStatesSelectorsTest.php index 3c6870e6c7..1f9e16bc4a 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementStatesSelectorsTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementStatesSelectorsTest.php @@ -36,6 +36,10 @@ public function setUp() { // Create 'tags' vocabulary. $this->createTags(); + \Drupal::configFactory()->getEditable('webform.settings') + ->set('libraries.excluded_libraries', []) + ->save(); + // Enable all elements, including password and password_confirm. \Drupal::configFactory()->getEditable('webform.settings') ->set('element.excluded_elements', []) @@ -51,7 +55,7 @@ public function testSelectors() { $webform = Webform::load($webform_id); $webform->setStatus(WebformInterface::STATUS_OPEN)->save(); - $this->drupalGet('webform/' . $webform_id); + $this->drupalGet('/webform/' . $webform_id); $selectors = OptGroup::flattenOptions($webform->getElementsSelectorOptions()); // Ignore text format and captcha selectors which are not available during diff --git a/web/modules/webform/src/Tests/Element/WebformElementStatesTest.php b/web/modules/webform/src/Tests/Element/WebformElementStatesTest.php index 52c85e80c3..7b4f07221f 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementStatesTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementStatesTest.php @@ -87,7 +87,7 @@ public function testElement() { // Rendering. /**************************************************************************/ - $this->drupalGet('webform/test_element_states'); + $this->drupalGet('/webform/test_element_states'); // Check 'States custom selector'. $this->assertRaw('<option value="custom_selector" selected="selected">custom_selector</option>'); @@ -101,8 +101,22 @@ public function testElement() { $this->assertRaw('<textarea data-drupal-selector="edit-states-unsupported-nesting-states" aria-describedby="edit-states-unsupported-nesting-states--description" class="js-webform-codemirror webform-codemirror yaml form-textarea resize-vertical" data-webform-codemirror-mode="text/x-yaml" id="edit-states-unsupported-nesting-states" name="states_unsupported_nesting[states]" rows="5" cols="60">'); // Check 'States single' (#multiple: FALSE) - $this->assertFieldById('edit-states-empty-add'); - $this->assertNoFieldById('edit-states-single-add'); + $this->assertFieldById('edit-states-empty-actions-add'); + $this->assertNoFieldById('edit-states-single-actions-add'); + + /**************************************************************************/ + // Validation. + /**************************************************************************/ + + // Check duplicate states validation. + $edit = ['states_basic[states][0][state]' => 'required']; + $this->drupalPostForm('webform/test_element_states', $edit, t('Submit')); + $this->assertRaw('The <em class="placeholder">Required</em> state is declared more than once. There can only be one declaration per state.'); + + // Check duplicate selectors validation. + $edit = ['states_basic[states][3][selector]' => 'selector_02']; + $this->drupalPostForm('webform/test_element_states', $edit, t('Submit')); + $this->assertRaw('The <em class="placeholder">Selector 02 (selector_02)</em> element is used more than once within the <em class="placeholder">Required</em> state. To use multiple values within a trigger try using the pattern trigger.'); /**************************************************************************/ // Processing. @@ -156,6 +170,25 @@ public function testElement() { $this->assertNoFieldByName('states_empty[states][2][selector]', 'selector_02'); $this->assertNoFieldByName('states_empty[states][2][trigger]', 'value'); $this->assertNoFieldByName('states_empty[states][2][value]', '{value_02}'); + + /**************************************************************************/ + // Edit source. + /**************************************************************************/ + + // Check that 'Edit source' button is not available. + $this->drupalGet('/webform/test_element_states'); + $this->assertNoRaw('<input class="button button--danger js-form-submit form-submit" data-drupal-selector="edit-states-basic-actions-source" formnovalidate="formnovalidate" type="submit" id="edit-states-basic-actions-source" name="states_basic_table_source" value="Edit source" />'); + + // Check that 'Edit source' button is available. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/webform/test_element_states'); + $this->assertRaw('<input class="button button--danger js-form-submit form-submit" data-drupal-selector="edit-states-basic-actions-source" formnovalidate="formnovalidate" type="submit" id="edit-states-basic-actions-source" name="states_basic_table_source" value="Edit source" />'); + $this->assertNoFieldByName('states_basic[states]'); + + // Check that 'source' is editable. + $this->drupalPostAjaxForm(NULL, [], 'states_basic_table_source'); + $this->assertRaw('Creating custom conditional logic (Form API #states) with nested conditions or custom selectors will disable the conditional logic builder. This will require that Form API #states be manually entered.'); + $this->assertFieldByName('states_basic[states]'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsReplaceTest.php b/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsReplaceTest.php new file mode 100644 index 0000000000..a1a18ddae0 --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsReplaceTest.php @@ -0,0 +1,84 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for webform submission views replace element. + * + * @group Webform + */ +class WebformElementSubmissionViewsReplaceTest extends WebformElementTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['views', 'node', 'webform', 'webform_node']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_submission_views_r']; + + /** + * Test webform submission views replace element. + */ + public function testSubmissionViewsReplace() { + // Check rendering. + $this->drupalGet('/webform/test_element_submission_views_r'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-global-global-routes" id="edit-webform-submission-views-replace-global-global-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-global-webform-routes" id="edit-webform-submission-views-replace-global-webform-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-global-node-routes" id="edit-webform-submission-views-replace-global-node-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + + // Check that the webform replace element is hidden. + $this->assertNoRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-webform-routes" id="edit-webform-submission-views-replace-webform-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertNoRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-node-routes" id="edit-webform-submission-views-replace-node-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + + // Check processing clears hidden webform_submission_views_replace. + $this->drupalPostForm('webform/test_element_submission_views_r', [], t('Submit')); + $this->assertRaw("webform_submission_views_replace_global: + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions +webform_submission_views_replace: + webform_routes: { } + node_routes: { }"); + + // Clear default_submission_views_replace. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('settings.default_submission_views_replace', [ + 'global_routes' => [], + 'webform_routes' => [], + 'node_routes' => [], + ]) + ->save(); + + // Check that the webform replace element is visible. + $this->drupalGet('/webform/test_element_submission_views_r'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-webform-routes" id="edit-webform-submission-views-replace-webform-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-submission-views-replace-node-routes" id="edit-webform-submission-views-replace-node-routes--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + + // Check processing with webform replace element is visible. + $this->drupalPostForm('webform/test_element_submission_views_r', [], t('Submit')); + $this->assertRaw("webform_submission_views_replace_global: + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions +webform_submission_views_replace: + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions"); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsTest.php b/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsTest.php new file mode 100644 index 0000000000..725abc823c --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementSubmissionViewsTest.php @@ -0,0 +1,139 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for webform submission views element. + * + * @group Webform + */ +class WebformElementSubmissionViewsTest extends WebformElementTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['views', 'node', 'webform', 'webform_node']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_submission_views']; + + /** + * Test webform submission views element. + */ + public function testSubmissionViews() { + // Check global and webform rendering. + $this->drupalGet('/webform/test_element_submission_views'); + $this->assertRaw('<th class="webform_submission_views_global-table--name_title_view webform-multiple-table--name_title_view">'); + $this->assertRaw('<th class="webform_submission_views_global-table--global_routes webform-multiple-table--global_routes">'); + $this->assertRaw('<th class="webform_submission_views_global-table--webform_routes webform-multiple-table--webform_routes">'); + $this->assertRaw('<th class="webform_submission_views_global-table--node_routes webform-multiple-table--node_routes">'); + $this->assertRaw('<th class="webform_submission_views-table--name_title_view webform-multiple-table--name_title_view">'); + $this->assertNoRaw('<th class="webform_submission_views-table--global_routes webform-multiple-table--global_routes">'); + $this->assertRaw('<th class="webform_submission_views-table--webform_routes webform-multiple-table--webform_routes">'); + $this->assertRaw('<th class="webform_submission_views-table--node_routes webform-multiple-table--node_routes">'); + + // Check name validation. + $edit = ['webform_submission_views_global[items][0][name]' => '']; + $this->drupalPostForm('webform/test_element_submission_views', $edit, t('Submit')); + $this->assertRaw('Name is required'); + + // Check view validation. + $edit = ['webform_submission_views_global[items][0][view]' => '']; + $this->drupalPostForm('webform/test_element_submission_views', $edit, t('Submit')); + $this->assertRaw('View name/display id is required.'); + + // Check title validation. + $edit = ['webform_submission_views_global[items][0][title]' => '']; + $this->drupalPostForm('webform/test_element_submission_views', $edit, t('Submit')); + $this->assertRaw('Title is required.'); + + // Check processing. + $this->drupalPostForm('webform/test_element_submission_views', [], t('Submit')); + $this->assertRaw("webform_submission_views_global: + admin: + view: 'webform_submissions:embed_administer' + title: Admin + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions +webform_submission_views: + admin: + view: 'webform_submissions:embed_administer' + title: Admin + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions"); + + // Check processing empty record. + $edit = [ + 'webform_submission_views_global[items][0][name]' => '', + 'webform_submission_views_global[items][0][view]' => '', + 'webform_submission_views_global[items][0][title]' => '', + 'webform_submission_views_global[items][0][global_routes][entity.webform_submission.collection]' => FALSE, + 'webform_submission_views_global[items][0][webform_routes][entity.webform.results_submissions]' => FALSE, + 'webform_submission_views_global[items][0][node_routes][entity.node.webform.results_submissions]' => FALSE, + ]; + $this->drupalPostForm('webform/test_element_submission_views', $edit, t('Submit')); + $this->assertNoRaw('Name is required'); + $this->assertNoRaw('View name/display id is required.'); + $this->assertNoRaw('Title is required.'); + $this->assertRaw("webform_submission_views_global: { } +webform_submission_views: + admin: + view: 'webform_submissions:embed_administer' + title: Admin + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions"); + + // Uninstall the webform node module. + $this->container->get('module_installer')->uninstall(['webform_node']); + + // Check global and webform rendering without node settings. + $this->drupalGet('/webform/test_element_submission_views'); + $this->assertNoRaw('<th class="webform_submission_views_global-table--node_routes webform-multiple-table--node_routes">'); + $this->assertNoRaw('<th class="webform_submission_views-table--node_routes webform-multiple-table--node_routes">'); + + // Check processing removes node settings. + $this->drupalPostForm('webform/test_element_submission_views', [], t('Submit')); + $this->assertRaw("webform_submission_views_global: + admin: + view: 'webform_submissions:embed_administer' + title: Admin + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions +webform_submission_views: + admin: + view: 'webform_submissions:embed_administer' + title: Admin + webform_routes: + - entity.webform.results_submissions"); + + // Uninstall the views module. + $this->container->get('module_installer')->uninstall(['views']); + + // Check that element is completely hidden. + $this->drupalGet('/webform/test_element_submission_views'); + $this->assertNoRaw('<th class="webform_submission_views_global-table--name_title_view webform-multiple-table--name_title_view">'); + $this->assertNoRaw('<th class="webform_submission_views-table--name_title_view webform-multiple-table--name_title_view">'); + + // Check that value is preserved. + $this->drupalPostForm('webform/test_element_submission_views', [], t('Submit')); + $this->assertRaw("webform_submission_views_global: { } +webform_submission_views: { }"); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementTableTest.php b/web/modules/webform/src/Tests/Element/WebformElementTableTest.php index ab8c11c915..40c6bd7d0b 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementTableTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementTableTest.php @@ -30,7 +30,7 @@ public function testTable() { /**************************************************************************/ // Check display elements within a table. - $this->drupalGet('webform/test_element_table'); + $this->drupalGet('/webform/test_element_table'); $this->assertRaw('<table class="js-form-wrapper responsive-enabled" data-drupal-selector="edit-table" id="edit-table" data-striping="1">'); $this->assertRaw('<th>First Name</th>'); $this->assertRaw('<th>Last Name</th>'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementTelephoneTest.php b/web/modules/webform/src/Tests/Element/WebformElementTelephoneTest.php new file mode 100644 index 0000000000..78fd05c20e --- /dev/null +++ b/web/modules/webform/src/Tests/Element/WebformElementTelephoneTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Drupal\webform\Tests\Element; + +/** + * Tests for telephone element. + * + * @group Webform + */ +class WebformElementTelephoneTest extends WebformElementTestBase { + + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'telephone_validation']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_element_telephone']; + + /** + * Test telephone element. + */ + public function testRating() { + $this->drupalGet('/webform/test_element_telephone'); + + // Check basic tel. + $this->assertRaw('<input data-drupal-selector="edit-tel-default" type="tel" id="edit-tel-default" name="tel_default" value="" size="30" maxlength="128" class="form-tel" />'); + + // Check international tel. + $this->assertRaw('<input class="js-webform-telephone-international webform-webform-telephone-international form-tel" data-drupal-selector="edit-tel-international" type="tel" id="edit-tel-international" name="tel_international" value="" size="30" maxlength="128" />'); + + // Check international telephone valddation. + $this->assertRaw('<input data-drupal-selector="edit-tel-validation-e164" type="tel" id="edit-tel-validation-e164" name="tel_validation_e164" value="" size="30" maxlength="128" class="form-tel" />'); + + // Check USE telephone validation. + $this->assertRaw('<input data-drupal-selector="edit-tel-validation-national" aria-describedby="edit-tel-validation-national--description" type="tel" id="edit-tel-validation-national" name="tel_validation_national" value="" size="30" maxlength="128" class="form-tel" />'); + + // Check telephone validation missing plus sign. + $edit = [ + 'tel_validation_e164' => '12024561111', + 'tel_validation_national' => '12024561111', + ]; + $this->drupalPostForm('webform/test_element_telephone', $edit, t('Submit')); + $this->assertRaw('The phone number <em class="placeholder">12024561111</em> is not valid.'); + + // Check telephone validation with plus sign. + $edit = [ + 'tel_validation_e164' => '+12024561111', + 'tel_validation_national' => '+12024561111', + ]; + $this->drupalPostForm('webform/test_element_telephone', $edit, t('Submit')); + $this->assertNoRaw('The phone number <em class="placeholder">12024561111</em> is not valid.'); + + // Check telephone validation with non US number. + $edit = [ + 'tel_validation_national' => '+74956970349', + ]; + $this->drupalPostForm('webform/test_element_telephone', $edit, t('Submit')); + $this->assertNoRaw('The phone number <em class="placeholder">+74956970349</em> is not valid.'); + } + +} diff --git a/web/modules/webform/src/Tests/Element/WebformElementTermReferenceTest.php b/web/modules/webform/src/Tests/Element/WebformElementTermReferenceTest.php index 5e666d2aa2..73f9db4261 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementTermReferenceTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementTermReferenceTest.php @@ -45,16 +45,16 @@ public function testTermReference() { // Term checkboxes /**************************************************************************/ - $this->drupalGet('webform/test_element_term_reference'); + $this->drupalGet('/webform/test_element_term_reference'); // Check term checkboxes tree default. - $this->assertRaw('<fieldset data-drupal-selector="edit-webform-term-checkboxes-tree-default" class="js-webform-term-checkboxes webform-term-checkboxes webform-term-checkboxes-scroll fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper" id="edit-webform-term-checkboxes-tree-default--wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-term-checkboxes-tree-default" class="js-webform-term-checkboxes webform-term-checkboxes webform-term-checkboxes-scroll webform-term-checkboxes--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-webform-term-checkboxes webform-type-webform-term-checkboxes js-form-item form-item js-form-wrapper form-wrapper" id="edit-webform-term-checkboxes-tree-default--wrapper">'); $this->assertRaw('<span class="field-prefix"> </span>'); $this->assertRaw('<input data-drupal-selector="edit-webform-term-checkboxes-tree-default-2" type="checkbox" id="edit-webform-term-checkboxes-tree-default-2" name="webform_term_checkboxes_tree_default[2]" value="2" class="form-checkbox" />'); $this->assertRaw('<label for="edit-webform-term-checkboxes-tree-default-2" class="option">Parent 1: Child 1</label>'); // Check term checkboxes tree advanced. - $this->assertRaw('<fieldset data-drupal-selector="edit-webform-term-checkboxes-tree-advanced" class="js-webform-term-checkboxes webform-term-checkboxes fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper" id="edit-webform-term-checkboxes-tree-advanced--wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-webform-term-checkboxes-tree-advanced" class="js-webform-term-checkboxes webform-term-checkboxes webform-term-checkboxes--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-webform-term-checkboxes webform-type-webform-term-checkboxes js-form-item form-item js-form-wrapper form-wrapper" id="edit-webform-term-checkboxes-tree-advanced--wrapper">'); $this->assertRaw('<span class="field-prefix">..</span>'); $this->assertRaw('<input data-drupal-selector="edit-webform-term-checkboxes-tree-advanced-2" type="checkbox" id="edit-webform-term-checkboxes-tree-advanced-2" name="webform_term_checkboxes_tree_advanced[2]" value="2" class="form-checkbox" />'); $this->assertRaw('<label for="edit-webform-term-checkboxes-tree-advanced-2" class="option">Parent 1: Child 1</label>'); @@ -75,7 +75,7 @@ public function testTermReference() { // Term select. /**************************************************************************/ - $this->drupalGet('webform/test_element_term_reference'); + $this->drupalGet('/webform/test_element_term_reference'); // Check term select tree default. $this->assertRaw('<option value="1">Parent 1</option>'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementTermsOfServiceTest.php b/web/modules/webform/src/Tests/Element/WebformElementTermsOfServiceTest.php index 9e3c6c0cdd..a1f8d3d759 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementTermsOfServiceTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementTermsOfServiceTest.php @@ -28,12 +28,19 @@ class WebformElementTermsOfServiceTest extends WebformElementTestBase { */ public function testTermsOfService() { // Check rendering. - $this->drupalGet('webform/test_element_terms_of_service'); + $this->drupalGet('/webform/test_element_terms_of_service'); + + // Check modal. $this->assertRaw('<div data-webform-terms-of-service-type="modal" class="form-type-webform-terms-of-service js-form-type-webform-terms-of-service js-form-item form-item js-form-type-checkbox form-type-checkbox js-form-item-terms-of-service-default form-item-terms-of-service-default">'); $this->assertRaw('<input data-drupal-selector="edit-terms-of-service-default" type="checkbox" id="edit-terms-of-service-default" name="terms_of_service_default" value class="form-checkbox required" required="required" aria-required="true" />'); - $this->assertRaw('<label for="edit-terms-of-service-default" class="option js-form-required form-required">I agree to the <a>terms of service</a>. (default)</label>'); - $this->assertRaw('<div id="edit-terms-of-service-default--description" class="description">'); - $this->assertRaw('<div class="webform-terms-of-service-details js-hide"><div class="webform-terms-of-service-details--title">terms_of_service_default</div><div class="webform-terms-of-service-details--content">These are the terms of service.</div></div>'); + $this->assertRaw('<label for="edit-terms-of-service-default" class="option js-form-required form-required">I agree to the <a role="button" href="#terms">terms of service</a>. (default)</label>'); + $this->assertRaw('<div id="edit-terms-of-service-default--description" class="webform-element-description">'); + $this->assertRaw('<div id="webform-terms-of-service-terms_of_service_default--description" class="webform-terms-of-service-details js-hide">'); + $this->assertRaw('<div class="webform-terms-of-service-details--title">terms_of_service_default</div>'); + $this->assertRaw('<div class="webform-terms-of-service-details--content">These are the terms of service.</div>'); + + // Check slideout. + $this->assertRaw('<label for="edit-terms-of-service-slideout" class="option">I agree to the <a role="button" href="#terms">terms of service</a>. (slideout)</label>'); // Check validation. $this->drupalPostForm('webform/test_element_terms_of_service', [], t('Preview')); diff --git a/web/modules/webform/src/Tests/Element/WebformElementTextFormatTest.php b/web/modules/webform/src/Tests/Element/WebformElementTextFormatTest.php index b404936bbf..382817fc21 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementTextFormatTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementTextFormatTest.php @@ -2,7 +2,9 @@ namespace Drupal\webform\Tests\Element; +use Drupal\file\Entity\File; use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; /** * Tests for text format element. @@ -16,7 +18,7 @@ class WebformElementTextFormatTest extends WebformElementTestBase { * * @var array */ - public static $modules = ['filter', 'webform']; + public static $modules = ['filter', 'file', 'webform']; /** * Webforms to load. @@ -25,14 +27,35 @@ class WebformElementTextFormatTest extends WebformElementTestBase { */ protected static $testWebforms = ['test_element_text_format']; + /** + * File usage manager. + * + * @var \Drupal\file\FileUsage\FileUsageInterface + */ + protected $fileUsage; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->fileUsage = $this->container->get('file.usage'); + } + /** * Test text format element. */ public function testTextFormat() { - $webform_text_format = Webform::load('test_element_text_format'); + $webform = Webform::load('test_element_text_format'); + + // Check that formats and tips are removed and/or hidden. + $this->drupalGet('/webform/test_element_text_format'); + $this->assertRaw('<div class="filter-wrapper js-form-wrapper form-wrapper" data-drupal-selector="edit-text-format-format" style="display: none" id="edit-text-format-format">'); + $this->assertRaw('<div class="filter-help js-form-wrapper form-wrapper" data-drupal-selector="edit-text-format-format-help" style="display: none" id="edit-text-format-format-help">'); // Check 'text_format' values. - $this->drupalGet('webform/test_element_text_format'); + $this->drupalGet('/webform/test_element_text_format'); $this->assertFieldByName('text_format[value]', 'The quick brown fox jumped over the lazy dog.'); $this->assertRaw('No HTML tags allowed.'); @@ -40,9 +63,123 @@ public function testTextFormat() { 'value' => 'Custom value', 'format' => 'custom_format', ]; - $form = $webform_text_format->getSubmissionForm(['data' => ['text_format' => $text_format]]); + $form = $webform->getSubmissionForm(['data' => ['text_format' => $text_format]]); $this->assertEqual($form['elements']['text_format']['#default_value'], $text_format['value']); $this->assertEqual($form['elements']['text_format']['#format'], $text_format['format']); } + /** + * Tests webform text format element files. + */ + public function testTextFormatFiles() { + $this->createFilters(); + + $webform = Webform::load('test_element_text_format'); + + $this->drupalLogin($this->rootUser); + + // Create three test images. + /** @var \Drupal\file\FileInterface[] $images */ + $images = $this->drupalGetTestFiles('image'); + $images = array_slice($images, 0, 5); + foreach ($images as $index => $image_file) { + $images[$index] = File::create((array) $image_file); + $images[$index]->save(); + } + // Check that all images are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Upload the first image. + $edit = [ + 'text_format[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/>', + 'text_format[format]' => 'full_html', + ]; + $sid = $this->postSubmission($webform, $edit); + $this->reloadImages($images); + + // Check that first image is not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check create first image file usage. + $this->assertIdentical(['editor' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + + // Upload the second image. + $edit = [ + 'text_format[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/><img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + 'text_format[format]' => 'full_html', + ]; + $this->drupalPostForm("/admin/structure/webform/manage/test_element_text_format/submission/$sid/edit", $edit, t('Save')); + $this->reloadImages($images); + + // Check that first and second image are not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical(['editor' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + $this->assertIdentical(['editor' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Remove the first image. + $edit = [ + 'text_format[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + 'text_format[format]' => 'full_html', + ]; + $this->drupalPostForm("/admin/structure/webform/manage/test_element_text_format/submission/$sid/edit", $edit, t('Save')); + $this->reloadImages($images); + + // Check that first is temporary and second image is not temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical([], $this->fileUsage->listUsage($images[0]), 'The file has 0 usage.'); + $this->assertIdentical(['editor' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Duplicate submission. + $webform_submission = WebformSubmission::load($sid); + $webform_submission_duplicate = $webform_submission->createDuplicate(); + $webform_submission_duplicate->save(); + + // Check second image file usage. + $this->assertIdentical(['editor' => ['webform_submission' => [$webform_submission->id() => '1', $webform_submission_duplicate->id() => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 2 usages.'); + + // Delete the duplicate webform submission. + $webform_submission_duplicate->delete(); + + // Check second image file usage. + $this->assertIdentical(['editor' => ['webform_submission' => [$sid => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Delete the webform submission. + $this->drupalPostForm("/admin/structure/webform/manage/test_element_text_format/submission/$sid/delete", [], t('Delete')); + $this->reloadImages($images); + + // Check that first and second image are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Reload images. + * + * @param array $images + * An array of image files. + */ + protected function reloadImages(array &$images) { + \Drupal::entityTypeManager()->getStorage('file')->resetCache(); + foreach ($images as $index => $image) { + $images[$index] = File::load($image->id()); + } + } + } diff --git a/web/modules/webform/src/Tests/Element/WebformElementTextTest.php b/web/modules/webform/src/Tests/Element/WebformElementTextTest.php deleted file mode 100644 index 1eed8c4798..0000000000 --- a/web/modules/webform/src/Tests/Element/WebformElementTextTest.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -namespace Drupal\webform\Tests\Element; - -/** - * Tests for webform text elements. - * - * @group Webform - */ -class WebformElementTextTest extends WebformElementTestBase { - - /** - * Modules to enable. - * - * @var array - */ - public static $modules = ['filter', 'webform']; - - /** - * Webforms to load. - * - * @var array - */ - protected static $testWebforms = ['test_element_text']; - - /** - * Tests text elements. - */ - public function testTextElements() { - - /**************************************************************************/ - // text format - /**************************************************************************/ - - // Check that formats and tips are removed and/or hidden. - $this->drupalGet('webform/test_element_text'); - $this->assertRaw('<div class="filter-wrapper js-form-wrapper form-wrapper" data-drupal-selector="edit-text-format-format" style="display: none" id="edit-text-format-format">'); - $this->assertRaw('<div class="filter-help js-form-wrapper form-wrapper" data-drupal-selector="edit-text-format-format-help" style="display: none" id="edit-text-format-format-help">'); - - /**************************************************************************/ - // counter - /**************************************************************************/ - - // Check counters. - $this->drupalGet('webform/test_element_text'); - $this->assertRaw('<input data-counter-type="character" data-counter-limit="10" class="js-webform-counter webform-counter form-text" data-drupal-selector="edit-counter-characters" type="text" id="edit-counter-characters" name="counter_characters" value="" size="60" maxlength="10" />'); - $this->assertRaw('<textarea data-counter-type="word" data-counter-limit="3" data-counter-message="word(s) left. This is a custom message" class="js-webform-counter webform-counter form-textarea resize-vertical" data-drupal-selector="edit-counter-words" id="edit-counter-words" name="counter_words" rows="5" cols="60"></textarea>'); - - // Check counter validation error. - $edit = [ - 'counter_characters' => '01234567890', - 'counter_words' => 'one two three four', - ]; - $this->drupalPostForm('webform/test_element_text', $edit, t('Submit')); - $this->assertRaw('Character counter cannot be longer than <em class="placeholder">10</em> characters but is currently <em class="placeholder">11</em> characters long.</li>'); - $this->assertRaw('Word counter cannot be longer than <em class="placeholder">3</em> words but is currently <em class="placeholder">4</em> words long.'); - - // Check counter validation passes. - $edit = [ - 'counter_characters' => '0123456789', - 'counter_words' => 'one two three', - ]; - $this->drupalPostForm('webform/test_element_text', $edit, t('Submit')); - $this->assertNoRaw('Character counter cannot be longer than <em class="placeholder">10</em> characters but is currently <em class="placeholder">11</em> characters long.</li>'); - $this->assertNoRaw('Word counter cannot be longer than <em class="placeholder">3</em> words but is currently <em class="placeholder">4</em> words long.'); - } - -} diff --git a/web/modules/webform/src/Tests/Element/WebformElementTimeTest.php b/web/modules/webform/src/Tests/Element/WebformElementTimeTest.php index 4c15d750ad..eb151e16c4 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementTimeTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementTimeTest.php @@ -20,32 +20,56 @@ class WebformElementTimeTest extends WebformElementTestBase { * Test time element. */ public function testTime() { - $this->drupalGet('webform/test_element_time'); + $this->drupalGet('/webform/test_element_time'); // Check time element. $this->assertRaw('<label for="edit-time-12-hour">time_12_hour</label>'); - $this->assertRaw('<input data-drupal-selector="edit-time-12-hour" data-webform-time-format="g:i A" type="time" id="edit-time-12-hour" name="time_12_hour" value="14:00" size="10" class="form-time webform-time" />'); + $this->assertRaw('<input data-drupal-selector="edit-time-12-hour" data-webform-time-format="g:i A" type="time" id="edit-time-12-hour" name="time_12_hour" value="14:00" size="12" maxlength="12" class="form-time webform-time" />'); // Check timepicker elements. - $this->assertRaw('<input data-drupal-selector="edit-time-timepicker" data-webform-time-format="g:i A" type="text" id="edit-time-timepicker" name="time_timepicker" value="2:00 PM" size="10" class="form-time webform-time" />'); - $this->assertRaw('<input data-drupal-selector="edit-time-timepicker-min-max" aria-describedby="edit-time-timepicker-min-max--description" data-webform-time-format="g:i A" type="text" id="edit-time-timepicker-min-max" name="time_timepicker_min_max" value="2:00 PM" size="10" min="14:00" max="18:00" class="form-time webform-time" />'); + $this->assertRaw('<input data-drupal-selector="edit-time-timepicker" data-webform-time-format="g:i A" type="text" id="edit-time-timepicker" name="time_timepicker" value="2:00 PM" size="12" maxlength="12" class="form-time webform-time" />'); + $this->assertRaw('<input data-drupal-selector="edit-time-timepicker-min-max" aria-describedby="edit-time-timepicker-min-max--description" data-webform-time-format="g:i A" type="text" id="edit-time-timepicker-min-max" name="time_timepicker_min_max" value="2:00 PM" size="12" maxlength="12" min="14:00" max="18:00" class="form-time webform-time" />'); + + // Check time processing. + $this->drupalPostForm('webform/test_element_time', [], t('Submit')); + $time_12_hour_plus_6_hours = date('H:i:00', strtotime('+6 hours')); + $this->assertRaw("time_default: '14:00:00' +time_24_hour: '14:00:00' +time_12_hour: '14:00:00' +time_12_hour_plus_6_hours: '$time_12_hour_plus_6_hours' +time_steps: '14:00:00' +time_min_max: '14:00:00' +time_timepicker: '14:00:00' +time_timepicker_min_max: '14:00:00'"); // Check time validation. $edit = ['time_24_hour' => 'not-valid']; $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">time_24_hour</em> must be a valid time.'); + // Check '0' string trigger validation error. + $edit = ['time_default' => '0']; + $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); + $this->assertRaw('em class="placeholder">time_default</em> must be a valid time.'); + + // Check '++' string (faulty relative date) trigger validation error. + $edit = ['time_default' => '++']; + $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); + $this->assertRaw('em class="placeholder">time_default</em> must be a valid time.'); + + // Check empty string trigger does not validation error. + $edit = ['time_default' => '']; + $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); + $this->assertNoRaw('<em class="placeholder">time_default</em> must be a valid time.'); + $this->assertRaw("time_default: ''"); + // Check time #max validation. - $edit = [ - 'time_min_max' => '12:00', - ]; + $edit = ['time_min_max' => '12:00']; $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">time_min_max</em> must be on or after <em class="placeholder">14:00</em>.'); // Check time #min validation. - $edit = [ - 'time_min_max' => '22:00', - ]; + $edit = ['time_min_max' => '22:00']; $this->drupalPostForm('webform/test_element_time', $edit, t('Submit')); $this->assertRaw('<em class="placeholder">time_min_max</em> must be on or before <em class="placeholder">18:00</em>.'); } diff --git a/web/modules/webform/src/Tests/Element/WebformElementToggleTest.php b/web/modules/webform/src/Tests/Element/WebformElementToggleTest.php index 0d5d9d63be..a0c6be59d8 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementToggleTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementToggleTest.php @@ -16,11 +16,21 @@ class WebformElementToggleTest extends WebformElementTestBase { */ protected static $testWebforms = ['test_element_toggle']; + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->config('webform.settings') + ->set('libraries.excluded_libraries', []) + ->save(); + } + /** * Test toggle element. */ public function testToggleElement() { - $this->drupalGet('webform/test_element_toggle'); + $this->drupalGet('/webform/test_element_toggle'); // Check basic toggle. $this->assertRaw('<div class="js-form-item form-item js-form-type-webform-toggle form-type-webform-toggle js-form-item-toggle-basic form-item-toggle-basic">'); @@ -35,14 +45,14 @@ public function testToggleElement() { $this->assertRaw('<div class="js-webform-toggle webform-toggle toggle toggle-large toggle-iphone" data-toggle-height="36" data-toggle-width="108" data-toggle-text-on="Yes" data-toggle-text-off="No"></div>'); // Check basic toggles. - $this->assertRaw('<fieldset data-drupal-selector="edit-toggles-basic" id="edit-toggles-basic--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-toggles-basic" id="edit-toggles-basic--wrapper" class="webform-toggles--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-webform-toggles webform-type-webform-toggles js-form-item form-item js-form-wrapper form-wrapper">'); $this->assertRaw('<span class="fieldset-legend">Basic toggles</span>'); $this->assertRaw('<div id="edit-toggles-basic" class="js-webform-webform-toggles form-checkboxes"><div class="js-form-item form-item js-form-type-webform-toggle form-type-webform-toggle js-form-item-toggles-basic-one form-item-toggles-basic-one">'); $this->assertRaw('<input data-drupal-selector="edit-toggles-basic-one" type="checkbox" id="edit-toggles-basic-one" name="toggles_basic[one]" value="one" class="form-checkbox" /><div class="js-webform-toggle webform-toggle toggle toggle-medium toggle-light" data-toggle-height="24" data-toggle-width="48" data-toggle-text-on="" data-toggle-text-off=""></div>'); $this->assertRaw('<label for="edit-toggles-basic-one" class="option">One</label>'); // Check advanced toggles. - $this->assertRaw('<fieldset data-drupal-selector="edit-toggles-advanced" id="edit-toggles-advanced--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">'); + $this->assertRaw('<fieldset data-drupal-selector="edit-toggles-advanced" id="edit-toggles-advanced--wrapper" class="webform-toggles--wrapper fieldgroup form-composite webform-composite-visible-title js-webform-type-webform-toggles webform-type-webform-toggles js-form-item form-item js-form-wrapper form-wrapper">'); $this->assertRaw('<span class="fieldset-legend">Advanced toggles</span>'); $this->assertRaw('<div id="edit-toggles-advanced" class="js-webform-webform-toggles form-checkboxes"><div class="js-form-item form-item js-form-type-webform-toggle form-type-webform-toggle js-form-item-toggles-advanced-one form-item-toggles-advanced-one">'); $this->assertRaw('<input data-drupal-selector="edit-toggles-advanced-one" type="checkbox" id="edit-toggles-advanced-one" name="toggles_advanced[one]" value="one" class="form-checkbox" /><div class="js-webform-toggle webform-toggle toggle toggle-large toggle-iphone" data-toggle-height="36" data-toggle-width="108" data-toggle-text-on="Yes" data-toggle-text-off="No"></div>'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementValidateMinlengthTest.php b/web/modules/webform/src/Tests/Element/WebformElementValidateMinlengthTest.php index c80f0418e1..cd1053db95 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementValidateMinlengthTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementValidateMinlengthTest.php @@ -27,6 +27,15 @@ public function testValidateMinlength() { // Check minlength validation. $this->postSubmission($webform, ['minlength_textfield' => 'X']); $this->assertRaw('<em class="placeholder">minlength_textfield</em> cannot be less than <em class="placeholder">5</em> characters but is currently <em class="placeholder">1</em> characters long.'); + + // Check minlength not required validation. + $this->postSubmission($webform, ['minlength_textfield' => '']); + $this->assertNoRaw('<em class="placeholder">minlength_textfield</em> cannot be less than <em class="placeholder">5</em> characters but is currently <em class="placeholder">0</em> characters long.'); + + // Check minlength required validation. + $this->postSubmission($webform, ['minlength_textfield_required' => '']); + $this->assertNoRaw('<em class="placeholder">minlength_textfield_required</em> cannot be less than <em class="placeholder">5</em> characters but is currently <em class="placeholder">0</em> characters long.'); + $this->assertRaw('minlength_textfield_required field is required.'); } } diff --git a/web/modules/webform/src/Tests/Element/WebformElementValidateMultipleTest.php b/web/modules/webform/src/Tests/Element/WebformElementValidateMultipleTest.php index cfc72a07da..d755be5ff2 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementValidateMultipleTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementValidateMultipleTest.php @@ -20,10 +20,21 @@ class WebformElementValidateMultipleTest extends WebformElementTestBase { * Tests element validate multiple. */ public function testValidateMultiple() { - $this->drupalGet('webform/test_element_validate_multiple'); + $this->drupalGet('/webform/test_element_validate_multiple'); // Check that only three textfields are displayed. $this->assertFieldByName('webform_element_multiple_textfield_three[items][0][_item_]'); + $this->assertNoFieldByName('webform_element_multiple_textfield_three[items][1][_item_]'); + $this->assertNoFieldByName('webform_element_multiple_textfield_three[items][2][_item_]'); + $this->assertNoFieldByName('webform_element_multiple_textfield_three[items][3][_item_]'); + $this->assertNoFieldByName('webform_element_multiple_textfield_three_table_add'); + + // Add 2 more items. + $edit = [ + 'webform_element_multiple_textfield_three[add][more_items]' => 2, + ]; + $this->drupalPostAjaxForm(NULL, $edit, 'webform_element_multiple_textfield_three_table_add'); + $this->assertFieldByName('webform_element_multiple_textfield_three[items][0][_item_]'); $this->assertFieldByName('webform_element_multiple_textfield_three[items][1][_item_]'); $this->assertFieldByName('webform_element_multiple_textfield_three[items][2][_item_]'); $this->assertNoFieldByName('webform_element_multiple_textfield_three[items][3][_item_]'); diff --git a/web/modules/webform/src/Tests/Element/WebformElementValidateUniqueTest.php b/web/modules/webform/src/Tests/Element/WebformElementValidateUniqueTest.php index 32911ec12d..1b847396a0 100644 --- a/web/modules/webform/src/Tests/Element/WebformElementValidateUniqueTest.php +++ b/web/modules/webform/src/Tests/Element/WebformElementValidateUniqueTest.php @@ -32,44 +32,51 @@ public function testValidateUnique() { 'unique_user_textfield' => '{unique_user_textfield}', 'unique_entity_textfield' => '{unique_entity_textfield}', 'unique_error' => '{unique_error}', + 'unique_multiple[1]' => TRUE, ]; // Check post submission with default values does not trigger // unique errors. $sid = $this->postSubmission($webform, $edit); - $this->assertNoRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>');; - $this->assertNoRaw('unique_textfield_multiple error message.');; + $this->assertNoRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>'); + $this->assertNoRaw('unique_textfield_multiple error message.'); $this->assertNoRaw('unique_user_textfield error message.'); $this->assertNoRaw('unique_entity_textfield error message.'); $this->assertNoRaw('unique_error error message.'); - $this->assertNoRaw('unique_ignored error message.'); + $this->assertNoRaw('unique_multiple error message.'); // Check post duplicate submission with default values does trigger // unique errors. $this->postSubmission($webform, $edit); - $this->assertRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>');; - $this->assertRaw('unique_textfield_multiple error message.');; + $this->assertRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>'); + $this->assertRaw('unique_textfield_multiple error message.'); $this->assertRaw('unique_user_textfield error message.'); $this->assertRaw('unique_entity_textfield error message.'); $this->assertRaw('unique_error error message.'); - $this->assertNoRaw('unique_ignored error message.'); + $this->assertRaw('unique_multiple error message.'); // Check #unique element can be updated. $this->drupalPostForm("admin/structure/webform/manage/test_element_validate_unique/submission/$sid/edit", [], t('Save')); - $this->assertNoRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>');; + $this->assertNoRaw('The value <em class="placeholder">{unique_textfield}</em> has already been submitted once for the <em class="placeholder">unique_textfield</em> element. You may have already submitted this webform, or you need to use a different value.</li>'); $this->assertNoRaw('unique_user_textfield error message.'); $this->assertNoRaw('unique_entity_textfield error message.'); $this->assertNoRaw('unique_error error message.'); - $this->assertNoRaw('unique_ignored error message.'); + $this->assertNoRaw('unique_multiple error message.'); // Check #unique multiple validation within the same element. // @see \Drupal\webform\Plugin\WebformElementBase::validateUniqueMultiple + // Add 2 more items. + $edit = [ + 'unique_textfield_multiple[add][more_items]' => 2, + ]; + $this->drupalPostAjaxForm('webform/test_element_validate_unique', $edit, 'unique_textfield_multiple_table_add'); + $edit = [ 'unique_textfield_multiple[items][0][_item_]' => '{same}', 'unique_textfield_multiple[items][2][_item_]' => '{same}', ]; - $this->postSubmission($webform, $edit); - $this->assertRaw('unique_textfield_multiple error message.');; + $this->drupalPostForm(NULL, $edit, t('Submit')); + $this->assertRaw('unique_textfield_multiple error message.'); // Purge existing submissions. $this->purgeSubmissions(); diff --git a/web/modules/webform/src/Tests/Exporter/WebformExporterExcludedTest.php b/web/modules/webform/src/Tests/Exporter/WebformExporterExcludedTest.php index 190231cc94..82a3bd3746 100644 --- a/web/modules/webform/src/Tests/Exporter/WebformExporterExcludedTest.php +++ b/web/modules/webform/src/Tests/Exporter/WebformExporterExcludedTest.php @@ -18,7 +18,7 @@ public function testExcludeExporters() { $this->drupalLogin($this->rootUser); // Check exporter options. - $this->drupalGet('admin/structure/webform/manage/contact/results/download'); + $this->drupalGet('/admin/structure/webform/manage/contact/results/download'); $this->assertRaw('<option value="delimited"'); $this->assertRaw('<option value="table"'); $this->assertRaw('<option value="json"'); @@ -28,7 +28,7 @@ public function testExcludeExporters() { \Drupal::configFactory()->getEditable('webform.settings')->set('export.excluded_exporters', ['delimited' => 'delimited'])->save(); // Check delimited exporter excluded. - $this->drupalGet('admin/structure/webform/manage/contact/results/download'); + $this->drupalGet('/admin/structure/webform/manage/contact/results/download'); $this->assertNoRaw('<option value="delimited"'); $this->assertRaw('<option value="table"'); $this->assertRaw('<option value="json"'); diff --git a/web/modules/webform/src/Tests/Form/WebformFormPropertiesTest.php b/web/modules/webform/src/Tests/Form/WebformFormPropertiesTest.php index f71555f50a..9f42fd5af3 100644 --- a/web/modules/webform/src/Tests/Form/WebformFormPropertiesTest.php +++ b/web/modules/webform/src/Tests/Form/WebformFormPropertiesTest.php @@ -25,11 +25,11 @@ public function testProperties() { global $base_path; // Check invalid elements . - $this->drupalGet('webform/test_element_invalid'); + $this->drupalGet('/webform/test_element_invalid'); $this->assertRaw('Unable to display this webform. Please contact the site administrator.'); // Check element's root properties moved to the webform's properties. - $this->drupalGet('webform/test_form_properties'); + $this->drupalGet('/webform/test_form_properties'); $this->assertPattern('/Form prefix<form /'); $this->assertPattern('/<\/form>\s+Form suffix/'); $this->assertRaw('<form class="webform-submission-form webform-submission-add-form webform-submission-test-form-properties-form webform-submission-test-form-properties-add-form test-form-properties js-webform-details-toggle webform-details-toggle" invalid="invalid" style="border: 10px solid red; padding: 1em;" data-drupal-selector="webform-submission-test-form-properties-add-form" action="https://www.google.com/search" method="get" id="webform-submission-test-form-properties-add-form" accept-charset="UTF-8">'); @@ -48,7 +48,7 @@ public function testProperties() { 'prefix': 'Form prefix TEST'", ]; $this->drupalPostForm('/admin/structure/webform/manage/test_form_properties/settings/form', $edit, t('Save')); - $this->drupalGet('webform/test_form_properties'); + $this->drupalGet('/webform/test_form_properties'); $this->assertPattern('/Form prefix TEST<form /'); $this->assertPattern('/<\/form>\s+Form suffix TEST/'); $this->assertRaw('<form class="webform-submission-form webform-submission-add-form webform-submission-test-form-properties-form webform-submission-test-form-properties-add-form form--inline clearfix test-form-properties js-webform-details-toggle webform-details-toggle" style="border: 10px solid green; padding: 1em;" data-drupal-selector="webform-submission-test-form-properties-add-form" action="' . $base_path . 'webform/test_form_properties" method="post" id="webform-submission-test-form-properties-add-form" accept-charset="UTF-8">'); diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerActionTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerActionTest.php index 76afec7654..dd639a3cba 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerActionTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerActionTest.php @@ -8,7 +8,7 @@ use Drupal\webform\Tests\WebformTestBase; /** - * Tests for action webform handler functionality. + * Tests for action webform handler functionality. * * @group Webform */ diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerConditionsTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerConditionsTest.php index cbff0d2187..a046433f41 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerConditionsTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerConditionsTest.php @@ -35,7 +35,7 @@ public function testConditions() { /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_handler_conditions'); - $this->drupalGet('webform/test_handler_conditions'); + $this->drupalGet('/webform/test_handler_conditions'); // Check no triggers. $this->assertRaw('Invoked test_a: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailAdvancedTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailAdvancedTest.php index 2ba78064d9..7a906eb942 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailAdvancedTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailAdvancedTest.php @@ -27,26 +27,8 @@ class WebformHandlerEmailAdvancedTest extends WebformTestBase { public function setUp() { parent::setUp(); - // Create users. - $this->createUsers(); - } - - /** - * Create webform test users. - */ - protected function createUsers() { // Create filter. $this->createFilters(); - - $this->normalUser = $this->drupalCreateUser([ - 'access user profiles', - $this->basicHtmlFilter->getPermissionName(), - ]); - $this->adminSubmissionUser = $this->drupalCreateUser([ - 'access user profiles', - 'administer webform submission', - $this->basicHtmlFilter->getPermissionName(), - ]); } /** @@ -59,9 +41,13 @@ protected function createUsers() { * @see \Drupal\Core\Mail\Plugin\Mail\TestMailCollector */ public function testAdvancedEmailHandler() { + global $base_url; + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_handler_email_advanced'); + /**************************************************************************/ + // Generate a test submission with a file upload. $this->drupalLogin($this->rootUser); @@ -136,7 +122,7 @@ public function testAdvancedEmailHandler() { // @see http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Core/Mail/MailManager.php#n285 'subject' => 'This has <removed>"special" \'chararacters\'', 'message[value]' => '<p><em>Please enter a message.</em> Test that double "quotes" are not encoded.</p>', - 'optional' => '', + 'checkbox' => FALSE, ]; $this->postSubmissionTest($webform, $edit); $sid = $this->getLastSubmissionId($webform); @@ -146,13 +132,14 @@ public function testAdvancedEmailHandler() { $this->assertEqual($sent_email['subject'], 'This has "special" \'chararacters\''); // Check email body is HTML. - $this->assertContains($sent_email['params']['body'], '<b>First name</b><br />John<br /><br />'); - $this->assertContains($sent_email['params']['body'], '<b>Last name</b><br />Smith<br /><br />'); - $this->assertContains($sent_email['params']['body'], '<b>Email</b><br /><a href="mailto:from@example.com">from@example.com</a><br /><br />'); - $this->assertContains($sent_email['params']['body'], '<b>Subject</b><br />This has <removed>"special" 'chararacters'<br /><br />'); - $this->assertContains($sent_email['params']['body'], '<b>Message</b><br /><p><em>Please enter a message.</em> Test that double "quotes" are not encoded.</p><br /><br />'); - $this->assertContains($sent_email['params']['body'], '<p style="color:yellow"><em>Custom styled HTML markup</em></p>'); - $this->assertNotContains($sent_email['params']['body'], '<b>Optional</b><br />{Empty}<br /><br />'); + $this->assertContains('<b>First name</b><br />John<br /><br />', $sent_email['params']['body']); + $this->assertContains('<b>Last name</b><br />Smith<br /><br />', $sent_email['params']['body']); + $this->assertContains('<b>Email</b><br /><a href="mailto:from@example.com">from@example.com</a><br /><br />', $sent_email['params']['body']); + $this->assertContains('<b>Subject</b><br />This has <removed>"special" 'chararacters'<br /><br />', $sent_email['params']['body']); + $this->assertContains('<b>Message</b><br /><p><em>Please enter a message.</em> Test that double "quotes" are not encoded.</p><br /><br />', $sent_email['params']['body']); + $this->assertContains('<p style="color:yellow"><em>Custom styled HTML markup</em></p>', $sent_email['params']['body']); + $this->assertNotContains('<b>Optional</b><br />{Empty}<br /><br />', $sent_email['params']['body']); + $this->assertNotContains('<b>Checkbox/b><br />Yes<br /><br />', $sent_email['params']['body']); // Check email has attachment. $this->assertEqual($sent_email['params']['attachments'][0]['filecontent'], "this is a sample txt file\nit has two lines\n"); @@ -161,14 +148,14 @@ public function testAdvancedEmailHandler() { // Check resend webform includes link to the attachment. $this->drupalGet("admin/structure/webform/manage/test_handler_email_advanced/submission/$sid/resend"); - $this->assertRaw('<span class="file file--mime-text-plain file--text">'); - $this->assertRaw('file.txt'); + $this->assertRaw('<strong><a href="' . $base_url . '/system/files/webform/test_handler_email_advanced/6/file.txt">file.txt</a></strong> (text/plain) - 43 bytes'); // Check resend webform with custom message. - $this->drupalPostForm("admin/structure/webform/manage/test_handler_email_advanced/submission/$sid/resend", ['message[body][value]' => 'Testing 123...'], t('Resend message')); + $this->drupalPostForm("admin/structure/webform/manage/test_handler_email_advanced/submission/$sid/resend", ['message[body][value]' => 'Testing 123…'], t('Resend message')); $sent_email = $this->getLastEmail(); - $this->assertNotContains($sent_email['params']['body'], '<b>First name</b><br />John<br /><br />'); - $this->assertEqual($sent_email['params']['body'], 'Testing 123...'); + $this->assertNotContains('<b>First name</b><br />John<br /><br />', $sent_email['params']['body']); + $this->debug($sent_email['params']['body']); + $this->assertEqual($sent_email['params']['body'], 'Testing 123…'); // Check resent email has the same attachment. $this->assertEqual($sent_email['params']['attachments'][0]['filecontent'], "this is a sample txt file\nit has two lines\n"); @@ -191,18 +178,20 @@ public function testAdvancedEmailHandler() { // Check empty element is excluded. $this->postSubmission($webform); $sent_email = $this->getLastEmail(); - $this->assertNotContains($sent_email['params']['body'], '<b>Optional</b><br />{Empty}<br /><br />'); + $this->assertNotContains('<b>Optional</b><br />{Empty}<br /><br />', $sent_email['params']['body']); // Include empty. $configuration = $email_handler->getConfiguration(); $configuration['settings']['exclude_empty'] = FALSE; + $configuration['settings']['exclude_empty_checkbox'] = FALSE; $email_handler->setConfiguration($configuration); $webform->save(); // Check empty included. $this->postSubmission($webform); $sent_email = $this->getLastEmail(); - $this->assertContains($sent_email['params']['body'], '<b>Optional</b><br />{Empty}<br /><br />'); + $this->assertContains('<b>Optional</b><br />{Empty}<br /><br />', $sent_email['params']['body']); + $this->assertContains('<b>Checkbox</b><br />No<br /><br />', $sent_email['params']['body']); // Logut and use anonymous user account. $this->drupalLogout(); @@ -210,7 +199,7 @@ public function testAdvancedEmailHandler() { // Check that private is include in email because 'ignore_access' is TRUE. $this->postSubmission($webform); $sent_email = $this->getLastEmail(); - $this->assertContains($sent_email['params']['body'], '<b>Notes</b><br />These notes are private.<br /><br />'); + $this->assertContains('<b>Notes</b><br />These notes are private.<br /><br />', $sent_email['params']['body']); // Disable ignore_access. $email_handler = $webform->getHandler('email'); @@ -222,7 +211,7 @@ public function testAdvancedEmailHandler() { // Check that private is excluded from email because 'ignore_access' is FALSE. $this->postSubmission($webform); $sent_email = $this->getLastEmail(); - $this->assertNotContains($sent_email['params']['body'], '<b>Notes</b><br />These notes are private.<br /><br />'); + $this->assertNotContains('<b>Notes</b><br />These notes are private.<br /><br />', $sent_email['params']['body']); } } diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailBasicTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailBasicTest.php index 2da586786d..fd54eaee22 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailBasicTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailBasicTest.php @@ -21,23 +21,19 @@ class WebformHandlerEmailBasicTest extends WebformTestBase { */ protected static $testWebforms = ['test_handler_email']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test basic email handler. */ public function testBasicEmailHandler() { + $admin_user = $this->drupalCreateUser([ + 'administer webform', + ]); + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_handler_email'); + /**************************************************************************/ + // Create a submission using the test webform's default values. $this->postSubmission($webform); @@ -45,9 +41,9 @@ public function testBasicEmailHandler() { $sent_email = $this->getLastEmail(); $this->assertEqual($sent_email['key'], 'test_handler_email_email'); $this->assertEqual($sent_email['reply-to'], "John Smith <from@example.com>"); - $this->assertContains($sent_email['body'], 'Submitted by: Anonymous'); - $this->assertContains($sent_email['body'], 'First name: John'); - $this->assertContains($sent_email['body'], 'Last name: Smith'); + $this->assertContains('Submitted by: Anonymous', $sent_email['body']); + $this->assertContains('First name: John', $sent_email['body']); + $this->assertContains('Last name: Smith', $sent_email['body']); $this->assertEqual($sent_email['headers']['From'], 'John Smith <from@example.com>'); $this->assertEqual($sent_email['headers']['Cc'], 'cc@example.com'); $this->assertEqual($sent_email['headers']['Bcc'], 'bcc@example.com'); @@ -60,12 +56,12 @@ public function testBasicEmailHandler() { $webform->setSetting('results_disabled', TRUE)->save(); $this->postSubmission($webform, ['first_name' => 'Jane', 'last_name' => 'Doe']); $sent_email = $this->getLastEmail(); - $this->assertContains($sent_email['body'], 'First name: Jane'); - $this->assertContains($sent_email['body'], 'Last name: Doe'); + $this->assertContains('First name: Jane', $sent_email['body']); + $this->assertContains('Last name: Doe', $sent_email['body']); $webform->setSetting('results_disabled', FALSE)->save(); // Check sending a custom email using tokens. - $this->drupalLogin($this->adminWebformUser); + $this->drupalLogin($admin_user); $body = implode(PHP_EOL, [ 'full name: [webform_submission:values:first_name] [webform_submission:values:last_name]', 'uuid: [webform_submission:uuid]', @@ -85,17 +81,20 @@ public function testBasicEmailHandler() { $webform_submission = WebformSubmission::load($sid); $sent_email = $this->getLastEmail(); - $this->assertContains($sent_email['body'], 'full name: John Smith'); - $this->assertContains($sent_email['body'], 'uuid: ' . $webform_submission->uuid->value); - $this->assertContains($sent_email['body'], 'sid: ' . $sid); - $this->assertContains($sent_email['body'], 'date: ' . \Drupal::service('date.formatter')->format($webform_submission->created->value, 'medium')); - $this->assertContains($sent_email['body'], 'ip-address: ' . $webform_submission->remote_addr->value); - $this->assertContains($sent_email['body'], 'user: ' . $this->adminWebformUser->label()); - $this->assertContains($sent_email['body'], "url:"); - $this->assertContains($sent_email['body'], $webform_submission->toUrl('canonical', ['absolute' => TRUE])->toString()); - $this->assertContains($sent_email['body'], "edit-url:"); - $this->assertContains($sent_email['body'], $webform_submission->toUrl('edit-form', ['absolute' => TRUE])->toString()); - $this->assertContains($sent_email['body'], 'Test that "double quotes" are not encoded.'); + $this->assertContains('full name: John Smith', $sent_email['body']); + $this->assertContains('uuid: ' . $webform_submission->uuid->value, $sent_email['body']); + $this->assertContains('sid: ' . $sid, $sent_email['body']); + $date_value = \Drupal::service('date.formatter')->format($webform_submission->created->value, 'medium'); + $this->assertContains('date: ' . $date_value, $sent_email['body']); + $this->assertContains('ip-address: ' . $webform_submission->remote_addr->value, $sent_email['body']); + $this->assertContains('user: ' . $admin_user->label(), $sent_email['body']); + $this->assertContains("url:", $sent_email['body']); + $this->assertContains($webform_submission->toUrl('canonical', ['absolute' => TRUE]) + ->toString(), $sent_email['body']); + $this->assertContains("edit-url:", $sent_email['body']); + $this->assertContains($webform_submission->toUrl('edit-form', ['absolute' => TRUE]) + ->toString(), $sent_email['body']); + $this->assertContains('Test that "double quotes" are not encoded.', $sent_email['body']); // Create a submission using HTML is subject and message. $edit = [ @@ -117,7 +116,7 @@ public function testBasicEmailHandler() { ]; $this->postSubmission($webform, $edit); $sent_email = $this->getLastEmail(); - $this->assertEqual($sent_email['reply-to'], '"first_name" "last_name" <from@example.com>'); + $this->assertEqual($sent_email['reply-to'], '"first_name\\" \\"last_name" <from@example.com>'); $this->assertEqual($sent_email['subject'], 'This has & "special" \'characters\''); // NOTE: // Drupal's PhpMail::format function calls @@ -135,8 +134,7 @@ public function testBasicEmailHandler() { This has & "special" \'characters\' This has & "special" \'characters\' -' -); +'); // Instead we are going to check params body. $this->assertEqual($sent_email['params']['body'], 'First name: "<first_name>" Last name: "<last_name>" diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailMappingTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailMappingTest.php index be6aa30415..7165b4433b 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailMappingTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailMappingTest.php @@ -60,7 +60,7 @@ public function testEmailMapping() { $this->assertText("Checkboxes sent to saturday@example.com,sunday@example.com from $site_name [$site_mail]."); $this->assertNoText('Email not sent for Checkboxes handler because a To, CC, or BCC email was not provided.'); - // Check that checkbxoes other option email sent. + // Check that checkboxes other option email sent. $edit = [ 'radios_other[radios]' => '_other_', 'radios_other[other]' => '{Other}', diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRenderingTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRenderingTest.php index 9fb342f231..e47d267595 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRenderingTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRenderingTest.php @@ -37,7 +37,7 @@ public function testEmailRendering() { $webform = Webform::load('contact'); // Check that we are currently using the bartik.theme. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('core/themes/bartik/css/base/elements.css'); // Post submission and send emails. @@ -68,7 +68,7 @@ public function testEmailRendering() { $webform->save(); // Check that we are now using the seven.theme. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertNoRaw('core/themes/bartik/css/base/elements.css'); // Post submission and send emails. diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRolesTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRolesTest.php index 0cdd57a9f3..db8ae40956 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRolesTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailRolesTest.php @@ -33,20 +33,20 @@ public function testEmailRoles() { // creates 'administrator' role. // WORKAROUND: Create 'administrator' role so that SimpleTest and Drupal // are in-sync. - $this->createRole([], 'administrator'); + $this->drupalCreateRole([], 'administrator'); $webform = Webform::load('test_handler_email_roles'); - $authenticated_user = $this->createUser(); + $authenticated_user = $this->drupalCreateUser(); $authenticated_user->set('mail', 'authenticated@example.com'); $authenticated_user->save(); - $blocked_user = $this->createUser(); + $blocked_user = $this->drupalCreateUser(); $blocked_user->set('mail', 'blocked@example.com'); $blocked_user->block(); $blocked_user->save(); - $admin_user = $this->createUser(); + $admin_user = $this->drupalCreateUser(); $admin_user->set('mail', 'administrator@example.com'); $admin_user->addRole('administrator'); $admin_user->save(); diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailStatesTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailStatesTest.php index 5f1baccd13..8161611069 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailStatesTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailStatesTest.php @@ -53,20 +53,25 @@ public function testEmailStates() { $this->assertRaw('<b>Subject:</b> Submission converted<br />'); $this->assertRaw('<b>Subject:</b> Submission completed<br />'); $this->assertRaw('<b>Subject:</b> Submission updated<br />'); + $this->assertRaw('<b>Subject:</b> Submission locked<br />'); $this->assertRaw('<b>Subject:</b> Submission deleted<br />'); $this->assertRaw('<b>Subject:</b> Submission custom<br />'); + // Check locked email. + $this->drupalPostForm("/admin/structure/webform/manage/test_handler_email_states/submission/$sid/notes", ['locked' => TRUE], t('Save')); + $this->assertRaw('Debug: Email: Submission locked'); + // Check deleted email. $this->drupalPostForm("/admin/structure/webform/manage/test_handler_email_states/submission/$sid/delete", [], t('Delete')); $this->assertRaw('Debug: Email: Submission deleted'); - // Check that 'Send when...' is visible. - $this->drupalGet('admin/structure/webform/manage/test_handler_email_states/handlers/email_draft/edit'); + // Check that 'Send when…' is visible. + $this->drupalGet('/admin/structure/webform/manage/test_handler_email_states/handlers/email_draft/edit'); $this->assertRaw('<span class="fieldset-legend">Send email</span>'); // Check states hidden when results are disabled. $webform->setSetting('results_disabled', TRUE)->save(); - $this->drupalGet('admin/structure/webform/manage/test_handler_email_states/handlers/email_draft/edit'); + $this->drupalGet('/admin/structure/webform/manage/test_handler_email_states/handlers/email_draft/edit'); $this->assertNoRaw('<span class="fieldset-legend js-form-required form-required">Send email</span>'); // Check that only completed email is triggered when states are disabled. diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailTwigTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailTwigTest.php index 017fcf937d..66fbe3c492 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerEmailTwigTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerEmailTwigTest.php @@ -41,6 +41,7 @@ public function testEmailTwigHandler() { <b>Subject</b><br />{subject}<br /><br /> <b>Message</b><br />{message}<br /><br />'); + } } diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerExcludedTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerExcludedTest.php index a6a4c7bcf6..489e103f20 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerExcludedTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerExcludedTest.php @@ -11,6 +11,21 @@ */ class WebformHandlerExcludedTest extends WebformTestBase { + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['block', 'webform']; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); + } + /** * Test excluded handlers. */ @@ -21,31 +36,31 @@ public function testExcludeHandlers() { $handler_manager = $this->container->get('plugin.manager.webform.handler'); // Check add mail and handler plugin. - $this->drupalGet('admin/structure/webform/manage/contact/handlers'); + $this->drupalGet('/admin/structure/webform/manage/contact/handlers'); $this->assertLink('Add email'); $this->assertLink('Add handler'); // Check add mail accessible. - $this->drupalGet('admin/structure/webform/manage/contact/handlers/add/email'); + $this->drupalGet('/admin/structure/webform/manage/contact/handlers/add/email'); $this->assertResponse(200); // Exclude the email handler. \Drupal::configFactory()->getEditable('webform.settings')->set('handler.excluded_handlers', ['email' => 'email'])->save(); // Check add mail hidden. - $this->drupalGet('admin/structure/webform/manage/contact/handlers'); + $this->drupalGet('/admin/structure/webform/manage/contact/handlers'); $this->assertNoLink('Add email'); $this->assertLink('Add handler'); // Check add mail access denied. - $this->drupalGet('admin/structure/webform/manage/contact/handlers/add/email'); + $this->drupalGet('/admin/structure/webform/manage/contact/handlers/add/email'); $this->assertResponse(403); // Exclude the email handler. \Drupal::configFactory()->getEditable('webform.settings')->set('handler.excluded_handlers', ['action' => 'action', 'broken' => 'broken', 'debug' => 'debug', 'email' => 'email', 'remote_post' => 'remote_post', 'settings' => 'settings'])->save(); // Check add mail and handler hidden. - $this->drupalGet('admin/structure/webform/manage/contact/handlers'); + $this->drupalGet('/admin/structure/webform/manage/contact/handlers'); $this->assertNoLink('Add email'); $this->assertNoLink('Add handler'); diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerInvokeAlterHookTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerInvokeAlterHookTest.php new file mode 100644 index 0000000000..5f8d14890f --- /dev/null +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerInvokeAlterHookTest.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\webform\Tests\Handler; + +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for the webform handler invoke alter hook. + * + * @group Webform + */ +class WebformHandlerInvokeAlterHookTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_test_handler_invoke_alter']; + + /** + * Tests webform handler invoke alter hook. + */ + public function testWebformHandlerInvokeAlterHook() { + // Check invoke alter hooks. + $this->drupalGet('/webform/contact'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::pre_create"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_pre_create_alter() for "contact:email_confirmation"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::pre_create"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_pre_create_alter() for "contact:email_notification"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::alter_elements"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::alter_elements"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::alter_elements"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::alter_elements"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::post_create"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::post_create"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::override_settings"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::override_settings"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_confirmation::alter_form"'); + $this->assertRaw('Invoking hook_webform_handler_invoke_alter() for "contact:email_notification::alter_form"'); + } + +} diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerPluginTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerPluginTest.php index e1e7eac1a7..8a5d861da4 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerPluginTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerPluginTest.php @@ -20,7 +20,7 @@ class WebformHandlerPluginTest extends WebformTestBase { public static $modules = ['webform', 'webform_test_handler']; /** - * Tests webform element plugin. + * Tests webform handler plugin. */ public function testWebformHandler() { $webform = Webform::load('contact'); diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerRemotePostTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerRemotePostTest.php index a8dbcf0178..213fee20b7 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerRemotePostTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerRemotePostTest.php @@ -28,6 +28,7 @@ class WebformHandlerRemotePostTest extends WebformTestBase { */ protected static $testWebforms = [ 'test_handler_remote_post', + 'test_handler_remote_put', 'test_handler_remote_get', 'test_handler_remote_post_file', ]; @@ -47,6 +48,22 @@ public function testRemotePostHandler() { // Check 'completed' operation. $sid = $this->postSubmission($webform); + + // Check POST response. + $this->assertRaw("method: post +status: success +message: 'Processed completed request.' +options: + headers: + Accept-Language: en + custom_header: 'true' + form_params: + custom_completed: true + custom_data: true + response_type: '200' + first_name: John + last_name: Smith"); + $webform_submission = WebformSubmission::load($sid); $this->assertRaw("form_params: custom_completed: true @@ -61,7 +78,7 @@ public function testRemotePostHandler() { $this->assertRaw('Your confirmation number is ' . $webform_submission->getElementData('confirmation_number') . '.'); // Check custom header. - $this->assertRaw('{"custom_header":"true"}'); + $this->assertRaw('{"headers":{"Accept-Language":"en","custom_header":"true"}'); // Sleep for 1 second to make sure submission timestamp is updated. sleep(1); @@ -166,6 +183,29 @@ public function testRemotePostHandler() { preg_match('/"confirmation_number":"([a-zA-z0-9]+)"/', $this->getRawContent(), $match); $this->assertRaw('Your confirmation number is ' . $match[1] . '.'); + /**************************************************************************/ + // PUT. + /**************************************************************************/ + + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = Webform::load('test_handler_remote_put'); + + $this->postSubmission($webform); + + // Check PUT response. + $this->assertRaw("method: put +status: success +message: 'Processed completed request.' +options: + headers: + custom_header: 'true' + form_params: + custom_completed: true + custom_data: true + response_type: '200' + first_name: John + last_name: Smith"); + /**************************************************************************/ // GET. /**************************************************************************/ @@ -175,11 +215,20 @@ public function testRemotePostHandler() { $this->postSubmission($webform); + // Check GET response. + $this->assertRaw("method: get +status: success +message: 'Processed completed request.' +options: + headers: + custom_header: 'true'"); + // Check request URL contains query string. - $this->assertRaw("http://webform-test-handler-remote-post/completed?custom_completed=1&custom_data=1&response_type=200&first_name=John&last_name=Smith"); + // @todo restore tests once the Drupal 8.6.x issue is resolved. + //$this->assertRaw("http://webform-test-handler-remote-post/completed?custom_completed=1&custom_data=1&first_name=John&last_name=Smith&response_type=200"); // Check response data. - $this->assertRaw("message: 'Processed completed?custom_completed=1&custom_data=1&response_type=200&first_name=John&last_name=Smith request.'"); + $this->assertRaw("message: 'Processed completed request.'"); // Get confirmation number from JSON packet. preg_match('/"confirmation_number":"([a-zA-z0-9]+)"/', $this->getRawContent(), $match); @@ -194,14 +243,43 @@ public function testRemotePostHandler() { $sid = $this->postSubmissionTest($webform); $webform_submission = WebformSubmission::load($sid); - $fid = $webform_submission->getElementData('file'); + + $file_data = $webform_submission->getElementData('file'); + $file = File::load($file_data); + $file_id = $file->id(); + $file_uuid = $file->uuid(); + + $files_data = $webform_submission->getElementData('files'); + $file = File::load(reset($files_data)); + $files_id = $file->id(); + $files_uuid = $file->uuid(); // Check the file name, uri, and data is appended to form params. $this->assertRaw("form_params: - file: $fid + file: 1 + files: + - 2 + _file: + id: $file_id + name: file.txt + uri: 'private://webform/test_handler_remote_post_file/$sid/file.txt' + mime: text/plain + uuid: $file_uuid + data: dGhpcyBpcyBhIHNhbXBsZSB0eHQgZmlsZQppdCBoYXMgdHdvIGxpbmVzCg== + file__id: $file_id file__name: file.txt file__uri: 'private://webform/test_handler_remote_post_file/$sid/file.txt' - file__data: dGhpcyBpcyBhIHNhbXBsZSB0eHQgZmlsZQppdCBoYXMgdHdvIGxpbmVzCg=="); + file__mime: text/plain + file__uuid: $file_uuid + file__data: dGhpcyBpcyBhIHNhbXBsZSB0eHQgZmlsZQppdCBoYXMgdHdvIGxpbmVzCg== + _files: + - + id: $files_id + name: files.txt + uri: 'private://webform/test_handler_remote_post_file/$sid/files.txt' + mime: text/plain + uuid: $files_uuid + data: dGhpcyBpcyBhIHNhbXBsZSB0eHQgZmlsZQppdCBoYXMgdHdvIGxpbmVzCg=="); } } diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerSettingsTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerSettingsTest.php index d17f3c2362..dfda99cff0 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerSettingsTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerSettingsTest.php @@ -36,7 +36,7 @@ public function testSettingsHandler() { $this->assertRaw($message_indentation . '{Custom draft saved message}'); // Check custom save load message. - $this->drupalGet('webform/test_handler_settings'); + $this->drupalGet('/webform/test_handler_settings'); // NOTE: Adding indentation to make sure the message is matched and not input value. $this->assertRaw($message_indentation . '{Custom draft loaded message}'); @@ -60,7 +60,7 @@ public function testSettingsHandler() { $this->assertNoRaw($message_indentation . '{Custom draft saved message}'); // Check no custom save load message. - $this->drupalGet('webform/test_handler_settings'); + $this->drupalGet('/webform/test_handler_settings'); $this->assertNoRaw($message_indentation . '{Custom draft loaded message}'); // Check no custom preview title and message. diff --git a/web/modules/webform/src/Tests/Handler/WebformHandlerTest.php b/web/modules/webform/src/Tests/Handler/WebformHandlerTest.php index 4ae5b1e15d..1a5c8317e7 100644 --- a/web/modules/webform/src/Tests/Handler/WebformHandlerTest.php +++ b/web/modules/webform/src/Tests/Handler/WebformHandlerTest.php @@ -38,7 +38,7 @@ public function testWebformHandler() { $webform_handler_test = Webform::load('test_handler_test'); // Check new submission plugin invoking. - $this->drupalGet('webform/test_handler_test'); + $this->drupalGet('/webform/test_handler_test'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postCreate'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); @@ -76,7 +76,7 @@ public function testWebformHandler() { $this->assertRaw('<div class="webform-confirmation__message">::preprocessConfirmation</div>'); // Check confirmation with token. - $this->drupalGet('webform/test_handler_test/confirmation', ['query' => ['token' => $webform_submission->getToken()]]); + $this->drupalGet('/webform/test_handler_test/confirmation', ['query' => ['token' => $webform_submission->getToken()]]); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preSave'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postLoad'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); @@ -85,7 +85,7 @@ public function testWebformHandler() { $this->assertRaw('<div class="webform-confirmation__message">::preprocessConfirmation</div>'); // Check confirmation with out token. - $this->drupalGet('webform/test_handler_test/confirmation'); + $this->drupalGet('/webform/test_handler_test/confirmation'); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postLoad'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings'); @@ -101,7 +101,7 @@ public function testWebformHandler() { $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postLoad'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preDelete'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postDelete'); - $this->assertRaw('Submission #' . $webform_submission->serial() . ' has been deleted.'); + $this->assertRaw('<em class="placeholder">Test: Handler: Test invoke methods: Submission #' . $webform_submission->serial() . '</em> has been deleted.'); // Check configuration settings. $this->drupalPostForm('admin/structure/webform/manage/test_handler_test/handlers/test/edit', ['settings[message]' => '{message}'], t('Save')); @@ -110,7 +110,7 @@ public function testWebformHandler() { // Check disabling a handler. $this->drupalPostForm('admin/structure/webform/manage/test_handler_test/handlers/test/edit', ['status' => FALSE], t('Save')); - $this->drupalGet('webform/test_handler_test'); + $this->drupalGet('/webform/test_handler_test'); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postCreate'); $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); @@ -124,7 +124,7 @@ public function testWebformHandler() { // Check webform disabled with saving of results is disabled and handler does // not process results. $this->drupalLogout(); - $this->drupalGet('webform/test_handler_test'); + $this->drupalGet('/webform/test_handler_test'); $this->assertNoFieldByName('op', 'Submit'); $this->assertNoRaw('This webform is not saving or handling any submissions. All submitted data will be lost.'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); @@ -134,7 +134,7 @@ public function testWebformHandler() { // Check admin can still post submission. $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/test_handler_test'); + $this->drupalGet('/webform/test_handler_test'); $this->assertFieldByName('op', 'Submit'); $this->assertRaw('This webform is currently not saving any submitted data.'); $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); @@ -173,11 +173,44 @@ public function testWebformHandler() { $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:deleteHandler'); // Check create handler. - $this->drupalPostForm('admin/structure/webform/manage/test_handler_test/handlers/add/test_log', ['handler_id' => 'test'], t('Save')); + $this->drupalPostForm('admin/structure/webform/manage/test_handler_test/handlers/add/test', ['handler_id' => 'test'], t('Save')); $this->assertRaw('The webform handler was successfully added.'); - // @todo Determine why create message is not being displayed. - // Ajax machine name callback could be causing the issue. - // $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:createHandler'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:createHandler'); + + /**************************************************************************/ + // Single handler. + /**************************************************************************/ + + // Check test handler is executed. + $this->drupalGet('/webform/test_handler_test/test'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postCreate'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings'); + $this->assertRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterForm'); + + // Check test handler is enabled and debug handler is disabled. + $this->drupalPostForm('webform/test_handler_test/test', ['element' => ''], t('Submit')); + $this->assertRaw('One two one two this is just a test'); + $this->assertNoRaw("element: ''"); + + // Check test handler is disabled. + $this->drupalGet('/webform/test_handler_test/test', ['query' => ['_webform_handler' => 'debug']]); + $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:preCreate'); + $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:postCreate'); + $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:alterElements'); + $this->assertNoRaw('Invoked test: Drupal\webform_test_handler\Plugin\WebformHandler\TestWebformHandler:overrideSettings'); + $this->assertRaw('Testing the <em class="placeholder">Test: Handler: Test invoke methods</em> webform <em class="placeholder">Debug</em> handler. <strong>All other emails/handlers are disabled.</strong>'); + + // Check test handler is now disabled and debug handler is enabled. + $this->drupalPostForm('webform/test_handler_test/test', ['element' => ''], t('Submit'), ['query' => ['_webform_handler' => 'debug']]); + $this->assertNoRaw('One two one two this is just a test'); + $this->assertRaw("element: ''"); + + // Check 403 access denied for missing handler. + $this->drupalGet('/webform/test_handler_test/test', ['query' => ['_webform_handler' => 'missing']]); + $this->assertResponse(403); + $this->assertRaw('The <em class="placeholder">missing</em> email/handler for the <em class="placeholder">Test: Handler: Test invoke methods</em> webform does not exist.'); } /** diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsAccessDeniedTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsAccessDeniedTest.php new file mode 100644 index 0000000000..cf8161a6f8 --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsAccessDeniedTest.php @@ -0,0 +1,178 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\Core\Url; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; +use Drupal\webform\WebformInterface; + +/** + * Tests for access denied webform and submissions. + * + * @group Webform + */ +class WebformSettingsAccessDeniedTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['block', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_form_access_denied']; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + // Place blocks. + $this->placeBlocks(); + } + + /** + * Tests webform access denied setting. + */ + public function testWebformAccessDenied() { + $webform = Webform::load('test_form_access_denied'); + $webform_edit_route_url = Url::fromRoute('entity.webform.edit_form', [ + 'webform' => $webform->id(), + ]); + + /**************************************************************************/ + // Redirect. + /**************************************************************************/ + + // Set access denied to redirect with message. + $webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_LOGIN); + $webform->save(); + + // Check form message is displayed and user is redirected to the login form. + $this->drupalGet('/admin/structure/webform/manage/test_form_access_denied'); + $this->assertRaw('Please login to access <b>Test: Webform: Access Denied</b>.'); + $route_options = [ + 'query' => [ + 'destination' => $webform_edit_route_url->toString(), + ], + ]; + $this->assertUrl(Url::fromRoute('user.login', [], $route_options)); + + /**************************************************************************/ + // Default. + /**************************************************************************/ + + // Set default access denied page. + $webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_DEFAULT); + $webform->save(); + + // Check default access denied page. + $this->drupalGet('/admin/structure/webform/manage/test_form_access_denied'); + $this->assertRaw('You are not authorized to access this page.'); + $this->assertNoRaw('Please login to access <b>Test: Webform: Access Denied</b>.'); + + /**************************************************************************/ + // Page. + /**************************************************************************/ + + // Set access denied to display a dedicated page. + $webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_PAGE); + $webform->setSetting('form_access_denied_title', 'Webform: Access denied'); + $webform->setSetting('form_access_denied_attributes', ['style' => 'border: 1px solid red', 'class' => [], 'attributes' => []]); + $webform->save(); + + // Check custom access denied page. + $this->drupalGet('/admin/structure/webform/manage/test_form_access_denied'); + $this->assertRaw('<h1 class="page-title">Webform: Access denied</h1>'); + $this->assertRaw('<div style="border: 1px solid red" class="webform-access-denied">Please login to access <b>Test: Webform: Access Denied</b>.</div>'); + + /**************************************************************************/ + // Message via a block. + /**************************************************************************/ + + // Place block. + $this->drupalPlaceBlock('webform_block', [ + 'webform_id' => 'test_form_access_denied', + ]); + + // Set access denied to default. + $webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_DEFAULT); + $webform->save(); + + // Check block does not displays access denied message. + $this->drupalGet('/<front>'); + $this->assertNoRaw('<div style="border: 1px solid red" class="webform-access-denied">Please login to access <b>Test: Webform: Access Denied</b>.</div>'); + + // Set access denied to display a message. + $webform->setSetting('form_access_denied', WebformInterface::ACCESS_DENIED_MESSAGE); + $webform->save(); + + // Check block displays access denied message. + $this->drupalGet('/<front>'); + $this->assertRaw('<div style="border: 1px solid red" class="webform-access-denied">Please login to access <b>Test: Webform: Access Denied</b>.</div>'); + + // Login. + $this->drupalLogin($this->rootUser); + + // Check block displays wth webform. + $this->drupalGet('/<front>'); + $this->assertNoRaw('<div class="webform-access-denied">Please login to access <b>Test: Webform: Access Denied</b>.</div>'); + $this->assertRaw('id="webform-submission-test-form-access-denied-user-1-add-form"'); + } + + /** + * Tests webform submission access denied setting. + */ + public function testWebformSubmissionAccessDenied() { + // Create a webform submission. + $this->drupalLogin($this->rootUser); + $webform = Webform::load('test_form_access_denied'); + $sid = $this->postSubmission($webform); + $this->drupalLogout(); + + /**************************************************************************/ + // Redirect. + /**************************************************************************/ + + // Check submission message is displayed. + $this->drupalGet("admin/structure/webform/manage/test_form_access_denied/submission/$sid"); + $this->assertRaw("Please login to access <b>Test: Webform: Access Denied: Submission #$sid</b>."); + + /**************************************************************************/ + // Default. + /**************************************************************************/ + + // Set default access denied page. + $webform->setSetting('submission_access_denied', WebformInterface::ACCESS_DENIED_DEFAULT); + $webform->save(); + + // Check default access denied page. + $this->drupalGet("admin/structure/webform/manage/test_form_access_denied/submission/$sid"); + $this->assertRaw('You are not authorized to access this page.'); + $this->assertNoRaw("Please login to access <b>Test: Webform: Access Denied: Submission #$sid</b>."); + + /**************************************************************************/ + // Page. + /**************************************************************************/ + + // Set access denied to display a dedicated page. + $webform->setSetting('submission_access_denied', WebformInterface::ACCESS_DENIED_PAGE); + $webform->setSetting('submission_access_denied_title', 'Webform submission: Access denied'); + $webform->setSetting('submission_access_denied_attributes', ['style' => 'border: 1px solid red', 'class' => [], 'attributes' => []]); + $webform->save(); + + // Check custom access denied page. + $this->drupalGet("admin/structure/webform/manage/test_form_access_denied/submission/$sid"); + $this->assertNoRaw('You are not authorized to access this page.'); + $this->assertRaw('<h1 class="page-title">Webform submission: Access denied</h1>'); + $this->assertRaw('<div style="border: 1px solid red" class="webform-submission-access-denied">Please login to access <b>Test: Webform: Access Denied: Submission #' . $sid . '</b>.</div>'); + } + +} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsAdminTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsAdminTest.php index 017c1c430a..407e9189c6 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsAdminTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsAdminTest.php @@ -2,7 +2,6 @@ namespace Drupal\webform\Tests\Settings; -use Drupal\Component\Serialization\Yaml; use Drupal\webform\Tests\WebformTestBase; use Drupal\webform\Utility\WebformYaml; @@ -18,7 +17,7 @@ class WebformSettingsAdminTest extends WebformTestBase { * * @var array */ - public static $modules = ['node', 'webform', 'webform_ui']; + public static $modules = ['block', 'captcha', 'node', 'views', 'webform', 'webform_ui', 'webform_node']; /** * Webforms to load. @@ -27,6 +26,14 @@ class WebformSettingsAdminTest extends WebformTestBase { */ protected static $testWebforms = ['test_element']; + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); + } + /** * Tests webform admin settings. */ @@ -60,8 +67,8 @@ public function testAdminSettings() { $this->assertEqual($updated_data, $original_data, 'Updated admin settings via the UI did not lose or change any data'); // DEBUG: - $original_yaml = WebformYaml::tidy(Yaml::encode($original_data)); - $updated_yaml = WebformYaml::tidy(Yaml::encode($updated_data)); + $original_yaml = WebformYaml::encode($original_data); + $updated_yaml = WebformYaml::encode($updated_data); $this->verbose('<pre>' . $original_yaml . '</pre>'); $this->verbose('<pre>' . $updated_yaml . '</pre>'); $this->debug(array_diff(explode(PHP_EOL, $original_yaml), explode(PHP_EOL, $updated_yaml))); @@ -70,40 +77,40 @@ public function testAdminSettings() { /* Elements */ // Check that description is 'after' the element. - $this->drupalGet('webform/test_element'); + $this->drupalGet('/webform/test_element'); $this->assertPattern('#\{item title\}.+\{item markup\}.+\{item description\}#ms'); // Set the default description display to 'before'. $this->drupalPostForm('admin/structure/webform/config/elements', ['element[default_description_display]' => 'before'], t('Save configuration')); // Check that description is 'before' the element. - $this->drupalGet('webform/test_element'); + $this->drupalGet('/webform/test_element'); $this->assertNoPattern('#\{item title\}.+\{item markup\}.+\{item description\}#ms'); $this->assertPattern('#\{item title\}.+\{item description\}.+\{item markup\}#ms'); /* UI disable dialog */ // Check that dialogs are enabled. - $this->drupalGet('admin/structure/webform'); - $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="button button-action button--primary button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":700,"dialogClass":"webform-modal"}">Add webform</a>'); + $this->drupalGet('/admin/structure/webform'); + $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="webform-ajax-link button button-action" data-dialog-type="modal" data-dialog-options="{"width":700,"dialogClass":"webform-ui-dialog"}" data-drupal-link-system-path="admin/structure/webform/add">Add webform</a>'); // Disable dialogs. $this->drupalPostForm('admin/structure/webform/config/advanced', ['ui[dialog_disabled]' => TRUE], t('Save configuration')); // Check that dialogs are disabled. (i.e. use-ajax is not included) - $this->drupalGet('admin/structure/webform'); - $this->assertNoRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="button button-action button--primary button--small webform-ajax-link" data-dialog-type="modal" data-dialog-options="{"width":700,"dialogClass":"webform-modal"}">Add webform</a>'); - $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="button button-action button--primary button--small">Add webform</a>'); + $this->drupalGet('/admin/structure/webform'); + $this->assertNoRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="webform-ajax-link button button-action" data-dialog-type="modal" data-dialog-options="{"width":700,"dialogClass":"webform-ui-dialog"}" data-drupal-link-system-path="admin/structure/webform/add">Add webform</a>'); + $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/add" class="button button-action" data-drupal-link-system-path="admin/structure/webform/add">Add webform</a>'); /* UI description help */ // Check moving #description to #help for webform admin routes. $this->drupalPostForm('admin/structure/webform/config/advanced', ['ui[description_help]' => TRUE], t('Save configuration')); - $this->assertRaw('<a href="#help" title="If checked, all element descriptions will be moved to help text (tooltip)." data-webform-help="If checked, all element descriptions will be moved to help text (tooltip)." class="webform-element-help">?</a>'); + $this->assertRaw('<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Display element description as help text (tooltip)</div><div class="webform-element-help--content">If checked, all element descriptions will be moved to help text (tooltip).</div>"><span aria-hidden="true">?</span></span>'); // Check moving #description to #help for webform admin routes. $this->drupalPostForm('admin/structure/webform/config/advanced', ['ui[description_help]' => FALSE], t('Save configuration')); - $this->assertNoRaw('<a href="#help" title="If checked, all element descriptions will be moved to help text (tooltip)." data-webform-help="If checked, all element descriptions will be moved to help text (tooltip)." class="webform-element-help">?</a>'); + $this->assertNoRaw('<span class="webform-element-help" role="tooltip" tabindex="0" data-webform-help="<div class="webform-element-help--title">Display element description as help text (tooltip)</div><div class="webform-element-help--content">If checked, all element descriptions will be moved to help text (tooltip).</div>"><span aria-hidden="true">?</span></span>'); } /** diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsArchivedTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsArchivedTest.php new file mode 100644 index 0000000000..0a57da35b0 --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsArchivedTest.php @@ -0,0 +1,83 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform archived. + * + * @group Webform + */ +class WebformSettingsArchivedTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform_node', 'webform_templates', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_form_archived']; + + /** + * Test webform submission form archived. + */ + public function testArchived() { + global $base_path; + + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('test_form_archived'); + + // Check that archived webform is removed from webforms manage page. + $this->drupalGet('/admin/structure/webform'); + $this->assertRaw('<td><a href="' . $base_path . 'form/contact">Contact</a></td>'); + $this->assertNoRaw('<td><a href="' . $base_path . 'form/test-form-archived">Test: Webform: Archive</a></td>'); + + // Check that archived webform appears when archived filter selected. + $this->drupalGet('/admin/structure/webform', ['query' => ['state' => 'archived']]); + $this->assertNoRaw('<td><a href="' . $base_path . 'form/contact">Contact</a></td>'); + $this->assertRaw('<td><a href="' . $base_path . 'form/test-form-archived">Test: Webform: Archive</a></td>'); + + // Check that archived webform displays archive message. + $this->drupalGet('/form/test-form-archived'); + $this->assertRaw('This webform is <a href="' . $base_path . 'admin/structure/webform/manage/test_form_archived/settings">archived</a>'); + + // Check that archived webform is remove webform select menu. + $this->drupalGet('/node/add/webform'); + $this->assertRaw('<option value="contact">Contact</option>'); + $this->assertNoRaw('Test: Webform: Archive'); + + // Check that selected archived webform is preserved in webform select menu. + $this->drupalGet('/node/add/webform', ['query' => ['webform_id' => 'test_form_archived']]); + $this->assertRaw('<option value="contact">Contact</option>'); + $this->assertRaw('<optgroup label="Archived"><option value="test_form_archived" selected="selected">Test: Webform: Archive</option></optgroup>'); + + // Change the archived webform to be a template. + $webform->set('template', TRUE); + $webform->save(); + + // Change archived webform to template. + $this->drupalGet('/admin/structure/webform'); + $this->assertRaw('Contact'); + $this->assertNoRaw('Test: Webform: Archive'); + + // Check that archived template with (Template) label appears when archived filter selected. + $this->drupalGet('/admin/structure/webform', ['query' => ['state' => 'archived']]); + $this->assertNoRaw('Contact'); + $this->assertRaw('<td><a href="' . $base_path . 'form/test-form-archived">Test: Webform: Archive</a> <b>(Template)</b></td>'); + + // Check that archived template displays archive message + // (not template message). + $this->drupalGet('/form/test-form-archived'); + $this->assertRaw('This webform is <a href="' . $base_path . 'admin/structure/webform/manage/test_form_archived/settings">archived</a>'); + } + +} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsAssetsTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsAssetsTest.php index ab8afe0bde..3205c538bc 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsAssetsTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsAssetsTest.php @@ -26,7 +26,7 @@ public function testAssets() { $webform_assets = Webform::load('test_form_assets'); // Check has CSS and JavaScript. - $this->drupalGet('webform/test_form_assets'); + $this->drupalGet('/webform/test_form_assets'); $this->assertRaw('<link rel="stylesheet" href="' . base_path() . 'webform/css/test_form_assets?'); $this->assertRaw('<script src="' . base_path() . 'webform/javascript/test_form_assets?'); @@ -34,7 +34,7 @@ public function testAssets() { $webform_assets->setCss('')->setJavaScript('')->save(); // Check has no CSS or JavaScript. - $this->drupalGet('webform/test_form_assets'); + $this->drupalGet('/webform/test_form_assets'); $this->assertNoRaw('<link rel="stylesheet" href="' . base_path() . 'webform/css/test_form_assets?'); $this->assertNoRaw('<script src="' . base_path() . 'webform/javascript/test_form_assets?'); @@ -45,7 +45,7 @@ public function testAssets() { ->save(); // Check has global CSS and JavaScript. - $this->drupalGet('webform/test_form_assets'); + $this->drupalGet('/webform/test_form_assets'); $this->assertRaw('<link rel="stylesheet" href="' . base_path() . 'webform/css/test_form_assets?'); $this->assertRaw('<script src="' . base_path() . 'webform/javascript/test_form_assets?'); } diff --git a/web/modules/webform/src/Tests/WebformSubmissionFormAutofillTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsAutofillTest.php similarity index 83% rename from web/modules/webform/src/Tests/WebformSubmissionFormAutofillTest.php rename to web/modules/webform/src/Tests/Settings/WebformSettingsAutofillTest.php index 0e4e430b43..fe2a483cdb 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionFormAutofillTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsAutofillTest.php @@ -1,15 +1,16 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Settings; use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission form autofill. * * @group Webform */ -class WebformSubmissionFormAutofillTest extends WebformTestBase { +class WebformSettingsAutofillTest extends WebformTestBase { /** * Webforms to load. @@ -27,7 +28,7 @@ public function testAutofill() { $webform = Webform::load('test_form_autofill'); // Check that elements are both blank. - $this->drupalGet('webform/test_form_autofill'); + $this->drupalGet('/webform/test_form_autofill'); $this->assertNoRaw('This submission has been autofilled with your previous submission.'); $this->assertFieldByName('textfield_autofill', ''); $this->assertFieldByName('textfield_excluded', ''); @@ -41,13 +42,13 @@ public function testAutofill() { // Check that 'textfield_autofill' is autofilled and 'textfield_excluded' // is empty. - $this->drupalGet('webform/test_form_autofill'); + $this->drupalGet('/webform/test_form_autofill'); $this->assertFieldByName('textfield_autofill', '{textfield_autofill}'); $this->assertNoFieldByName('textfield_autofill', '{textfield_excluded}'); $this->assertFieldByName('textfield_excluded', ''); // Check that default configuration message is displayed. - $this->drupalGet('webform/test_form_autofill'); + $this->drupalGet('/webform/test_form_autofill'); $this->assertFieldByName('textfield_autofill', '{textfield_autofill}'); $this->assertRaw('This submission has been autofilled with your previous submission.'); @@ -57,7 +58,7 @@ public function testAutofill() { ->save(); // Check no autofill message is displayed. - $this->drupalGet('webform/test_form_autofill'); + $this->drupalGet('/webform/test_form_autofill'); $this->assertFieldByName('textfield_autofill', '{textfield_autofill}'); $this->assertNoRaw('This submission has been autofilled with your previous submission.'); @@ -67,7 +68,7 @@ public function testAutofill() { ->save(); // Check custom autofill message is displayed. - $this->drupalGet('webform/test_form_autofill'); + $this->drupalGet('/webform/test_form_autofill'); $this->assertFieldByName('textfield_autofill', '{textfield_autofill}'); $this->assertRaw('{autofill_message}'); } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsBehaviorsTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsBehaviorsTest.php index b5345ece84..219c5ba191 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsBehaviorsTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsBehaviorsTest.php @@ -52,7 +52,7 @@ public function testSettings() { $webform_form_submit_once = Webform::load('test_form_submit_once'); // Check webform has webform.form.submit_once.js. - $this->drupalGet('webform/test_form_submit_once'); + $this->drupalGet('/webform/test_form_submit_once'); $this->assertRaw('webform.form.submit_once.js'); // Disable YAML specific form_submit_once setting. @@ -60,11 +60,11 @@ public function testSettings() { $webform_form_submit_once->save(); // Check submit once checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_submit_once/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_submit_once/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-submit-once" aria-describedby="edit-form-submit-once--description" type="checkbox" id="edit-form-submit-once" name="form_submit_once" value class="form-checkbox" />'); // Check webform no longer has webform.form.submit_once.js. - $this->drupalGet('webform/test_form_submit_once'); + $this->drupalGet('/webform/test_form_submit_once'); $this->assertNoRaw('webform.form.submit_once.js'); // Enable default (global) submit_once on all webforms. @@ -73,12 +73,12 @@ public function testSettings() { ->save(); // Check submit_once checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_submit_once/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_submit_once/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-submit-once-disabled" aria-describedby="edit-form-submit-once-disabled--description" disabled="disabled" type="checkbox" id="edit-form-submit-once-disabled" name="form_submit_once_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Submit button is disabled immediately after it is clicked for all forms.'); // Check webform has webform.form.submit_once.js. - $this->drupalGet('webform/test_form_submit_once'); + $this->drupalGet('/webform/test_form_submit_once'); $this->assertRaw('webform.form.submit_once.js'); /**************************************************************************/ @@ -88,7 +88,7 @@ public function testSettings() { $webform_form_disable_back = Webform::load('test_form_disable_back'); // Check webform has webform.form.disable_back.js. - $this->drupalGet('webform/test_form_disable_back'); + $this->drupalGet('/webform/test_form_disable_back'); $this->assertRaw('webform.form.disable_back.js'); // Disable YAML specific form_disable_back setting. @@ -96,11 +96,11 @@ public function testSettings() { $webform_form_disable_back->save(); // Check disable_back checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_disable_back/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_disable_back/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-disable-back" aria-describedby="edit-form-disable-back--description" type="checkbox" id="edit-form-disable-back" name="form_disable_back" value class="form-checkbox" />'); // Check webform no longer has webform.form.disable_back.js. - $this->drupalGet('webform/test_form_disable_back'); + $this->drupalGet('/webform/test_form_disable_back'); $this->assertNoRaw('webform.form.disable_back.js'); // Enable default (global) disable_back on all webforms. @@ -109,12 +109,12 @@ public function testSettings() { ->save(); // Check disable_back checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_disable_back/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_disable_back/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-disable-back-disabled" aria-describedby="edit-form-disable-back-disabled--description" disabled="disabled" type="checkbox" id="edit-form-disable-back-disabled" name="form_disable_back_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Back button is disabled for all forms.'); // Check webform has webform.form.disable_back.js. - $this->drupalGet('webform/test_form_disable_back'); + $this->drupalGet('/webform/test_form_disable_back'); $this->assertRaw('webform.form.disable_back.js'); /**************************************************************************/ @@ -124,7 +124,7 @@ public function testSettings() { $webform_form_submit_back = Webform::load('test_form_submit_back'); // Check webform has webform.form.submit_back.js. - $this->drupalGet('webform/test_form_submit_back'); + $this->drupalGet('/webform/test_form_submit_back'); $this->assertRaw('webform.form.submit_back.js'); // Disable YAML specific form_submit_back setting. @@ -132,11 +132,11 @@ public function testSettings() { $webform_form_submit_back->save(); // Check submit_back checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_submit_back/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_submit_back/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-submit-back" aria-describedby="edit-form-submit-back--description" type="checkbox" id="edit-form-submit-back" name="form_submit_back" value class="form-checkbox" />'); // Check webform no longer has webform.form.submit_back.js. - $this->drupalGet('webform/test_form_submit_back'); + $this->drupalGet('/webform/test_form_submit_back'); $this->assertNoRaw('webform.form.submit_back.js'); // Enable default (global) submit_back on all webforms. @@ -145,12 +145,12 @@ public function testSettings() { ->save(); // Check submit_back checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_submit_back/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_submit_back/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-submit-back-disabled" aria-describedby="edit-form-submit-back-disabled--description" disabled="disabled" type="checkbox" id="edit-form-submit-back-disabled" name="form_submit_back_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Browser back button submits the previous page for all forms.'); // Check webform has webform.form.submit_back.js. - $this->drupalGet('webform/test_form_submit_back'); + $this->drupalGet('/webform/test_form_submit_back'); $this->assertRaw('webform.form.submit_back.js'); /**************************************************************************/ @@ -160,7 +160,7 @@ public function testSettings() { $webform_form_unsaved = Webform::load('test_form_unsaved'); // Check webform has .js-webform-unsaved class. - $this->drupalGet('webform/test_form_unsaved'); + $this->drupalGet('/webform/test_form_unsaved'); $this->assertCssSelect('form.js-webform-unsaved', t('Form has .js-webform-unsaved class.')); // Disable YAML specific webform unsaved setting. @@ -168,11 +168,11 @@ public function testSettings() { $webform_form_unsaved->save(); // Check novalidate checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_unsaved/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_unsaved/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-unsaved" aria-describedby="edit-form-unsaved--description" type="checkbox" id="edit-form-unsaved" name="form_unsaved" value class="form-checkbox" />'); // Check webform no longer has .js-webform-unsaved class. - $this->drupalGet('webform/test_form_novalidate'); + $this->drupalGet('/webform/test_form_novalidate'); $this->assertNoCssSelect('webform.js-webform-unsaved', t('Webform does not have .js-webform-unsaved class.')); // Enable default (global) unsaved on all webforms. @@ -181,12 +181,12 @@ public function testSettings() { ->save(); // Check unsaved checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_unsaved/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_unsaved/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-unsaved-disabled" aria-describedby="edit-form-unsaved-disabled--description" disabled="disabled" type="checkbox" id="edit-form-unsaved-disabled" name="form_unsaved_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Unsaved warning is enabled for all forms.'); // Check unsaved attribute added to webform. - $this->drupalGet('webform/test_form_unsaved'); + $this->drupalGet('/webform/test_form_unsaved'); $this->assertCssSelect('form.js-webform-unsaved', t('Form has .js-webform-unsaved class.')); /**************************************************************************/ @@ -194,7 +194,7 @@ public function testSettings() { /**************************************************************************/ // Check webform has autocomplete=off attribute. - $this->drupalGet('webform/test_form_disable_autocomplete'); + $this->drupalGet('/webform/test_form_disable_autocomplete'); $this->assertCssSelect('form[autocomplete="off"]', t('Form has autocomplete=off attribute.')); /**************************************************************************/ @@ -204,7 +204,7 @@ public function testSettings() { $webform_form_novalidate = Webform::load('test_form_novalidate'); // Check webform has novalidate attribute. - $this->drupalGet('webform/test_form_novalidate'); + $this->drupalGet('/webform/test_form_novalidate'); $this->assertCssSelect('form[novalidate="novalidate"]', t('Form has the proper novalidate attribute.')); // Disable YAML specific webform client-side validation setting. @@ -212,12 +212,12 @@ public function testSettings() { $webform_form_novalidate->save(); // Check novalidate checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_novalidate/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_novalidate/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-novalidate" aria-describedby="edit-form-novalidate--description" type="checkbox" id="edit-form-novalidate" name="form_novalidate" value class="form-checkbox" />'); $this->assertRaw('If checked, the <a href="http://www.w3schools.com/tags/att_form_novalidate.asp">novalidate</a> attribute, which disables client-side validation, will be added to this form.'); // Check webform no longer has novalidate attribute. - $this->drupalGet('webform/test_form_novalidate'); + $this->drupalGet('/webform/test_form_novalidate'); $this->assertNoCssSelect('form[novalidate="novalidate"]', t('Webform have client-side validation enabled.')); // Enable default (global) disable client-side validation on all webforms. @@ -226,13 +226,13 @@ public function testSettings() { ->save(); // Check novalidate checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_novalidate/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_novalidate/settings/form'); $this->assertNoRaw('If checked, the <a href="http://www.w3schools.com/tags/att_form_novalidate.asp">novalidate</a> attribute, which disables client-side validation, will be added to this form.'); $this->assertRaw('<input data-drupal-selector="edit-form-novalidate-disabled" aria-describedby="edit-form-novalidate-disabled--description" disabled="disabled" type="checkbox" id="edit-form-novalidate-disabled" name="form_novalidate_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Client-side validation is disabled for all forms.'); // Check novalidate attribute added to webform. - $this->drupalGet('webform/test_form_novalidate'); + $this->drupalGet('/webform/test_form_novalidate'); $this->assertCssSelect('form[novalidate="novalidate"]', t('Form has the proper novalidate attribute.')); /**************************************************************************/ @@ -242,7 +242,7 @@ public function testSettings() { $webform_form_required = Webform::load('test_form_required'); // Check webform has required indicator. - $this->drupalGet('webform/test_form_required'); + $this->drupalGet('/webform/test_form_required'); $this->assertRaw('Indicates required field'); // Disable required indicator. @@ -250,7 +250,7 @@ public function testSettings() { $webform_form_required->save(); // Check webform does not have have required indicator. - $this->drupalGet('webform/test_form_required'); + $this->drupalGet('/webform/test_form_required'); $this->assertNoRaw('Indicates required field'); // Enable default (global) required indicator on all webforms. @@ -260,12 +260,12 @@ public function testSettings() { ->save(); // Check required checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_required/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_required/settings/form'); $this->assertRaw('Required indicator is displayed on all forms.'); $this->assertRaw('<input data-drupal-selector="edit-form-required-disabled" aria-describedby="edit-form-required-disabled--description" disabled="disabled" type="checkbox" id="edit-form-required-disabled" name="form_required_disabled" value="1" checked="checked" class="form-checkbox" />'); // Check global required indicator added to webform. - $this->drupalGet('webform/test_form_required'); + $this->drupalGet('/webform/test_form_required'); $this->assertRaw('Custom required field'); $elements = $webform_form_required->getElementsDecoded(); @@ -274,7 +274,7 @@ public function testSettings() { $webform_form_required->save(); // Check required indicator not added to webform with no required elements. - $this->drupalGet('webform/test_form_required'); + $this->drupalGet('/webform/test_form_required'); $this->assertNoRaw('Custom required field'); /**************************************************************************/ @@ -282,7 +282,7 @@ public function testSettings() { /**************************************************************************/ // Check webform has autofocus class. - $this->drupalGet('webform/test_form_autofocus'); + $this->drupalGet('/webform/test_form_autofocus'); $this->assertCssSelect('.js-webform-autofocus'); /**************************************************************************/ @@ -292,11 +292,11 @@ public function testSettings() { $webform_form_details_toggle = Webform::load('test_form_details_toggle'); // Check webform has .webform-details-toggle class. - $this->drupalGet('webform/test_form_details_toggle'); + $this->drupalGet('/webform/test_form_details_toggle'); $this->assertCssSelect('form.webform-details-toggle', t('Form has the .webform-details-toggle class.')); // Check details toggle checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_details_toggle/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_details_toggle/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-details-toggle-disabled" aria-describedby="edit-form-details-toggle-disabled--description" disabled="disabled" type="checkbox" id="edit-form-details-toggle-disabled" name="form_details_toggle_disabled" value="1" checked="checked" class="form-checkbox" />'); $this->assertRaw('Expand/collapse all (details) link is automatically added to all forms.'); @@ -306,11 +306,11 @@ public function testSettings() { ->save(); // Check .webform-details-toggle class still added to webform. - $this->drupalGet('webform/test_form_details_toggle'); + $this->drupalGet('/webform/test_form_details_toggle'); $this->assertCssSelect('form.webform-details-toggle', t('Form has the .webform-details-toggle class.')); // Check details toggle checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_details_toggle/settings/form'); + $this->drupalGet('/admin/structure/webform/manage/test_form_details_toggle/settings/form'); $this->assertRaw('<input data-drupal-selector="edit-form-details-toggle" aria-describedby="edit-form-details-toggle--description" type="checkbox" id="edit-form-details-toggle" name="form_details_toggle" value checked="checked" class="form-checkbox" />'); $this->assertRaw('If checked, an expand/collapse all (details) link will be added to this webform when there are two or more details elements available on the webform.'); @@ -319,73 +319,56 @@ public function testSettings() { $webform_form_details_toggle->save(); // Check webform does not hav .webform-details-toggle class. - $this->drupalGet('webform/test_form_details_toggle'); + $this->drupalGet('/webform/test_form_details_toggle'); $this->assertNoCssSelect('webform.webform-details-toggle', t('Webform does not have the .webform-details-toggle class.')); /**************************************************************************/ - /* Test webform disable inline form errors (test_form_disable_inline_errors) */ + /* Test webform disable inline form errors (test_form_disable_inline_errors) */ /**************************************************************************/ $webform_form_inline_errors = Webform::load('test_form_disable_inline_errors'); - if (floatval(\Drupal::VERSION) >= 8.5) { - // Check that error message is displayed at the top of the page. - $this->postSubmission($webform_form_inline_errors); - $this->assertPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); - - // Enable the inline form errors module. - \Drupal::service('module_installer')->install(['inline_form_errors']); - - // Check that error message is still displayed at the top of the page. - $this->postSubmission($webform_form_inline_errors); - $this->assertPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); - - // Allow inline error message for this form. - $webform_form_inline_errors->setSetting('form_disable_inline_errors', FALSE); - $webform_form_inline_errors->save(); - - // Check that error message is not displayed at the top of the page. - $this->postSubmission($webform_form_inline_errors); - $this->assertNoPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); - - // Check that error message is displayed inline. - $this->assertRaw('1 error has been found: <div class="item-list--comma-list item-list"><ul class="item-list__comma-list"><li><a href="#edit-textfield">textfield</a></li></ul>'); - $this->assertRaw('<strong>textfield field is required.</strong>'); - - // Check disable inline errors checkbox is enabled. - $this->drupalGet('admin/structure/webform/manage/test_form_disable_inline_errors/settings/form'); - $this->assertRaw('<input data-drupal-selector="edit-form-disable-inline-errors" aria-describedby="edit-form-disable-inline-errors--description" type="checkbox" id="edit-form-disable-inline-errors" name="form_disable_inline_errors" value class="form-checkbox" />'); - $this->assertRaw('If checked, <a href="https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-form-errors-module-overview">inline form errors</a> will be disabled for this form.'); - - // Enable default (global) disable inline form errors on all webforms. - \Drupal::configFactory()->getEditable('webform.settings') - ->set('settings.default_form_disable_inline_errors', TRUE) - ->save(); - - // Check novalidate checkbox is disabled. - $this->drupalGet('admin/structure/webform/manage/test_form_disable_inline_errors/settings/form'); - $this->assertRaw('<input data-drupal-selector="edit-form-disable-inline-errors-disabled" aria-describedby="edit-form-disable-inline-errors-disabled--description" disabled="disabled" type="checkbox" id="edit-form-disable-inline-errors-disabled" name="form_disable_inline_errors_disabled" value="1" checked="checked" class="form-checkbox" />'); - $this->assertRaw('Inline form errors is disabled for all forms.'); - - // Check that error message is not displayed inline. - $this->assertNoRaw('1 error has been found: <div class="item-list--comma-list item-list"><ul class="item-list__comma-list"><li><a href="#edit-textfield">textfield</a></li></ul>'); - $this->assertNoRaw('<strong>textfield field is required.</strong>'); - } - else { - // Check that error message is displayed at the top of the page. - $this->postSubmission($webform_form_inline_errors); - $this->assertPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); - - // Enable the inline form errors module. - \Drupal::service('module_installer')->install(['inline_form_errors']); - - // Check that error message is NOT displayed at the top of the page. - $this->postSubmission($webform_form_inline_errors); - $this->assertNoPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); - - // Check that disable inline form errors is not visible in < 8.4.x. - $this->drupalGet('admin/structure/webform/config'); - $this->assertNoRaw('Disable inline form errors for all webforms'); - } + + // Check that error message is displayed at the top of the page. + $this->postSubmission($webform_form_inline_errors); + $this->assertPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); + + // Enable the inline form errors module. + \Drupal::service('module_installer')->install(['inline_form_errors']); + + // Check that error message is still displayed at the top of the page. + $this->postSubmission($webform_form_inline_errors); + $this->assertPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); + + // Allow inline error message for this form. + $webform_form_inline_errors->setSetting('form_disable_inline_errors', FALSE); + $webform_form_inline_errors->save(); + + // Check that error message is not displayed at the top of the page. + $this->postSubmission($webform_form_inline_errors); + $this->assertNoPattern('#<h2 class="visually-hidden">Error message</h2>\s+textfield field is required.#m'); + + // Check that error message is displayed inline. + $this->assertRaw('1 error has been found: <div class="item-list--comma-list item-list"><ul class="item-list__comma-list"><li><a href="#edit-textfield">textfield</a></li></ul>'); + $this->assertRaw('<strong>textfield field is required.</strong>'); + + // Check disable inline errors checkbox is enabled. + $this->drupalGet('/admin/structure/webform/manage/test_form_disable_inline_errors/settings/form'); + $this->assertRaw('<input data-drupal-selector="edit-form-disable-inline-errors" aria-describedby="edit-form-disable-inline-errors--description" type="checkbox" id="edit-form-disable-inline-errors" name="form_disable_inline_errors" value class="form-checkbox" />'); + $this->assertRaw('If checked, <a href="https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-form-errors-module-overview">inline form errors</a> will be disabled for this form.'); + + // Enable default (global) disable inline form errors on all webforms. + \Drupal::configFactory()->getEditable('webform.settings') + ->set('settings.default_form_disable_inline_errors', TRUE) + ->save(); + + // Check novalidate checkbox is disabled. + $this->drupalGet('/admin/structure/webform/manage/test_form_disable_inline_errors/settings/form'); + $this->assertRaw('<input data-drupal-selector="edit-form-disable-inline-errors-disabled" aria-describedby="edit-form-disable-inline-errors-disabled--description" disabled="disabled" type="checkbox" id="edit-form-disable-inline-errors-disabled" name="form_disable_inline_errors_disabled" value="1" checked="checked" class="form-checkbox" />'); + $this->assertRaw('Inline form errors is disabled for all forms.'); + + // Check that error message is not displayed inline. + $this->assertNoRaw('1 error has been found: <div class="item-list--comma-list item-list"><ul class="item-list__comma-list"><li><a href="#edit-textfield">textfield</a></li></ul>'); + $this->assertNoRaw('<strong>textfield field is required.</strong>'); } } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsConfidentialTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsConfidentialTest.php index fa821cd741..5cd560d8e1 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsConfidentialTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsConfidentialTest.php @@ -2,6 +2,7 @@ namespace Drupal\webform\Tests\Settings; +use Drupal\user\Entity\Role; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Tests\WebformTestBase; @@ -20,56 +21,56 @@ class WebformSettingsConfidentialTest extends WebformTestBase { */ protected static $testWebforms = ['test_form_confidential']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - $this->addWebformSubmissionOwnPermissionsToAnonymous(); - } - /** * Tests webform confidential setting. */ public function testConfidential() { + /** @var \Drupal\user\RoleInterface $anonymous_role */ + $anonymous_role = Role::load('anonymous'); + $anonymous_role->grantPermission('view own webform submission') + ->grantPermission('edit own webform submission') + ->grantPermission('delete own webform submission') + ->save(); + + /**************************************************************************/ + $this->drupalLogin($this->rootUser); - $webform_confidential = Webform::load('test_form_confidential'); + $webform = Webform::load('test_form_confidential'); // Check logout warning when accessing webform. - $this->drupalGet('webform/test_form_confidential'); + $this->drupalGet('/webform/test_form_confidential'); $this->assertNoFieldById('edit-name'); $this->assertRaw('This form is confidential.'); // Check no logout warning when testing webform. - $this->drupalGet('webform/test_form_confidential/test'); + $this->drupalGet('/webform/test_form_confidential/test'); $this->assertFieldById('edit-name'); $this->assertNoRaw('This form is confidential.'); - // Check that test submission does not record the IP address - $sid = $this->postSubmissionTest($webform_confidential, ['name' => 'John']); + // Check that test submission does not record the IP address. + $sid = $this->postSubmissionTest($webform, ['name' => 'John']); $webform_submission = WebformSubmission::load($sid); $this->assertEqual($webform_submission->getRemoteAddr(), t('(unknown)')); $this->assertEqual($webform_submission->getOwnerId(), 0); // Check anonymous access to webform. $this->drupalLogout(); - $this->drupalGet('webform/test_form_confidential'); + $this->drupalGet('/webform/test_form_confidential'); $this->assertFieldById('edit-name'); $this->assertNoRaw('This form is confidential.'); // Check that submission does not track the requests IP address. - $sid = $this->postSubmission($webform_confidential, ['name' => 'John']); + $sid = $this->postSubmission($webform, ['name' => 'John']); $webform_submission = WebformSubmission::load($sid); $this->assertEqual($webform_submission->getRemoteAddr(), t('(unknown)')); $this->assertEqual($webform_submission->getOwnerId(), 0); // Check that previous submissions are visible. - $this->drupalGet('webform/test_form_confidential'); + $this->drupalGet('/webform/test_form_confidential'); $this->assertRaw('View your previous submission'); - // Check that anonymous submissison is not converted to authenticated. + // Check that anonymous submission is not converted to authenticated. // @see \Drupal\webform\WebformSubmissionStorage::userLogin $this->drupalLogin($this->rootUser); $webform_submission = $this->loadSubmission($sid); @@ -77,7 +78,7 @@ public function testConfidential() { // Check that previous submissions $_SESSION was unset after login/logout. $this->drupalLogout(); - $this->drupalGet('webform/test_form_confidential'); + $this->drupalGet('/webform/test_form_confidential'); $this->assertNoRaw('View your previous submission.'); } diff --git a/web/modules/webform/src/Tests/WebformSubmissionFormConfirmationTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsConfirmationTest.php similarity index 83% rename from web/modules/webform/src/Tests/WebformSubmissionFormConfirmationTest.php rename to web/modules/webform/src/Tests/Settings/WebformSettingsConfirmationTest.php index 21f8c1bd35..f03b5caefa 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionFormConfirmationTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsConfirmationTest.php @@ -1,23 +1,33 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Settings; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission form confirmation. * * @group Webform */ -class WebformSubmissionFormConfirmationTest extends WebformTestBase { +class WebformSettingsConfirmationTest extends WebformTestBase { /** * Webforms to load. * * @var array */ - protected static $testWebforms = ['test_confirmation_message', 'test_confirmation_modal', 'test_confirmation_inline', 'test_confirmation_page', 'test_confirmation_page_custom', 'test_confirmation_url', 'test_confirmation_url_message']; + protected static $testWebforms = [ + 'test_confirmation_message', + 'test_confirmation_modal', + 'test_confirmation_inline', + 'test_confirmation_page', + 'test_confirmation_page_custom', + 'test_confirmation_url', + 'test_confirmation_url_message', + 'test_confirmation_none', + ]; /** * {@inheritdoc} @@ -69,12 +79,12 @@ public function testConfirmation() { // Check confirmation inline. $this->drupalPostForm('webform/test_confirmation_inline', [], t('Submit')); - $this->assertRaw('<a href="' . $webform_confirmation_inline->toUrl('canonical', ['absolute' => TRUE])->toString() . '" rel="back" title="Back to form">Back to form</a>'); + $this->assertRaw('<a href="' . $webform_confirmation_inline->toUrl('canonical', ['absolute' => TRUE])->toString() . '" rel="prev" title="Back to form">Back to form</a>'); $this->assertUrl('webform/test_confirmation_inline'); // Check confirmation inline with custom query parameters. $this->drupalPostForm('webform/test_confirmation_inline', [], t('Submit'), ['query' => ['custom' => 'param']]); - $this->assertRaw('<a href="' . $webform_confirmation_inline->toUrl('canonical', ['absolute' => TRUE, 'query' => ['custom' => 'param']])->toString() . '" rel="back" title="Back to form">Back to form</a>'); + $this->assertRaw('<a href="' . $webform_confirmation_inline->toUrl('canonical', ['absolute' => TRUE, 'query' => ['custom' => 'param']])->toString() . '" rel="prev" title="Back to form">Back to form</a>'); $this->assertUrl('webform/test_confirmation_inline', ['query' => ['custom' => 'param']]); /* Test confirmation page (confirmation_type=page) */ @@ -85,12 +95,12 @@ public function testConfirmation() { $sid = $this->postSubmission($webform_confirmation_page); $webform_submission = WebformSubmission::load($sid); $this->assertRaw('This is a custom confirmation page.'); - $this->assertRaw('<a href="' . $webform_confirmation_page->toUrl('canonical', ['absolute' => TRUE])->toString() . '" rel="back" title="Back to form">Back to form</a>'); + $this->assertRaw('<a href="' . $webform_confirmation_page->toUrl('canonical', ['absolute' => TRUE])->toString() . '" rel="prev" title="Back to form">Back to form</a>'); $this->assertUrl('webform/test_confirmation_page/confirmation', ['query' => ['token' => $webform_submission->getToken()]]); // Check that the confirmation page's 'Back to form 'link includes custom // query parameters. - $this->drupalGet('webform/test_confirmation_page/confirmation', ['query' => ['custom' => 'param']]); + $this->drupalGet('/webform/test_confirmation_page/confirmation', ['query' => ['custom' => 'param']]); // Check confirmation page with custom query parameters. $sid = $this->postSubmission($webform_confirmation_page, [], NULL, ['query' => ['custom' => 'param']]); @@ -109,7 +119,7 @@ public function testConfirmation() { $this->postSubmission($webform_confirmation_page); $this->assertUrl('webform/test_confirmation_page/confirmation'); - // TODO: (TESTING) Figure out why the inline confirmation link is not including the query string parameters. + // TODO: (TESTING) Figure out why the inline confirmation link is not including the query string parameters. // $this->assertRaw('<a href="' . $webform_confirmation_page->toUrl()->toString() . '?custom=param">Back to form</a>');. /* Test confirmation page custom (confirmation_type=page) */ @@ -120,13 +130,13 @@ public function testConfirmation() { $this->postSubmission($webform_confirmation_page_custom); $this->assertRaw('<h1 class="page-title">Custom confirmation page title</h1>'); $this->assertRaw('<div style="border: 10px solid red; padding: 1em;" class="webform-confirmation">'); - $this->assertRaw('<a href="' . $webform_confirmation_page_custom->toUrl()->setAbsolute()->toString() . '" rel="back" title="Custom back to link" class="button">Custom back to link</a>'); + $this->assertRaw('<a href="' . $webform_confirmation_page_custom->toUrl()->setAbsolute()->toString() . '" rel="prev" title="Custom back to link" class="button">Custom back to link</a>'); // Check back link is hidden. $webform_confirmation_page_custom->setSetting('confirmation_back', FALSE); $webform_confirmation_page_custom->save(); $this->postSubmission($webform_confirmation_page_custom); - $this->assertNoRaw('<a href="' . $webform_confirmation_page_custom->toUrl()->toString() . '" rel="back" title="Custom back to link" class="button">Custom back to link</a>'); + $this->assertNoRaw('<a href="' . $webform_confirmation_page_custom->toUrl()->toString() . '" rel="prev" title="Custom back to link" class="button">Custom back to link</a>'); /* Test confirmation URL (confirmation_type=url) */ @@ -146,6 +156,16 @@ public function testConfirmation() { $this->assertRaw('<h2 class="visually-hidden">Status message</h2>'); $this->assertRaw('This is a custom confirmation message.'); $this->assertUrl('<front>'); + + /* Test confirmation none (confirmation_type=none) */ + + $this->drupalLogout(); + $webform_confirmation_url_message = Webform::load('test_confirmation_none'); + + // Check no confirmation message. + $this->postSubmission($webform_confirmation_url_message); + $this->assertNoRaw('<h2 class="visually-hidden">Status message</h2>'); + } } diff --git a/web/modules/webform/src/Tests/WebformSubmissionFormDraftTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsDraftTest.php similarity index 63% rename from web/modules/webform/src/Tests/WebformSubmissionFormDraftTest.php rename to web/modules/webform/src/Tests/Settings/WebformSettingsDraftTest.php index 4f83c41206..12799522fb 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionFormDraftTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsDraftTest.php @@ -1,16 +1,17 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Settings; use Drupal\webform\Entity\Webform; use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission form draft. * * @group Webform */ -class WebformSubmissionFormDraftTest extends WebformTestBase { +class WebformSettingsDraftTest extends WebformTestBase { /** * Webforms to load. @@ -19,20 +20,31 @@ class WebformSubmissionFormDraftTest extends WebformTestBase { */ protected static $testWebforms = ['test_form_draft_authenticated', 'test_form_draft_anonymous', 'test_form_draft_multiple', 'test_form_preview']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test webform submission form draft. */ public function testDraft() { + $normal_user = $this->drupalCreateUser(['view own webform submission']); + + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + /**************************************************************************/ + // Draft access. + /**************************************************************************/ + + // Check access denied to review drafts when disabled. + $this->drupalGet('/webform/contact/drafts'); + $this->assertResponse(403); + + // Check access denied to review authenticated drafts. + $this->drupalGet('/webform/test_form_draft_authenticated/drafts'); + $this->assertResponse(403); + + // Check access allowed to review anonymous drafts. + $this->drupalGet('/webform/test_form_draft_anonymous/drafts'); + $this->assertResponse(200); /**************************************************************************/ // Autosave for anonymous draft to authenticated draft. @@ -46,7 +58,7 @@ public function testDraft() { $is_authenticated = ($webform_id == 'test_form_draft_authenticated') ? TRUE : FALSE; // Login draft account. - ($is_authenticated) ? $this->drupalLogin($this->normalUser) : $this->drupalLogout(); + ($is_authenticated) ? $this->drupalLogin($normal_user) : $this->drupalLogout(); $webform = Webform::load($webform_id); @@ -59,21 +71,33 @@ public function testDraft() { $this->assertRaw('Your draft has been saved'); $this->assertNoRaw('You have an existing draft'); + // Check access allowed to review drafts. + $this->drupalGet("webform/$webform_id/drafts"); + $this->assertResponse(200); + // Check loaded draft message. $this->drupalGet("webform/$webform_id"); $this->assertNoRaw('Your draft has been saved'); $this->assertRaw('You have an existing draft'); $this->assertFieldByName('name', 'John Smith'); + // Check no draft message when webform is closed. + $webform->setStatus(FALSE)->save(); + $this->drupalGet("webform/$webform_id"); + $this->assertNoRaw('You have an existing draft'); + $this->assertNoFieldByName('name', 'John Smith'); + $this->assertRaw('Sorry…This form is closed to new submissions.'); + $webform->setStatus(TRUE)->save(); + // Login admin account. - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); // Check submission. $this->drupalGet("admin/structure/webform/manage/$webform_id/submission/$sid"); $this->assertRaw('<div><b>Is draft:</b> Yes</div>'); // Login draft account. - ($is_authenticated) ? $this->drupalLogin($this->normalUser) : $this->drupalLogout(); + ($is_authenticated) ? $this->drupalLogin($normal_user) : $this->drupalLogout(); // Check update draft and bypass validation. $this->drupalPostForm("webform/$webform_id", [ @@ -126,24 +150,24 @@ public function testDraft() { $this->assertEqual($webform_submission->getOwnerId(), 0); // Check loaded draft message. - $this->drupalGet('webform/test_form_draft_anonymous'); + $this->drupalGet('/webform/test_form_draft_anonymous'); $this->assertRaw('You have an existing draft'); $this->assertFieldByName('name', 'John Smith'); // Login the normal user. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); // Check that submission is now owned by the normal user. \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); $webform_submission = WebformSubmission::load($sid); - $this->assertEqual($webform_submission->getOwnerId(), $this->normalUser->id()); + $this->assertEqual($webform_submission->getOwnerId(), $normal_user->id()); // Check that drafts are not convert when form_convert_anonymous = FALSE. $this->drupalLogout(); $webform->setSetting('form_convert_anonymous', FALSE)->save(); $sid = $this->postSubmission($webform, ['name' => 'John Smith']); - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); // Check that submission is still owned by anonymous user. \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); @@ -166,16 +190,16 @@ public function testDraft() { $this->assertEqual($webform_submission->getOwnerId(), 0); // Check loaded draft message does NOT appear on confidential submissions. - $this->drupalGet('webform/test_form_draft_anonymous'); + $this->drupalGet('/webform/test_form_draft_anonymous'); $this->assertRaw('You have an existing draft'); // Login the normal user. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); $webform_submission = WebformSubmission::load($sid); // Check that submission is NOT owned by the normal user. - $this->assertNotEqual($webform_submission->getOwnerId(), $this->normalUser->id()); + $this->assertNotEqual($webform_submission->getOwnerId(), $normal_user->id()); // Check that submission is still anonymous. $this->assertEqual($webform_submission->getOwnerId(), 0); @@ -184,35 +208,34 @@ public function testDraft() { // Export. /**************************************************************************/ - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); // Check export with draft settings. - $this->drupalGet('admin/structure/webform/manage/test_form_draft_authenticated/results/download'); + $this->drupalGet('/admin/structure/webform/manage/test_form_draft_authenticated/results/download'); $this->assertFieldByName('state', 'all'); // Check export without draft settings. - $this->drupalGet('admin/structure/webform/manage/test_form_preview/results/download'); + $this->drupalGet('/admin/structure/webform/manage/test_form_preview/results/download'); $this->assertNoFieldByName('state', 'all'); // Check autosave on submit with validation errors. $this->drupalPostForm('webform/test_form_draft_authenticated', [], t('Submit')); $this->assertRaw('Name field is required.'); - $this->drupalGet('webform/test_form_draft_authenticated'); + $this->drupalGet('/webform/test_form_draft_authenticated'); $this->assertRaw('You have an existing draft'); // Check autosave on preview. $this->drupalPostForm('webform/test_form_draft_authenticated', ['name' => 'John Smith'], t('Preview')); $this->assertRaw('Please review your submission.'); - $this->drupalGet('webform/test_form_draft_authenticated'); + $this->drupalGet('/webform/test_form_draft_authenticated'); $this->assertRaw('You have an existing draft'); $this->assertRaw('<label>Name</label>' . PHP_EOL . ' John Smith'); - } - /** - * Test webform draft multiple. - */ - public function testDraftMultiple() { - $this->drupalLogin($this->normalUser); + /**************************************************************************/ + // Test webform draft multiple. + /**************************************************************************/ + + $this->drupalLogin($normal_user); $webform = Webform::load('test_form_draft_multiple'); @@ -222,38 +245,38 @@ public function testDraftMultiple() { $webform_submission_1 = WebformSubmission::load($sid_1); // Check restore first draft. - $this->drupalGet('webform/test_form_draft_multiple'); + $this->drupalGet('/webform/test_form_draft_multiple'); $this->assertNoRaw('You have saved drafts.'); $this->assertRaw('You have a pending draft for this webform.'); $this->assertFieldByName('name', ''); // Check load pending draft using token. - $this->drupalGet('webform/test_form_draft_multiple'); + $this->drupalGet('/webform/test_form_draft_multiple'); $this->clickLink('Load your pending draft'); $this->assertFieldByName('name', 'John Smith'); - $this->drupalGet('webform/test_form_draft_multiple', ['query' => ['token' => $webform_submission_1->getToken()]]); + $this->drupalGet('/webform/test_form_draft_multiple', ['query' => ['token' => $webform_submission_1->getToken()]]); $this->assertFieldByName('name', 'John Smith'); // Check user drafts. - $this->drupalGet('webform/test_form_draft_multiple/drafts'); + $this->drupalGet('/webform/test_form_draft_multiple/drafts'); $this->assertRaw('token=' . $webform_submission_1->getToken()); // Save second draft. $sid_2 = $this->postSubmission($webform, ['name' => 'John Smith'], t('Save Draft')); $webform_submission_2 = WebformSubmission::load($sid_2); $this->assertRaw('Submission saved. You may return to this form later and it will restore the current values.'); - $this->drupalGet('webform/test_form_draft_multiple'); + $this->drupalGet('/webform/test_form_draft_multiple'); $this->assertNoRaw('You have a pending draft for this webform.'); $this->assertRaw('You have pending drafts for this webform. <a href="' . base_path() . 'webform/test_form_draft_multiple/drafts">View your pending drafts</a>.'); // Check user drafts now has second draft. - $this->drupalGet('webform/test_form_draft_multiple/drafts'); + $this->drupalGet('/webform/test_form_draft_multiple/drafts'); $this->assertRaw('token=' . $webform_submission_1->getToken()); $this->assertRaw('token=' . $webform_submission_2->getToken()); // Check that anonymous user can't load drafts. $this->drupalLogout(); - $this->drupalGet('webform/test_form_draft_multiple', ['query' => ['token' => $webform_submission_1->getToken()]]); + $this->drupalGet('/webform/test_form_draft_multiple', ['query' => ['token' => $webform_submission_1->getToken()]]); $this->assertFieldByName('name', ''); // Save third anonymous draft. @@ -261,14 +284,49 @@ public function testDraftMultiple() { $this->assertRaw('Submission saved. You may return to this form later and it will restore the current values.'); // Check restore third anonymous draft. - $this->drupalGet('webform/test_form_draft_multiple'); + $this->drupalGet('/webform/test_form_draft_multiple'); $this->assertNoRaw('You have saved drafts.'); $this->assertRaw('You have a pending draft for this webform.'); $this->assertFieldByName('name', ''); - $this->drupalGet('webform/test_form_draft_multiple'); + $this->drupalGet('/webform/test_form_draft_multiple'); $this->clickLink('Load your pending draft'); $this->assertFieldByName('name', 'Jane Doe'); + + /**************************************************************************/ + // Test webform submission form reset draft. + /**************************************************************************/ + + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('test_form_draft_authenticated'); + + // Check saved draft. + $sid = $this->postSubmission($webform, ['name' => 'John Smith'], t('Save Draft')); + $this->assertNotNull($sid); + $webform_submission = WebformSubmission::load($sid); + $this->assertEqual($sid, $webform_submission->id()); + + // Check reset delete's the draft. + $this->postSubmission($webform, [], t('Reset')); + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + $webform_submission = WebformSubmission::load($sid); + $this->assertNull($webform_submission); + + // Check submission with comment. + $sid = $this->postSubmission($webform, ['name' => 'John Smith', 'comment' => 'This is a comment'], t('Save Draft')); + $this->postSubmission($webform); + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + $webform_submission = WebformSubmission::load($sid); + $this->assertEqual('This is a comment', $webform_submission->getElementData('comment')); + + // Check submitted draft is not delete on reset. + $this->drupalPostForm('/admin/structure/webform/manage/test_form_draft_authenticated/submission/' . $sid . '/edit', ['comment' => 'This is ignored'], t('Reset')); + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + $webform_submission = WebformSubmission::load($sid); + $this->assertEqual($sid, $webform_submission->id()); + $this->assertEqual('This is a comment', $webform_submission->getElementData('comment')); + $this->assertNotEqual('This is ignored', $webform_submission->getElementData('comment')); } } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsFormTitleTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsFormTitleTest.php new file mode 100644 index 0000000000..51c41e2bd2 --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsFormTitleTest.php @@ -0,0 +1,89 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\Core\Serialization\Yaml; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; +use Drupal\webform\WebformInterface; + +/** + * Tests for webform form title. + * + * @group Webform + */ +class WebformSettingsFormTitleTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'node']; + + /** + * Tests form title. + */ + public function testTitle() { + $node = $this->drupalCreateNode(['title' => 'test_node']); + + $webform = Webform::create([ + 'langcode' => 'en', + 'status' => WebformInterface::STATUS_OPEN, + 'id' => 'test_webform', + 'title' => 'test_webform', + 'elements' => Yaml::encode([ + 'test' => ['#markup' => 'test'], + ]), + 'settings' => [ + 'form_prepopulate_source_entity' => TRUE, + ], + ]); + $webform->save(); + + $options = ['query' => ['source_entity_type' => 'node', 'source_entity_id' => $node->id()]]; + + /**************************************************************************/ + + // Check webform title. + $this->drupalGet('/webform/test_webform'); + $this->assertRaw('<title>test_webform | Drupal</title>'); + + // Check (default) both title. + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_node: test_webform | Drupal</title>'); + + // Check webform and source entity title. + $webform + ->setSetting('form_title', WebformInterface::TITLE_WEBFORM_SOURCE_ENTITY) + ->save(); + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_webform: test_node | Drupal</title>'); + + // Check source entity title. + $webform + ->setSetting('form_title', WebformInterface::TITLE_SOURCE_ENTITY) + ->save(); + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_node | Drupal</title>'); + + // Check webform title. + $webform + ->setSetting('form_title', WebformInterface::TITLE_WEBFORM) + ->save(); + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_webform | Drupal</title>'); + + // Check duplicate titles. + $webform + ->setSetting('form_title', WebformInterface::TITLE_SOURCE_ENTITY_WEBFORM) + ->save(); + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_node: test_webform | Drupal</title>'); + $webform->set('title', 'test_node') + ->save(); + $this->drupalGet('/webform/test_webform', $options); + $this->assertRaw('<title>test_node | Drupal</title>'); + } + +} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsLimitUniqueTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsLimitUniqueTest.php new file mode 100644 index 0000000000..19bb3e94ab --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsLimitUniqueTest.php @@ -0,0 +1,164 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\webform\Entity\Webform; +use Drupal\webform_node\Tests\WebformNodeTestBase; + +/** + * Tests for webform submission form unique limit. + * + * @group Webform + */ +class WebformSettingsLimitUniqueTest extends WebformNodeTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'webform_node']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_form_limit_total_unique', 'test_form_limit_user_unique']; + + /** + * Tests webform submission form unique limit. + */ + public function testLimitUnique() { + $admin_user = $this->drupalCreateUser(['administer webform']); + + $webform_total_unique = Webform::load('test_form_limit_total_unique'); + $webform_user_unique = Webform::load('test_form_limit_user_unique'); + + /**************************************************************************/ + // Total unique. (webform) + /**************************************************************************/ + + // Check that name is empty for new submission for admin user. + $this->drupalLogin($admin_user); + $this->drupalGet('/webform/test_form_limit_total_unique'); + $this->assertFieldByName('name', ''); + + // Check that 'Test' form is available and display a message. + $this->drupalGet('/webform/test_form_limit_total_unique/test'); + $this->assertRaw(' The below webform has been prepopulated with custom/random test data. When submitted, this information <strong>will still be saved</strong> and/or <strong>sent to designated recipients</strong>'); + + // Check that name is empty for new submission for root user. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/webform/test_form_limit_total_unique'); + $this->assertFieldByName('name', ''); + + // Check that name is set to 'John Smith' and 'Submission information' is + // visible for admin user. + $this->drupalLogin($admin_user); + $sid = $this->postSubmission($webform_total_unique, ['name' => 'John Smith']); + $this->drupalGet('/webform/test_form_limit_total_unique'); + $this->assertFieldByName('name', 'John Smith'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that name is set to 'John Smith' and 'Submission information' is + // visible for root user. + $this->drupalGet('/webform/test_form_limit_total_unique'); + $this->assertFieldByName('name', 'John Smith'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that 'Test' form also has name set to 'John Smith' + // and does not display a message. + $this->drupalGet('/webform/test_form_limit_total_unique/test'); + $this->assertFieldByName('name', 'John Smith'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + $this->assertNoRaw(' The below webform has been prepopulated with custom/random test data. When submitted, this information <strong>will still be saved</strong> and/or <strong>sent to designated recipients</strong>'); + + /**************************************************************************/ + // Total unique. (node) + /**************************************************************************/ + + // Create webform node. + $node_total_unique = $this->createWebformNode('test_form_limit_total_unique'); + + $this->drupalLogin($admin_user); + + // Check that name is empty for new submission for admin user. + $this->drupalGet('/node/' . $node_total_unique->id()); + $this->assertFieldByName('name', ''); + + // Check that name is set to 'John Lennon' and 'Submission information' is + // visible for admin user. + $sid = $this->postNodeSubmission($node_total_unique, ['name' => 'John Lennon']); + $this->drupalGet('/webform/test_form_limit_total_unique'); + $this->assertFieldByName('name', 'John Lennon'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that 'Test' form also has name set to 'John Lennon' + // and does not display a message. + $this->drupalGet('/node/' . $node_total_unique->id() . '/webform/test'); + $this->assertFieldByName('name', 'John Lennon'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that 'Test' form also has name set to 'John Lennon' + // and does not display a message for root user. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/node/' . $node_total_unique->id() . '/webform/test'); + $this->assertFieldByName('name', 'John Lennon'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + /**************************************************************************/ + // User unique. (webform) + /**************************************************************************/ + + // Check that name is empty for new submission for admin user. + $this->drupalLogin($admin_user); + $this->drupalGet('/webform/test_form_limit_user_unique'); + $this->assertFieldByName('name', ''); + + // Check that 'Test' form is available and display a message. + $this->drupalGet('/webform/test_form_limit_user_unique/test'); + $this->assertRaw(' The below webform has been prepopulated with custom/random test data. When submitted, this information <strong>will still be saved</strong> and/or <strong>sent to designated recipients</strong>'); + + // Check that name is empty for new submission for root user. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/webform/test_form_limit_user_unique'); + $this->assertFieldByName('name', ''); + + // Check that name is set to 'John Smith' and 'Submission information' is + // visible for admin user. + $this->drupalLogin($admin_user); + $sid = $this->postSubmission($webform_user_unique, ['name' => 'John Smith']); + $this->drupalGet('/webform/test_form_limit_user_unique'); + $this->assertFieldByName('name', 'John Smith'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that 'Test' form also has name set to 'John Smith' + // and does not display a message. + $this->drupalGet('/webform/test_form_limit_user_unique/test'); + $this->assertFieldByName('name', 'John Smith'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + /**************************************************************************/ + + // Check that name is still empty for new submission for root user. + $this->drupalLogin($this->rootUser); + $this->drupalGet('/webform/test_form_limit_user_unique'); + $this->assertFieldByName('name', ''); + + // Check that name is set to 'John Smith' and 'Submission information' is + // visible for root user. + $sid = $this->postSubmission($webform_user_unique, ['name' => 'Jane Doe']); + $this->drupalGet('/webform/test_form_limit_user_unique'); + $this->assertFieldByName('name', 'Jane Doe'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + // Check that 'Test' form also has name set to 'Jane Doe' + // and does not display a message. + $this->drupalGet('/webform/test_form_limit_user_unique/test'); + $this->assertFieldByName('name', 'Jane Doe'); + $this->assertRaw("<div><b>Submission ID:</b> $sid</div>"); + + } + +} diff --git a/web/modules/webform/src/Tests/WebformSubmissionFormLimitsTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsLimitsTest.php similarity index 85% rename from web/modules/webform/src/Tests/WebformSubmissionFormLimitsTest.php rename to web/modules/webform/src/Tests/Settings/WebformSettingsLimitsTest.php index 740df6bf69..f7326162a4 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionFormLimitsTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsLimitsTest.php @@ -1,16 +1,17 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Settings; use Drupal\user\Entity\Role; use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission form limits. * * @group Webform */ -class WebformSubmissionFormLimitsTest extends WebformTestBase { +class WebformSettingsLimitsTest extends WebformTestBase { /** * Modules to enable. @@ -32,9 +33,6 @@ class WebformSubmissionFormLimitsTest extends WebformTestBase { public function setUp() { parent::setUp(); - // Create users. - $this->createUsers(); - // Place webform test blocks. $this->placeWebformBlocks('webform_test_block_submission_limit'); } @@ -43,9 +41,18 @@ public function setUp() { * Tests webform submission form limits. */ public function testFormLimits() { + $own_submission_user = $this->drupalCreateUser([ + 'view own webform submission', + 'edit own webform submission', + 'delete own webform submission', + 'access webform submission user', + ]); + $webform_limit = Webform::load('test_form_limit'); - $this->drupalGet('webform/test_form_limit'); + /**************************************************************************/ + + $this->drupalGet('/webform/test_form_limit'); // Check webform available. $this->assertFieldByName('op', 'Submit'); @@ -56,11 +63,11 @@ public function testFormLimits() { $this->assertRaw('0 webform submission(s)'); $this->assertRaw('4 webform limit (every minute)'); - $this->drupalLogin($this->ownWebformSubmissionUser); + $this->drupalLogin($own_submission_user); // Check that draft does not count toward limit. $this->postSubmission($webform_limit, [], t('Save Draft')); - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertFieldByName('op', 'Submit'); $this->assertRaw('A partially-completed form was found. Please complete the remaining portions.'); $this->assertNoRaw('You are only allowed to have 1 submission for this webform.'); @@ -71,7 +78,7 @@ public function testFormLimits() { // Check limit reached and webform not available for authenticated user. $sid = $this->postSubmission($webform_limit); - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertNoFieldByName('op', 'Submit'); $this->assertRaw('You are only allowed to have 1 submission for this webform.'); @@ -105,7 +112,7 @@ public function testFormLimits() { $role->save(); // Check webform is still available for anonymous users. - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertFieldByName('op', 'Submit'); $this->assertNoRaw('You are only allowed to have 1 submission for this webform.'); @@ -118,7 +125,7 @@ public function testFormLimits() { $this->assertRaw('3 webform submission(s)'); // Check limit reached and webform not available for anonymous user. - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertNoFieldByName('op', 'Submit'); $this->assertRaw('You are only allowed to have 1 submission for this webform.'); @@ -134,7 +141,7 @@ public function testFormLimits() { $this->drupalLogout(); // Check total limit. - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertNoFieldByName('op', 'Submit'); $this->assertRaw('Only 4 submissions are allowed.'); $this->assertNoRaw('You are only allowed to have 1 submission for this webform.'); @@ -145,7 +152,7 @@ public function testFormLimits() { // Check admin can still post submissions. $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertFieldByName('op', 'Submit'); $this->assertRaw('Only 4 submissions are allowed.'); $this->assertRaw('Only submission administrators are allowed to access this webform and create new submissions.'); @@ -159,7 +166,7 @@ public function testFormLimits() { // Check submission limit blocks are removed because the submission // intervals have passed. - $this->drupalGet('webform/test_form_limit'); + $this->drupalGet('/webform/test_form_limit'); $this->assertRaw('0 user submission(s)'); $this->assertRaw('0 webform submission(s)'); } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsLoginTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsLoginTest.php deleted file mode 100644 index 54c14f2748..0000000000 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsLoginTest.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php - -namespace Drupal\webform\Tests\Settings; - -use Drupal\webform\Entity\Webform; -use Drupal\webform\Tests\WebformTestBase; - -/** - * Tests for login redirect webform and submissions. - * - * @group Webform - */ -class WebformSettingsLoginTest extends WebformTestBase { - - /** - * Webforms to load. - * - * @var array - */ - protected static $testWebforms = ['test_form_login']; - - /** - * Tests webform login setting. - */ - public function testLogin() { - // Create a webform submission. - $this->drupalLogin($this->rootUser); - $webform = Webform::load('test_form_login'); - $sid = $this->postSubmission($webform); - $this->drupalLogout(); - - // Check form message is displayed. - $this->drupalGet('admin/structure/webform/manage/test_form_login'); - $this->assertRaw('Please login to access <b>Test: Webform: Login</b>.'); - - // Check submission message is displayed. - $this->drupalGet("admin/structure/webform/manage/test_form_login/submission/$sid"); - $this->assertRaw("Please login to access <b>Test: Webform: Login: Submission #$sid</b>."); - - // Disable login message. - $webform = Webform::load('test_form_login'); - $webform->setSetting('form_login', FALSE); - $webform->setSetting('submission_login', FALSE); - $webform->save(); - - // Check submission message is not displayed. - $this->drupalGet('admin/structure/webform/manage/test_form_login'); - $this->assertNoRaw('Please login to access <b>Test: Webform: Login</b>.'); - - // Check form message is not displayed. - $this->drupalGet("admin/structure/webform/manage/test_form_login/submission/$sid"); - $this->assertNoRaw("Please login to access <b>Test: Webform: Login: Submission #$sid</b>."); - } - -} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsPathTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsPathTest.php index 67cd4653b6..787c509df3 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsPathTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsPathTest.php @@ -14,12 +14,17 @@ */ class WebformSettingsPathTest extends WebformTestBase { - public static $modules = ['path', 'webform']; + public static $modules = ['path', 'webform', 'node']; /** * Tests YAML page and title. */ public function testPaths() { + /** @var \Drupal\Core\Path\AliasStorageInterface $alias_storage */ + $alias_storage = $this->container->get('path.alias_storage'); + + $node = $this->drupalCreateNode(); + $webform = Webform::create([ 'langcode' => 'en', 'status' => WebformInterface::STATUS_OPEN, @@ -29,56 +34,95 @@ public function testPaths() { 'test' => ['#markup' => 'test'], ]), ]); + $webform->setSetting('draft', WebformInterface::DRAFT_ALL); $webform->save(); + $webform_path = '/webform/' . $webform->id(); + $form_path = '/form/' . str_replace('_', '-', $webform->id()); + + // Check paths. + $this->drupalLogin($this->rootUser); + + // Check that aliases exist. + $this->assert(is_array($alias_storage->load(['alias' => $form_path]))); + $this->assert(is_array($alias_storage->load(['alias' => "$form_path/confirmation"]))); + $this->assert(is_array($alias_storage->load(['alias' => "$form_path/drafts"]))); + $this->assert(is_array($alias_storage->load(['alias' => "$form_path/submissions"]))); // Check default system submit path. - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet($webform_path); $this->assertResponse(200, 'Submit system path exists'); // Check default alias submit path. - $this->drupalGet('form/' . str_replace('_', '-', $webform->id())); + $this->drupalGet($form_path); $this->assertResponse(200, 'Submit URL alias exists'); // Check default alias confirm path. - $this->drupalGet('form/' . str_replace('_', '-', $webform->id()) . '/confirmation'); + $this->drupalGet("$form_path/confirmation"); $this->assertResponse(200, 'Confirm URL alias exists'); - // Check page hidden (i.e. access denied). + // Check default alias drafts path. + $this->drupalGet("$form_path/drafts"); + $this->assertResponse(200, 'Drafts URL alias exists'); + + // Check default alias submissions path. + $this->drupalGet("$form_path/submissions"); + $this->assertResponse(200, 'Submissions URL alias exists'); + + $this->drupalLogout(); + + // Disable paths for the webform. $webform->setSettings(['page' => FALSE])->save(); - $this->drupalGet('webform/' . $webform->id()); + + // Check that aliases do not exist. + $this->assertFalse($alias_storage->load(['alias' => $form_path])); + $this->assertFalse($alias_storage->load(['alias' => "$form_path/confirmation"])); + $this->assertFalse($alias_storage->load(['alias' => "$form_path/drafts"])); + $this->assertFalse($alias_storage->load(['alias' => "$form_path/submissions"])); + + // Check page hidden (i.e. access denied). + $this->drupalGet($webform_path); $this->assertResponse(403, 'Submit system path access denied'); $this->assertNoRaw('Only webform administrators are allowed to access this page and create new submissions.'); - $this->drupalGet('form/' . str_replace('_', '-', $webform->id())); + $this->drupalGet($form_path); $this->assertResponse(404, 'Submit URL alias does not exist'); + // Check page hidden with source entity. + $this->drupalGet($webform_path, ['query' => ['source_entity_type' => 'node', 'source_entity_id' => $node->id()]]); + $this->assertResponse(403, 'Submit system path access denied'); + + // Check page visible with source entity. + $webform->setSettings(['form_prepopulate_source_entity' => TRUE])->save(); + $this->drupalGet($webform_path, ['query' => ['source_entity_type' => 'node', 'source_entity_id' => $node->id()]]); + $this->assertResponse(200, 'Submit system path exists'); + // Check hidden page visible to admin. $this->drupalLogin($this->rootUser); - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet($webform_path); $this->assertResponse(200, 'Submit system path access permitted'); $this->assertRaw('Only webform administrators are allowed to access this page and create new submissions.'); $this->drupalLogout(); // Check custom submit and confirm path. $webform->setSettings(['page' => TRUE, 'page_submit_path' => 'page_submit_path', 'page_confirm_path' => 'page_confirm_path'])->save(); - $this->drupalGet('page_submit_path'); + $this->drupalGet('/page_submit_path'); $this->assertResponse(200, 'Submit system path access permitted'); - $this->drupalGet('page_confirm_path'); + $this->drupalGet('/page_confirm_path'); $this->assertResponse(200, 'Submit URL alias access permitted'); // Check custom base path. $webform->setSettings(['page_submit_path' => '', 'page_confirm_path' => ''])->save(); $this->drupalLogin($this->rootUser); $this->drupalPostForm('admin/structure/webform/config', ['page_settings[default_page_base_path]' => 'base/path'], t('Save configuration')); - $this->drupalGet('base/path/' . str_replace('_', '-', $webform->id())); + $this->drupalGet('/base/path/' . str_replace('_', '-', $webform->id())); $this->assertResponse(200, 'Submit URL alias with custom base path exists'); - $this->drupalGet('base/path/' . str_replace('_', '-', $webform->id()) . '/confirmation'); + $this->drupalGet('/base/path/' . str_replace('_', '-', $webform->id()) . '/confirmation'); $this->assertResponse(200, 'Confirm URL alias with custom base path exists'); // Check custom base path delete if accessing webform as page is disabled. $webform->setSettings(['page' => FALSE])->save(); - $this->drupalGet('base/path/' . str_replace('_', '-', $webform->id())); + $this->drupalGet('/base/path/' . str_replace('_', '-', $webform->id())); $this->assertResponse(404, 'Submit URL alias does not exist.'); - $this->drupalGet('base/path/' . str_replace('_', '-', $webform->id()) . '/confirmation'); + $this->drupalGet('/base/path/' . str_replace('_', '-', $webform->id()) . '/confirmation'); $this->assertResponse(404, 'Confirm URL alias does not exist.'); // Disable automatic generation of paths. @@ -96,13 +140,15 @@ public function testPaths() { ]), ]); $webform->save(); + $webform_path = '/webform/' . $webform->id(); + $form_path = '/form/' . str_replace('_', '-', $webform->id()); // Check default system submit path. - $this->drupalGet('webform/' . $webform->id()); + $this->drupalGet($webform_path); $this->assertResponse(200, 'Submit system path exists'); // Check no default alias submit path. - $this->drupalGet('form/' . str_replace('_', '-', $webform->id())); + $this->drupalGet($form_path); $this->assertResponse(404, 'Submit URL alias does not exist'); } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsPrepopulateTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsPrepopulateTest.php index ee9eeec732..abb3c9ed6d 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsPrepopulateTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsPrepopulateTest.php @@ -32,13 +32,13 @@ public function testPrepopulate() { $webform_prepopulate = Webform::load('test_form_prepopulate'); // Check prepopulation of an element. - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['name' => 'John', 'colors' => ['red', 'white']]]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['name' => 'John', 'colors' => ['red', 'white']]]); $this->assertFieldByName('name', 'John'); $this->assertFieldChecked('edit-colors-red'); $this->assertFieldChecked('edit-colors-white'); $this->assertNoFieldChecked('edit-colors-blue'); - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['name' => 'John', 'colors' => 'red']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['name' => 'John', 'colors' => 'red']]); $this->assertFieldByName('name', 'John'); $this->assertFieldChecked('edit-colors-red'); $this->assertNoFieldChecked('edit-colors-white'); @@ -47,7 +47,7 @@ public function testPrepopulate() { // Check disabling prepopulation of an element. $webform_prepopulate->setSetting('form_prepopulate', FALSE); $webform_prepopulate->save(); - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['name' => 'John']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['name' => 'John']]); $this->assertFieldByName('name', ''); /**************************************************************************/ @@ -79,25 +79,32 @@ public function testPrepopulate() { // Check required prepopulated source entity displays error when no source // entity is defined. - $this->drupalGet('webform/test_form_prepopulate'); + $this->drupalGet('/webform/test_form_prepopulate'); $this->assertRaw('This webform is not available. Please contact the site administrator.'); // Check required prepopulated source entity displays error when invalid // source entity is defined. - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'DOES_NOT_EXIST']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'DOES_NOT_EXIST']]); $this->assertRaw('This webform is not available. Please contact the site administrator.'); // Check required prepopulated source entity loads when source entity is // valid. - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); $this->assertNoRaw('This webform is not available. Please contact the site administrator.'); + // Check that required prepopulated source entity can be updated (edit). + $this->drupalLogin($this->rootUser); + $sid = $this->postSubmission($webform_prepopulate, [], t('Submit'), ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); + $this->drupalGet("/admin/structure/webform/manage/test_form_prepopulate/submission/$sid/edit"); + $this->assertNoRaw('This webform is not available. Please contact the site administrator.'); + $this->drupalLogout(); + // Set prepopulated source entity type to user. $webform_prepopulate->setSetting('form_prepopulate_source_entity_type', 'user'); $webform_prepopulate->save(); // Check invalid source entity type displays error. - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); $this->assertRaw('This webform is not available. Please contact the site administrator.'); // Set prepopulated source entity type to webform. @@ -105,7 +112,7 @@ public function testPrepopulate() { $webform_prepopulate->save(); // Check invalid source entity type displays error. - $this->drupalGet('webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); + $this->drupalGet('/webform/test_form_prepopulate', ['query' => ['source_entity_type' => 'webform', 'source_entity_id' => 'contact']]); $this->assertNoRaw('This webform is not available. Please contact the site administrator.'); } diff --git a/web/modules/webform/src/Tests/WebformSubmissionFormPreviewTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsPreviewTest.php similarity index 54% rename from web/modules/webform/src/Tests/WebformSubmissionFormPreviewTest.php rename to web/modules/webform/src/Tests/Settings/WebformSettingsPreviewTest.php index 7248837344..9ac5fbfb56 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionFormPreviewTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsPreviewTest.php @@ -1,15 +1,16 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\Settings; use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission form preview. * * @group Webform */ -class WebformSubmissionFormPreviewTest extends WebformTestBase { +class WebformSettingsPreviewTest extends WebformTestBase { /** * Webforms to load. @@ -40,24 +41,55 @@ public function testPreview() { $webform_preview = Webform::load('test_form_preview'); // Check webform with optional preview. - $this->drupalGet('webform/test_form_preview'); + $this->drupalGet('/webform/test_form_preview'); $this->assertFieldByName('op', 'Submit'); $this->assertFieldByName('op', 'Preview'); - // Check default preview. - $this->drupalPostForm('webform/test_form_preview', ['name' => 'test', 'email' => 'example@example.com'], t('Preview')); + // Check default preview with values. + $this->drupalPostForm('webform/test_form_preview', ['name' => 'test', 'email' => 'example@example.com', 'checkbox' => TRUE], t('Preview')); $this->assertRaw('<h1 class="page-title">Test: Webform: Preview: Preview</h1>'); + $this->assertRaw('<b>Preview</b></li>'); + $this->assertRaw('Please review your submission. Your submission is not complete until you press the "Submit" button!'); + $this->assertFieldByName('op', 'Submit'); $this->assertFieldByName('op', '< Previous'); - $this->assertRaw('<div id="test_form_preview--name" class="webform-element webform-element-type-textfield js-form-item form-item js-form-type-item form-type-item js-form-item-name form-item-name">'); + + $this->assertRaw('<div class="webform-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-preview" id="edit-preview"><fieldset class="format-attributes-class webform-container webform-container-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" id="test_form_preview--fieldset">'); + $this->assertRaw('<div class="format-attributes-class webform-element webform-element-type-textfield js-form-item form-item js-form-type-item form-type-item js-form-item-name form-item-name" id="test_form_preview--name">'); $this->assertRaw('<label>Name</label>' . PHP_EOL . ' test'); - $this->assertRaw('<div id="test_form_preview--email" class="webform-element webform-element-type-email js-form-item form-item js-form-type-item form-type-item js-form-item-email form-item-email">'); + + $this->assertRaw('<section class="format-attributes-class js-form-item form-item js-form-wrapper form-wrapper webform-section" id="test_form_preview--container">'); + $this->assertRaw('<div class="format-attributes-class webform-element webform-element-type-email js-form-item form-item js-form-type-item form-type-item js-form-item-email form-item-email" id="test_form_preview--email">'); $this->assertRaw('<label>Email</label>' . PHP_EOL . ' <a href="mailto:example@example.com">example@example.com</a>'); + + $this->assertRaw('<div class="format-attributes-class webform-element webform-element-type-checkbox js-form-item form-item js-form-type-item form-type-item js-form-item-checkbox form-item-checkbox" id="test_form_preview--checkbox">'); + $this->assertRaw('<section class="format-attributes-class js-form-item form-item js-form-wrapper form-wrapper webform-section" id="test_form_preview--section">'); + $this->assertRaw('<label>Checkbox</label>' . PHP_EOL . ' Yes'); $this->assertRaw('<div class="webform-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-preview" id="edit-preview">'); + // Check default preview without values. + $this->drupalPostForm('webform/test_form_preview', [], t('Preview')); + $this->assertNoRaw('<label>Name</label>'); + $this->assertNoRaw('<label>Email</label>'); + $this->assertNoRaw('<label>Checkbox</label>'); + + // Check submission view without values. + $sid = $this->postSubmission($webform_preview); + $this->drupalGet("admin/structure/webform/manage/test_form_preview/submission/$sid"); + $this->assertNoRaw('<label>Name</label>'); + $this->assertNoRaw('<label>Email</label>'); + $this->assertNoRaw('<label>Checkbox</label>'); + + // Check submission table without values. + $this->drupalGet("admin/structure/webform/manage/test_form_preview/submission/$sid/table"); + $this->assertNoRaw('<th>Name</th>'); + $this->assertNoRaw('<th>Email</th>'); + $this->assertNoRaw('<th>Checkbox</th>'); + $this->assertNoRaw('<td>No</td>'); + // Clear default preview message. \Drupal::configFactory()->getEditable('webform.settings') ->set('settings.default_preview_message', '') @@ -67,22 +99,40 @@ public function testPreview() { $this->drupalPostForm('webform/test_form_preview', ['name' => 'test', 'email' => 'example@example.com'], t('Preview')); $this->assertNoRaw('Please review your submission. Your submission is not complete until you press the "Submit" button!'); - // Set preview to include empty. + // Set preview and submission to include empty. $webform_preview->setSetting('preview_exclude_empty', FALSE); + $webform_preview->setSetting('preview_exclude_empty_checkbox', FALSE); + $webform_preview->setSetting('submission_exclude_empty', FALSE); + $webform_preview->setSetting('submission_exclude_empty_checkbox', FALSE); $webform_preview->save(); - // Check empty element is included from preview. - $this->drupalPostForm('webform/test_form_preview', ['name' => '', 'email' => ''], t('Preview')); + // Check empty elements are included in preview. + $this->drupalPostForm('webform/test_form_preview', ['name' => '', 'email' => '', 'checkbox' => FALSE], t('Preview')); $this->assertRaw('<label>Name</label>' . PHP_EOL . ' {Empty}'); - $this->assertRaw('<div id="test_form_preview--email" class="webform-element webform-element-type-email js-form-item form-item js-form-type-item form-type-item js-form-item-email form-item-email">'); + $this->assertRaw('<div class="format-attributes-class webform-element webform-element-type-email js-form-item form-item js-form-type-item form-type-item js-form-item-email form-item-email" id="test_form_preview--email">'); $this->assertRaw('<label>Email</label>' . PHP_EOL . ' {Empty}'); + $this->assertRaw('<label>Checkbox</label>' . PHP_EOL . ' No'); + + // Check empty elements are included in submission view. + $sid = $this->postSubmission($webform_preview); + $this->drupalGet("admin/structure/webform/manage/test_form_preview/submission/$sid"); + $this->assertRaw('<label>Name</label>'); + $this->assertRaw('<label>Email</label>'); + $this->assertRaw('<label>Checkbox</label>'); + + // Check submission table without values. + $this->drupalGet("admin/structure/webform/manage/test_form_preview/submission/$sid/table"); + $this->assertRaw('<th>Name</th>'); + $this->assertRaw('<th>Email</th>'); + $this->assertRaw('<th>Checkbox</th>'); + $this->assertRaw('<td>No</td>'); // Add special character to title. $webform_preview->set('title', "This has special characters. '<>\"&"); $webform_preview->save(); // Check special characters in form page title. - $this->drupalGet('webform/test_form_preview'); + $this->drupalGet('/webform/test_form_preview'); $this->assertRaw('<title>This has special characters. \'"& | Drupal</title>'); $this->assertRaw('<h1 class="page-title">This has special characters. '<>"&</h1>'); @@ -120,7 +170,7 @@ public function testPreview() { $this->assertNoRaw('<label>Email</label>'); $this->assertRaw('<div class="preview-custom webform-preview js-form-wrapper form-wrapper" data-drupal-selector="edit-preview" id="edit-preview">'); - $this->drupalGet('webform/test_form_preview'); + $this->drupalGet('/webform/test_form_preview'); $this->assertNoFieldByName('op', 'Submit'); $this->assertFieldByName('op', '{Preview}'); @@ -128,7 +178,6 @@ public function testPreview() { $this->drupalPostForm('webform/test_form_preview', ['name' => 'test', 'email' => ''], t('{Preview}')); $this->assertRaw('<label>Name</label>' . PHP_EOL . ' test'); $this->assertNoRaw('<label>Email</label>'); - } } diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsPreviousTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsPreviousTest.php new file mode 100644 index 0000000000..800ad06ca4 --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsPreviousTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform submission form previous. + * + * @group Webform + */ +class WebformSettingsPreviousTest extends WebformTestBase { + + /** + * Test webform submission form previous submission(s). + */ + public function testPrevious() { + global $base_path; + + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('contact'); + + /**************************************************************************/ + // Previous submission message. + /**************************************************************************/ + + // Create single submission. + $sid_1 = $this->postSubmissionTest($webform); + + // Check default global previous submission message. + $this->drupalGet('/webform/contact'); + $this->assertRaw("You have already submitted this webform. <a href=\"{$base_path}webform/contact/submissions/{$sid_1}\">View your previous submission</a>."); + + // Check custom global previous submission message. + $this->config('webform.settings') + ->set('settings.default_previous_submission_message', '{default_previous_submission}') + ->save(); + $this->drupalGet('/webform/contact'); + $this->assertRaw('{default_previous_submission}'); + + // Check custom webform previous submission message. + $webform + ->setSetting('previous_submission_message', '{custom_previous_submission}') + ->save(); + $this->drupalGet('/webform/contact'); + $this->assertRaw('{custom_previous_submission}'); + + /**************************************************************************/ + // Previous submissions message. + /**************************************************************************/ + + // Create second submission. + $sid_2 = $this->postSubmissionTest($webform); + + // Check default global previous submissions message. + $this->drupalGet('/webform/contact'); + $this->assertRaw("You have already submitted this webform. <a href=\"{$base_path}webform/contact/submissions\">View your previous submissions</a>."); + + // Check custom global previous submissions message. + $this->config('webform.settings') + ->set('settings.default_previous_submissions_message', '{default_previous_submissions}') + ->save(); + $this->drupalGet('/webform/contact'); + $this->assertRaw('{default_previous_submissions}'); + + // Check custom webform previous submissions message. + $webform + ->setSetting('previous_submissions_message', '{custom_previous_submissions}') + ->save(); + $this->drupalGet('/webform/contact'); + $this->assertRaw('{custom_previous_submissions}'); + } + +} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsRemoteAddrTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsRemoteAddrTest.php new file mode 100644 index 0000000000..81c70d495b --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsRemoteAddrTest.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for disable tracking of remote IP address. + * + * @group Webform + */ +class WebformSettingsRemoteAddrTest extends WebformTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_form_remote_addr']; + + /** + * Tests webform disable remote IP address. + */ + public function testRemoteAddr() { + $this->drupalLogin($this->rootUser); + + $webform = Webform::load('test_form_remote_addr'); + $sid = $this->postSubmission($webform, ['name' => 'John']); + $webform_submission = WebformSubmission::load($sid); + $this->assertEqual($webform_submission->getRemoteAddr(), t('(unknown)')); + $this->assertEqual($webform_submission->getOwnerId(), 1); + } + +} diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsScheduleTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsScheduleTest.php index 03fb814aa3..03cc76dfb9 100644 --- a/web/modules/webform/src/Tests/Settings/WebformSettingsScheduleTest.php +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsScheduleTest.php @@ -38,21 +38,21 @@ public function testSchedule() { // Check webform open message is displayed. $this->assertTrue($webform_opening->isClosed()); $this->assertTrue($webform_opening->isOpening()); - $this->drupalGet('webform/test_form_opening'); + $this->drupalGet('/webform/test_form_opening'); $this->assertNoRaw('This message should not be displayed)'); $this->assertRaw('This form is opening soon.'); // Check webform closed message is displayed. $webform_opening->setSetting('form_open_message', ''); $webform_opening->save(); - $this->drupalGet('webform/test_form_opening'); + $this->drupalGet('/webform/test_form_opening'); $this->assertNoRaw('This form is opening soon.'); $this->assertRaw('This form has not yet been opened to submissions.'); $this->drupalLogin($this->rootUser); // Check webform is not closed for admins and warning is displayed. - $this->drupalGet('webform/test_form_opening'); + $this->drupalGet('/webform/test_form_opening'); $this->assertRaw('This message should not be displayed'); $this->assertNoRaw('This form has not yet been opened to submissions.'); $this->assertRaw('Only submission administrators are allowed to access this webform and create new submissions.'); @@ -62,7 +62,7 @@ public function testSchedule() { $webform_opening->save(); $this->assertFalse($webform_opening->isClosed()); $this->assertTrue($webform_opening->isOpen()); - $this->drupalGet('webform/test_form_opening'); + $this->drupalGet('/webform/test_form_opening'); $this->assertRaw('This message should not be displayed'); $this->assertNoRaw('This form has not yet been opened to submissions.'); $this->assertNoRaw('Only submission administrators are allowed to access this webform and create new submissions.'); @@ -78,21 +78,21 @@ public function testSchedule() { // Check webform closed message is displayed. $this->assertTrue($webform_closed->isClosed()); $this->assertFalse($webform_closed->isOpen()); - $this->drupalGet('webform/test_form_closed'); + $this->drupalGet('/webform/test_form_closed'); $this->assertNoRaw('This message should not be displayed)'); $this->assertRaw('This form is closed.'); // Check webform closed message is displayed. $webform_closed->setSetting('form_close_message', ''); $webform_closed->save(); - $this->drupalGet('webform/test_form_closed'); + $this->drupalGet('/webform/test_form_closed'); $this->assertNoRaw('This form is closed.'); - $this->assertRaw('Sorry...This form is closed to new submissions.'); + $this->assertRaw('Sorry…This form is closed to new submissions.'); $this->drupalLogin($this->rootUser); // Check webform is not closed for admins and warning is displayed. - $this->drupalGet('webform/test_form_closed'); + $this->drupalGet('/webform/test_form_closed'); $this->assertRaw('This message should not be displayed'); $this->assertNoRaw('This form is closed.'); $this->assertRaw('Only submission administrators are allowed to access this webform and create new submissions.'); @@ -102,7 +102,7 @@ public function testSchedule() { $webform_closed->save(); $this->assertFalse($webform_closed->isClosed()); $this->assertTrue($webform_closed->isOpen()); - $this->drupalGet('webform/test_form_closed'); + $this->drupalGet('/webform/test_form_closed'); $this->assertRaw('This message should not be displayed'); $this->assertNoRaw('This form is closed.'); $this->assertNoRaw('Only submission administrators are allowed to access this webform and create new submissions.'); diff --git a/web/modules/webform/src/Tests/Settings/WebformSettingsStatusTest.php b/web/modules/webform/src/Tests/Settings/WebformSettingsStatusTest.php new file mode 100644 index 0000000000..c5a6f9642b --- /dev/null +++ b/web/modules/webform/src/Tests/Settings/WebformSettingsStatusTest.php @@ -0,0 +1,47 @@ +<?php + +namespace Drupal\webform\Tests\Settings; + +use Drupal\webform\Tests\WebformTestBase; +use Drupal\webform\WebformInterface; + +/** + * Tests for webform default status. + * + * @group Webform + */ +class WebformSettingsStatusTest extends WebformTestBase { + + /** + * Tests default status. + */ + public function testStatus() { + $this->drupalLogin($this->rootUser); + + // Check add form status = open. + $this->drupalGet('/admin/structure/webform/add'); + $this->assertFieldChecked('edit-status-open'); + $this->assertNoFieldChecked('edit-status-closed'); + + // Check duplicate form status = open. + $this->drupalGet('/admin/structure/webform/manage/contact/duplicate'); + $this->assertFieldChecked('edit-status-open'); + $this->assertNoFieldChecked('edit-status-closed'); + + // Set default status to closed. + $this->config('webform.settings') + ->set('settings.default_status', WebformInterface::STATUS_CLOSED) + ->save(); + + // Check add form status = closed. + $this->drupalGet('/admin/structure/webform/add'); + $this->assertNoFieldChecked('edit-status-open'); + $this->assertFieldChecked('edit-status-closed'); + + // Check duplicate form status = closed. + $this->drupalGet('/admin/structure/webform/manage/contact/duplicate'); + $this->assertNoFieldChecked('edit-status-open'); + $this->assertFieldChecked('edit-status-closed'); + } + +} diff --git a/web/modules/webform/src/Tests/States/WebformStatesPreviewTest.php b/web/modules/webform/src/Tests/States/WebformStatesPreviewTest.php new file mode 100644 index 0000000000..b0c69904a5 --- /dev/null +++ b/web/modules/webform/src/Tests/States/WebformStatesPreviewTest.php @@ -0,0 +1,95 @@ +<?php + +namespace Drupal\webform\Tests\States; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform states preview. + * + * @group Webform + */ +class WebformStatesPreviewTest extends WebformTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = [ + 'test_states_server_preview', + 'test_states_server_save', + 'test_states_server_clear', + ]; + + /** + * Tests visible conditions (#states) validator for elements . + */ + public function testStatesValidatorElementVisible() { + $webform_preview = Webform::load('test_states_server_preview'); + + // Check trigger unchecked and elements are conditionally hidden. + $this->postSubmission($webform_preview, [], t('Preview')); + $this->assertRaw('trigger_checkbox'); + $this->assertNoRaw('dependent_checkbox'); + $this->assertNoRaw('dependent_markup'); + $this->assertNoRaw('dependent_message'); + $this->assertNoRaw('dependent_fieldset'); + $this->assertNoRaw('nested_textfield'); + + // Check trigger checked and elements are conditionally visible. + $this->postSubmission($webform_preview, ['trigger_checkbox' => TRUE], t('Preview')); + $this->assertRaw('trigger_checkbox'); + $this->assertRaw('dependent_checkbox'); + $this->assertRaw('dependent_markup'); + $this->assertRaw('dependent_message'); + $this->assertRaw('dependent_fieldset'); + $this->assertRaw('nested_textfield'); + + $webform_save = Webform::load('test_states_server_save'); + + // Check trigger unchecked and saved. + $this->postSubmission($webform_save, ['trigger_checkbox' => FALSE], t('Submit')); + $this->assertRaw("trigger_checkbox: 0 +dependent_hidden: '' +dependent_checkbox: '' +dependent_value: '' +dependent_textfield: '' +dependent_textfield_multiple: { } +dependent_details_textfield: ''"); + + // Check trigger checked and saved. + $this->postSubmission($webform_save, ['trigger_checkbox' => TRUE], t('Submit')); + $this->assertRaw("trigger_checkbox: 1 +dependent_hidden: '{dependent_hidden}' +dependent_checkbox: 0 +dependent_value: '{value}' +dependent_textfield: '{dependent_textfield}' +dependent_textfield_multiple: + - '{dependent_textfield}' +dependent_details_textfield: '{dependent_details_textfield}'"); + + $webform_clear = Webform::load('test_states_server_clear'); + + // Check trigger unchecked and not cleared. + $this->postSubmission($webform_clear, ['trigger_checkbox' => FALSE], t('Submit')); + $this->assertRaw("trigger_checkbox: 0 +dependent_hidden: '{dependent_hidden}' +dependent_checkbox: 1 +dependent_radios: One +dependent_value: '{value}' +dependent_textfield: '{dependent_textfield}' +dependent_textfield_multiple: + - '{dependent_textfield}' +dependent_webform_name: + - title: '' + first: John + middle: '' + last: Smith + suffix: '' + degree: '' +dependent_details_textfield: '{dependent_details_textfield}'"); + } + +} diff --git a/web/modules/webform/src/Tests/WebformSubmissionConditionsValidatorTest.php b/web/modules/webform/src/Tests/States/WebformStatesServerTest.php similarity index 60% rename from web/modules/webform/src/Tests/WebformSubmissionConditionsValidatorTest.php rename to web/modules/webform/src/Tests/States/WebformStatesServerTest.php index b86e0cf367..d6ed19c4e3 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionConditionsValidatorTest.php +++ b/web/modules/webform/src/Tests/States/WebformStatesServerTest.php @@ -1,16 +1,17 @@ <?php -namespace Drupal\webform\Tests; +namespace Drupal\webform\Tests\States; use Drupal\webform\Element\WebformOtherBase; use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; /** * Tests for webform submission conditions (#states) validator. * * @group Webform */ -class WebformSubmissionConditionsValidatorTest extends WebformTestBase { +class WebformStatesServerTest extends WebformTestBase { /** * Webforms to load. @@ -18,13 +19,13 @@ class WebformSubmissionConditionsValidatorTest extends WebformTestBase { * @var array */ protected static $testWebforms = [ - 'test_form_states_server_custom', - 'test_form_states_server_comp', - 'test_form_states_server_multiple', - 'test_form_states_server_nested', - 'test_form_states_server_preview', - 'test_form_states_server_required', - 'test_form_states_server_wizard', + 'test_states_crosspage', + 'test_states_server_custom', + 'test_states_server_comp', + 'test_states_server_nested', + 'test_states_server_multiple', + 'test_states_server_containers', + 'test_states_server_required', ]; /** @@ -50,7 +51,12 @@ public function setUp() { * Tests webform submission conditions (#states) validator required. */ public function testFormStatesValidatorRequired() { - $webform = Webform::load('test_form_states_server_required'); + + /**************************************************************************/ + // required. + /**************************************************************************/ + + $webform = Webform::load('test_states_server_required'); // Check no #states required errors. $this->postSubmission($webform); @@ -107,14 +113,21 @@ public function testFormStatesValidatorRequired() { $this->assertRaw('required_hidden_dependent_required field is required.'); /**************************************************************************/ - // minlength_hidden_trigger + // minlength_hidden_trigger. /**************************************************************************/ $edit = [ 'minlength_hidden_trigger' => TRUE, ]; $this->postSubmission($webform, $edit); - $this->assertRaw('<em class="placeholder">minlength_hidden_dependent</em> cannot be less than <em class="placeholder">1</em> characters but is currently <em class="placeholder">0</em> characters long.'); + $this->assertNoRaw('<em class="placeholder">minlength_hidden_dependent</em> cannot be less than <em class="placeholder">5</em> characters'); + + $edit = [ + 'minlength_hidden_trigger' => TRUE, + 'minlength_hidden_dependent' => 'X', + ]; + $this->postSubmission($webform, $edit); + // $this->assertRaw('<em class="placeholder">minlength_hidden_dependent</em> cannot be less than <em class="placeholder">5</em> characters'); /**************************************************************************/ // checkboxes_trigger. @@ -127,6 +140,25 @@ public function testFormStatesValidatorRequired() { $this->postSubmission($webform, $edit); $this->assertRaw('checkboxes_dependent_required field is required.'); + /**************************************************************************/ + // checkboxes_other_trigger. + /**************************************************************************/ + + // Check required checkboxes other checkbox. + $edit = [ + 'checkboxes_other_trigger[checkboxes][one]' => TRUE, + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw('checkboxes_other_dependent_required field is required.'); + + // Check required checkboxes other text field. + $edit = [ + 'checkboxes_other_trigger[checkboxes][_other_]' => TRUE, + 'checkboxes_other_trigger[other]' => 'filled', + ]; + $this->postSubmission($webform, $edit); + $this->assertRaw('checkboxes_other_dependent_required field is required.'); + /**************************************************************************/ // text_format_trigger. /**************************************************************************/ @@ -300,7 +332,7 @@ public function testFormStatesValidatorRequired() { // custom. /**************************************************************************/ - $webform = Webform::load('test_form_states_server_custom'); + $webform = Webform::load('test_states_server_custom'); // Check no #states required errors. $this->postSubmission($webform); @@ -323,7 +355,7 @@ public function testFormStatesValidatorRequired() { // multiple element. /**************************************************************************/ - $webform = Webform::load('test_form_states_server_multiple'); + $webform = Webform::load('test_states_server_multiple'); $edit = [ 'trigger_required' => TRUE, @@ -337,18 +369,19 @@ public function testFormStatesValidatorRequired() { // composite element. /**************************************************************************/ - $webform = Webform::load('test_form_states_server_comp'); + $webform = Webform::load('test_states_server_comp'); $edit = [ 'webform_name_trigger' => TRUE, 'webform_name_multiple_trigger' => TRUE, 'webform_name_multiple_header_trigger' => TRUE, + 'webform_name_nested_trigger' => TRUE, ]; $this->postSubmission($webform, $edit); // Check basic composite. $this->assertRaw('First field is required.'); - $this->assertRaw('<input data-drupal-selector="edit-webform-name-first" type="text" id="edit-webform-name-first" name="webform_name[first]" value="" size="60" maxlength="255" class="form-text error" aria-invalid="true" data-drupal-states="{"required":{":input[name=\u0022webform_name_trigger\u0022]":{"checked":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-webform-name-first" type="text" id="edit-webform-name-first" name="webform_name[first]" value="" size="60" maxlength="255" class="form-text error" aria-invalid="true" data-drupal-states="{"required":{".webform-submission-test-states-server-comp-add-form :input[name=\u0022webform_name_trigger\u0022]":{"checked":true}}}" />'); // Check multiple composite with custom error. $this->assertRaw("Custom error message for 'last' element."); @@ -356,20 +389,26 @@ public function testFormStatesValidatorRequired() { // Check multiple table composite. $this->assertRaw('Last field is required.'); - $this->assertRaw('<input data-drupal-selector="edit-webform-name-multiple-header-items-0-last" type="text" id="edit-webform-name-multiple-header-items-0-last" name="webform_name_multiple_header[items][0][last]" value="" size="60" maxlength="255" class="form-text error" aria-invalid="true" data-drupal-states="{"required":{":input[name=\u0022webform_name_multiple_header_trigger\u0022]":{"checked":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-webform-name-multiple-header-items-0-last" type="text" id="edit-webform-name-multiple-header-items-0-last" name="webform_name_multiple_header[items][0][last]" value="" size="60" maxlength="255" class="form-text error" aria-invalid="true" data-drupal-states="{"required":{".webform-submission-test-states-server-comp-add-form :input[name=\u0022webform_name_multiple_header_trigger\u0022]":{"checked":true}}}" />'); + + // Check single nested composite. + $this->assertRaw('webform_name_nested_first field is required.'); + $this->assertRaw('webform_name_nested_last field is required.'); + $this->assertRaw(' <input data-drupal-selector="edit-webform-name-nested-last" type="text" id="edit-webform-name-nested-last" name="webform_name_nested[last]" value="" size="60" maxlength="255" class="form-text error" aria-invalid="true" data-drupal-states="{"required":{".webform-submission-test-states-server-comp-add-form :input[name=\u0022webform_name_nested_trigger\u0022]":{"checked":true}}}" />'); /**************************************************************************/ - // nested. + // nested containers. /**************************************************************************/ - $webform = Webform::load('test_form_states_server_nested'); + $webform = Webform::load('test_states_server_containers'); // Check sub elements. - $this->drupalGet('webform/test_form_states_server_nested'); - $this->assertRaw('<input data-drupal-selector="edit-visible-textfield" type="text" id="edit-visible-textfield" name="visible_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{":input[name=\u0022visible_trigger\u0022]":{"checked":true}}}" />'); - $this->assertRaw('<input data-drupal-selector="edit-visible-custom-textfield" type="text" id="edit-visible-custom-textfield" name="visible_custom_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{":input[name=\u0022visible_trigger\u0022]":{"checked":true},":input[name=\u0022visible_textfield\u0022]":{"filled":true}}}" />'); - $this->assertRaw('<input data-drupal-selector="edit-visible-slide-textfield" type="text" id="edit-visible-slide-textfield" name="visible_slide_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{":input[name=\u0022visible_trigger\u0022]":{"checked":true}}}" />'); - $this->assertRaw('<input data-drupal-selector="edit-visible-slide-custom-textfield" type="text" id="edit-visible-slide-custom-textfield" name="visible_slide_custom_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{":input[name=\u0022visible_trigger\u0022]":{"checked":true},":input[name=\u0022visible_slide_textfield\u0022]":{"filled":true}}}" />'); + $this->drupalGet('/webform/test_states_server_containers'); + $this->assertRaw('<input data-drupal-selector="edit-visible-textfield" type="text" id="edit-visible-textfield" name="visible_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_trigger\u0022]":{"checked":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-visible-custom-textfield" type="text" id="edit-visible-custom-textfield" name="visible_custom_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_trigger\u0022]":{"checked":true},".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_textfield\u0022]":{"filled":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-visible-slide-textfield" type="text" id="edit-visible-slide-textfield" name="visible_slide_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_trigger\u0022]":{"checked":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-visible-slide-custom-textfield" type="text" id="edit-visible-slide-custom-textfield" name="visible_slide_custom_textfield" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_trigger\u0022]":{"checked":true},".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_slide_textfield\u0022]":{"filled":true}}}" />'); + $this->assertRaw('<input data-drupal-selector="edit-visible-composite-items-0-textfield" type="text" id="edit-visible-composite-items-0-textfield" name="visible_composite[items][0][textfield]" value="" size="60" maxlength="255" class="form-text" data-drupal-states="{"required":{".webform-submission-test-states-server-containers-add-form :input[name=\u0022visible_trigger\u0022]":{"checked":true}}}" />'); // Check nested element is required. $edit = [ @@ -380,6 +419,8 @@ public function testFormStatesValidatorRequired() { $this->assertNoRaw('visible_custom_textfield field is required.'); $this->assertRaw('visible_slide_textfield field is required.'); $this->assertNoRaw('visible_slide_custom_textfield field is required.'); + $this->assertRaw('textfield field is required.'); + $this->assertRaw('select_other field is required.'); // Check nested element is not required. $edit = []; @@ -388,6 +429,8 @@ public function testFormStatesValidatorRequired() { $this->assertNoRaw('visible_custom_textfield field is required.'); $this->assertNoRaw('visible_slide_textfield field is required.'); $this->assertNoRaw('visible_slide_custom_textfield field is required.'); + $this->assertNoRaw('textfield field is required.'); + $this->assertNoRaw('select_other field is required.'); // Check custom states element validation. $edit = [ @@ -398,127 +441,57 @@ public function testFormStatesValidatorRequired() { $this->postSubmission($webform, $edit); $this->assertRaw('visible_custom_textfield field is required.'); $this->assertRaw('visible_slide_custom_textfield field is required.'); - } - - /** - * Tests webform submission conditions (#states) validator wizard cross-page conditions. - */ - public function testFormStatesValidatorWizard() { - $webform = Webform::load('test_form_states_server_wizard'); /**************************************************************************/ - - // Go to default #states for page 02 with trigger-checkbox unchecked. - $this->postSubmission($webform, [], t('Next Page >')); - - // Check trigger-checkbox value is No. - $this->assertRaw('<input data-drupal-selector="edit-page-01-trigger-checkbox-computed" type="hidden" name="page_01_trigger_checkbox_computed" value="No" />'); - - // Check page_02_textfield_required is not required. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-required" aria-describedby="edit-page-02-textfield-required--description" type="text" id="edit-page-02-textfield-required" name="page_02_textfield_required" value="{default_value}" size="60" maxlength="255" class="form-text" />'); - - // Check page_02_textfield_optional is required. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-optional" aria-describedby="edit-page-02-textfield-optional--description" type="text" id="edit-page-02-textfield-optional" name="page_02_textfield_optional" value="{default_value}" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); - - // Check page_02_textfield_disabled is not disabled. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-disabled" aria-describedby="edit-page-02-textfield-disabled--description" type="text" id="edit-page-02-textfield-disabled" name="page_02_textfield_disabled" value="" size="60" maxlength="255" class="form-text" />'); - - // Check page_02_textfield_enabled is disabled. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-enabled" aria-describedby="edit-page-02-textfield-enabled--description" disabled="disabled" type="text" id="edit-page-02-textfield-enabled" name="page_02_textfield_enabled" value="" size="60" maxlength="255" class="form-text" />'); - - // Check page_02_textfield_visible is not visible. - $this->assertNoFieldByName('page_02_textfield_visible'); - - // Check page_02_textfield_visible_slide is not visible. - $this->assertNoFieldByName('page_02_textfield_visible_slide'); - - // Check page_02_textfield_invisible is visible. - $this->assertFieldByName('page_02_textfield_invisible'); - - // Check page_02_textfield_invisible_slide is visible. - $this->assertFieldByName('page_02_textfield_invisible_slide'); - - // Check page_02_checkbox_checked is not checked. - $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-checked" aria-describedby="edit-page-02-checkbox-checked--description" type="checkbox" id="edit-page-02-checkbox-checked" name="page_02_checkbox_checked" value="1" class="form-checkbox" />'); - - // Check page_02_checkbox_unchecked is checked. - $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-unchecked" aria-describedby="edit-page-02-checkbox-unchecked--description" type="checkbox" id="edit-page-02-checkbox-unchecked" name="page_02_checkbox_unchecked" value="1" checked="checked" class="form-checkbox" />'); - - // Check page_02_details_expanded is not open. - $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_expanded" data-drupal-selector="edit-page-02-details-expanded" aria-describedby="edit-page-02-details-expanded--description" id="edit-page-02-details-expanded" class="js-form-wrapper form-wrapper"> '); - - // Check page_02_details_collapsed is open. - $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_collapsed" data-drupal-selector="edit-page-02-details-collapsed" aria-describedby="edit-page-02-details-collapsed--description" id="edit-page-02-details-collapsed" class="js-form-wrapper form-wrapper" open="open">'); - + // nested conditions. /**************************************************************************/ - // Go to default #states for page 02 with trigger_checkbox checked. - $this->postSubmission($webform, ['page_01_trigger_checkbox' => TRUE], t('Next Page >')); - - // Check trigger-checkbox value is Yes. - $this->assertRaw('<input data-drupal-selector="edit-page-01-trigger-checkbox-computed" type="hidden" name="page_01_trigger_checkbox_computed" value="Yes" />'); - - // Check page_02_textfield_required is required. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-required" aria-describedby="edit-page-02-textfield-required--description" type="text" id="edit-page-02-textfield-required" name="page_02_textfield_required" value="{default_value}" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); - - // Check page_02_textfield_optional is not required. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-optional" aria-describedby="edit-page-02-textfield-optional--description" type="text" id="edit-page-02-textfield-optional" name="page_02_textfield_optional" value="{default_value}" size="60" maxlength="255" class="form-text" />'); - - // Check page_02_textfield_disabled is disabled. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-disabled" aria-describedby="edit-page-02-textfield-disabled--description" disabled="disabled" type="text" id="edit-page-02-textfield-disabled" name="page_02_textfield_disabled" value="" size="60" maxlength="255" class="form-text" />'); + $webform = Webform::load('test_states_server_nested'); - // Check page_02_textfield_enabled is not disabled. - $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-enabled" aria-describedby="edit-page-02-textfield-enabled--description" type="text" id="edit-page-02-textfield-enabled" name="page_02_textfield_enabled" value="" size="60" maxlength="255" class="form-text" />'); + // Check a and b sets target required page 1. + $edit = ['a' => TRUE, 'b' => TRUE, 'c' => FALSE]; + $this->drupalPostForm('webform/test_states_server_nested', $edit, t('Next Page >')); + $this->assertRaw('page_1_target: [a and b] or c = required field is required.'); - // Check page_02_textfield_visible is visible. - $this->assertFieldByName('page_02_textfield_visible'); + // Check c sets target required page 1. + $edit = ['a' => FALSE, 'b' => TRUE, 'c' => TRUE]; + $this->drupalPostForm('webform/test_states_server_nested', $edit, t('Next Page >')); + $this->assertRaw('page_1_target: [a and b] or c = required field is required.'); - // Check page_02_textfield_visible_slide is visible. - $this->assertFieldByName('page_02_textfield_visible_slide'); + // Check none sets target not required page 1. + $edit = ['a' => FALSE, 'b' => FALSE, 'c' => FALSE]; + $this->drupalPostForm('webform/test_states_server_nested', $edit, t('Next Page >')); + $this->assertNoRaw('page_1_target: [a and b] or c = required field is required.'); - // Check page_02_textfield_invisible is not visible. - $this->assertNoFieldByName('page_02_textfield_invisible'); + // Check none sets target not required page 2. + $this->assertRaw('<label for="edit-page-2-target">page_2_target: [a and b] or c = required</label>'); + $this->assertRaw('<input data-drupal-selector="edit-page-2-target" type="text" id="edit-page-2-target" name="page_2_target" value="" size="60" maxlength="255" class="form-text" />'); - // Check page_02_textfield_invisible_slide is not visible. - $this->assertNoFieldByName('page_02_textfield_invisible_slide'); + // Check a and b sets target required page 2. + $edit = ['a' => TRUE, 'b' => TRUE, 'c' => FALSE, 'page_1_target' => '{value}']; + $this->drupalPostForm('webform/test_states_server_nested', $edit, t('Next Page >')); + $this->assertNoRaw('<input data-drupal-selector="edit-page-2-target" type="text" id="edit-page-2-target" name="page_2_target" value="" size="60" maxlength="255" class="form-text" />'); + $this->assertRaw('<label for="edit-page-2-target" class="js-form-required form-required">page_2_target: [a and b] or c = required</label>'); + $this->assertRaw('<input data-drupal-selector="edit-page-2-target" type="text" id="edit-page-2-target" name="page_2_target" value="" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); - // Check page_02_checkbox_checked is checked. - $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-checked" aria-describedby="edit-page-02-checkbox-checked--description" type="checkbox" id="edit-page-02-checkbox-checked" name="page_02_checkbox_checked" value="1" checked="checked" class="form-checkbox" />'); + /**************************************************************************/ + // test_states_crosspage. + /**************************************************************************/ - // Check page_02_checkbox_unchecked is not checked. - $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-unchecked" aria-describedby="edit-page-02-checkbox-unchecked--description" type="checkbox" id="edit-page-02-checkbox-unchecked" name="page_02_checkbox_unchecked" value="1" class="form-checkbox" />'); + $webform = Webform::load('test_states_crosspage'); - // Check page_02_details_expanded is open. - $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_expanded" data-drupal-selector="edit-page-02-details-expanded" aria-describedby="edit-page-02-details-expanded--description" id="edit-page-02-details-expanded" class="js-form-wrapper form-wrapper" open="open">'); + $trigger_1_name = 'webform_states_' . md5('.webform-submission-test-states-crosspage-add-form :input[name="trigger_1"]'); + $trigger_2_name = 'webform_states_' . md5('.webform-submission-test-states-crosspage-add-form :input[name="trigger_2"]'); - // Check page_02_details_collapsed is not open. - $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_collapsed" data-drupal-selector="edit-page-02-details-collapsed" aria-describedby="edit-page-02-details-collapsed--description" id="edit-page-02-details-collapsed" class="js-form-wrapper form-wrapper">'); - } + // Check cross page states attribute and input on page 1. + $this->drupalGet('/webform/test_states_crosspage'); + $this->assertRaw(':input[name=\u0022' . $trigger_2_name . '\u0022]'); + $this->assertFieldByName($trigger_2_name); - /** - * Tests conditions (#states) validator for elements . - */ - public function testStatesValidatorElementVisible() { - $webform = Webform::load('test_form_states_server_preview'); - - // Check trigger unchecked and elements are conditionally hidden. - $this->postSubmission($webform, [], t('Preview')); - $this->assertRaw('trigger_checkbox'); - $this->assertNoRaw('dependent_checkbox'); - $this->assertNoRaw('dependent_markup'); - $this->assertNoRaw('dependent_message'); - $this->assertNoRaw('dependent_fieldset'); - $this->assertNoRaw('nested_textfield'); - - // Check trigger checked and elements are conditionally visible. - $this->postSubmission($webform, ['trigger_checkbox' => TRUE], t('Preview')); - $this->assertRaw('trigger_checkbox'); - $this->assertRaw('dependent_checkbox'); - $this->assertRaw('dependent_markup'); - $this->assertRaw('dependent_message'); - $this->assertRaw('dependent_fieldset'); - $this->assertRaw('nested_textfield'); + // Check cross page states attribute and input on page 2. + $this->postSubmission($webform, ['trigger_1' => TRUE], t('Next Page >')); + $this->assertRaw(':input[name=\u0022' . $trigger_1_name . '\u0022]'); + $this->assertFieldByName($trigger_1_name); } } - diff --git a/web/modules/webform/src/Tests/States/WebformStatesWizardTest.php b/web/modules/webform/src/Tests/States/WebformStatesWizardTest.php new file mode 100644 index 0000000000..9ae54351b0 --- /dev/null +++ b/web/modules/webform/src/Tests/States/WebformStatesWizardTest.php @@ -0,0 +1,203 @@ +<?php + +namespace Drupal\webform\Tests\States; + +use Drupal\webform\Entity\Webform; +use Drupal\webform\Tests\WebformTestBase; + +/** + * Tests for webform states wizard server. + * + * @group Webform + */ +class WebformStatesWizardTest extends WebformTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = [ + 'test_states_server_wizard', + ]; + + /** + * Tests webform submission conditions (#states) validator wizard cross-page conditions. + */ + public function testFormStatesValidatorWizard() { + $webform = Webform::load('test_states_server_wizard'); + + /**************************************************************************/ + + // Go to default #states for page 02 with trigger-checkbox unchecked. + $this->postSubmission($webform, [], t('Next Page >')); + + $this->assertRaw("page_01_trigger_checkbox: 0 +page_01_textfield_required: '{default_value}' +page_01_textfield_optional: '{default_value}' +page_01_textfield_disabled: '' +page_01_textfield_enabled: '' +page_01_textfield_visible: '' +page_01_textfield_invisible: '' +page_01_checkbox_checked: 0 +page_01_checkbox_unchecked: 0 +page_02_textfield_required: '{default_value}' +page_02_textfield_optional: '{default_value}' +page_02_textfield_disabled: '' +page_02_textfield_enabled: '' +page_02_textfield_visible: '{default_value}' +page_02_textfield_visible_slide: '{default_value}' +page_02_textfield_invisible: '{default_value}' +page_02_textfield_invisible_slide: '{default_value}' +page_02_checkbox_checked: 0 +page_02_checkbox_unchecked: 0"); + + // Check trigger-checkbox value is No. + $this->assertRaw('<input data-drupal-selector="edit-page-01-trigger-checkbox-computed" type="hidden" name="page_01_trigger_checkbox_computed" value="No" />'); + + // Check page_02_textfield_required is not required. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-required" aria-describedby="edit-page-02-textfield-required--description" type="text" id="edit-page-02-textfield-required" name="page_02_textfield_required" value="{default_value}" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_optional is required. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-optional" aria-describedby="edit-page-02-textfield-optional--description" type="text" id="edit-page-02-textfield-optional" name="page_02_textfield_optional" value="{default_value}" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); + + // Check page_02_textfield_disabled is not disabled. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-disabled" aria-describedby="edit-page-02-textfield-disabled--description" type="text" id="edit-page-02-textfield-disabled" name="page_02_textfield_disabled" value="" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_enabled is disabled. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-enabled" aria-describedby="edit-page-02-textfield-enabled--description" disabled="disabled" type="text" id="edit-page-02-textfield-enabled" name="page_02_textfield_enabled" value="" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_visible is not visible. + $this->assertNoFieldByName('page_02_textfield_visible', '{default_value}'); + + // Check page_02_textfield_visible_slide is not visible. + $this->assertNoFieldByName('page_02_textfield_visible_slide', '{default_value}'); + + // Check page_02_textfield_invisible is visible. + $this->assertFieldByName('page_02_textfield_invisible', '{default_value}'); + + // Check page_02_textfield_invisible_slide is visible. + $this->assertFieldByName('page_02_textfield_invisible_slide', '{default_value}'); + + // Check page_02_checkbox_checked is not checked. + $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-checked" aria-describedby="edit-page-02-checkbox-checked--description" type="checkbox" id="edit-page-02-checkbox-checked" name="page_02_checkbox_checked" value="1" class="form-checkbox" />'); + + // Check page_02_checkbox_unchecked is checked. + $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-unchecked" aria-describedby="edit-page-02-checkbox-unchecked--description" type="checkbox" id="edit-page-02-checkbox-unchecked" name="page_02_checkbox_unchecked" value="1" checked="checked" class="form-checkbox" />'); + + // Check page_02_details_expanded is not open. + $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_expanded" data-drupal-selector="edit-page-02-details-expanded" aria-describedby="edit-page-02-details-expanded--description" id="edit-page-02-details-expanded" class="js-form-wrapper form-wrapper"> '); + + // Check page_02_details_collapsed is open. + $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_collapsed" data-drupal-selector="edit-page-02-details-collapsed" aria-describedby="edit-page-02-details-collapsed--description" id="edit-page-02-details-collapsed" class="js-form-wrapper form-wrapper" open="open">'); + + // Check submission data. + $this->drupalPostForm(NULL, [], t('Submit')); + $this->assertRaw("page_01_trigger_checkbox: 0 +page_01_textfield_required: '{default_value}' +page_01_textfield_optional: '{default_value}' +page_01_textfield_disabled: '' +page_01_textfield_enabled: '' +page_01_textfield_visible: '' +page_01_textfield_invisible: '' +page_01_checkbox_checked: 0 +page_01_checkbox_unchecked: 0 +page_02_textfield_required: '{default_value}' +page_02_textfield_optional: '{default_value}' +page_02_textfield_disabled: '' +page_02_textfield_enabled: '' +page_02_textfield_visible: '' +page_02_textfield_visible_slide: '' +page_02_textfield_invisible: '{default_value}' +page_02_textfield_invisible_slide: '{default_value}' +page_02_checkbox_checked: 0 +page_02_checkbox_unchecked: 1"); + + /**************************************************************************/ + + // Go to default #states for page 02 with trigger_checkbox checked. + $this->postSubmission($webform, ['page_01_trigger_checkbox' => TRUE], t('Next Page >')); + + $this->assertRaw("page_01_trigger_checkbox: 1 +page_01_textfield_required: '{default_value}' +page_01_textfield_optional: '{default_value}' +page_01_textfield_disabled: '' +page_01_textfield_enabled: '' +page_01_textfield_visible: '' +page_01_textfield_invisible: '' +page_01_checkbox_checked: 0 +page_01_checkbox_unchecked: 0 +page_02_textfield_required: '{default_value}' +page_02_textfield_optional: '{default_value}' +page_02_textfield_disabled: '' +page_02_textfield_enabled: '' +page_02_textfield_visible: '{default_value}' +page_02_textfield_visible_slide: '{default_value}' +page_02_textfield_invisible: '{default_value}' +page_02_textfield_invisible_slide: '{default_value}' +page_02_checkbox_checked: 0 +page_02_checkbox_unchecked: 0"); + + // Check trigger-checkbox value is Yes. + $this->assertRaw('<input data-drupal-selector="edit-page-01-trigger-checkbox-computed" type="hidden" name="page_01_trigger_checkbox_computed" value="Yes" />'); + + // Check page_02_textfield_required is required. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-required" aria-describedby="edit-page-02-textfield-required--description" type="text" id="edit-page-02-textfield-required" name="page_02_textfield_required" value="{default_value}" size="60" maxlength="255" class="form-text required" required="required" aria-required="true" />'); + + // Check page_02_textfield_optional is not required. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-optional" aria-describedby="edit-page-02-textfield-optional--description" type="text" id="edit-page-02-textfield-optional" name="page_02_textfield_optional" value="{default_value}" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_disabled is disabled. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-disabled" aria-describedby="edit-page-02-textfield-disabled--description" disabled="disabled" type="text" id="edit-page-02-textfield-disabled" name="page_02_textfield_disabled" value="" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_enabled is not disabled. + $this->assertRaw('<input data-drupal-selector="edit-page-02-textfield-enabled" aria-describedby="edit-page-02-textfield-enabled--description" type="text" id="edit-page-02-textfield-enabled" name="page_02_textfield_enabled" value="" size="60" maxlength="255" class="form-text" />'); + + // Check page_02_textfield_visible is visible. + $this->assertFieldByName('page_02_textfield_visible', '{default_value}'); + + // Check page_02_textfield_visible_slide is visible. + $this->assertFieldByName('page_02_textfield_visible_slide', '{default_value}'); + + // Check page_02_textfield_invisible is not visible. + $this->assertNoFieldByName('page_02_textfield_invisible', '{default_value}'); + + // Check page_02_textfield_invisible_slide is not visible. + $this->assertNoFieldByName('page_02_textfield_invisible_slide', '{default_value}'); + + // Check page_02_checkbox_checked is checked. + $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-checked" aria-describedby="edit-page-02-checkbox-checked--description" type="checkbox" id="edit-page-02-checkbox-checked" name="page_02_checkbox_checked" value="1" checked="checked" class="form-checkbox" />'); + + // Check page_02_checkbox_unchecked is not checked. + $this->assertRaw('<input data-drupal-selector="edit-page-02-checkbox-unchecked" aria-describedby="edit-page-02-checkbox-unchecked--description" type="checkbox" id="edit-page-02-checkbox-unchecked" name="page_02_checkbox_unchecked" value="1" class="form-checkbox" />'); + + // Check page_02_details_expanded is open. + $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_expanded" data-drupal-selector="edit-page-02-details-expanded" aria-describedby="edit-page-02-details-expanded--description" id="edit-page-02-details-expanded" class="js-form-wrapper form-wrapper" open="open">'); + + // Check page_02_details_collapsed is not open. + $this->assertRaw('<details data-webform-details-nosave data-webform-key="page_02_details_collapsed" data-drupal-selector="edit-page-02-details-collapsed" aria-describedby="edit-page-02-details-collapsed--description" id="edit-page-02-details-collapsed" class="js-form-wrapper form-wrapper">'); + + // Check submission data. + $this->drupalPostForm(NULL, [], t('Submit')); + $this->assertRaw("page_01_trigger_checkbox: 1 +page_01_textfield_required: '{default_value}' +page_01_textfield_optional: '{default_value}' +page_01_textfield_disabled: '' +page_01_textfield_enabled: '' +page_01_textfield_visible: '' +page_01_textfield_invisible: '' +page_01_checkbox_checked: 0 +page_01_checkbox_unchecked: 0 +page_02_textfield_required: '{default_value}' +page_02_textfield_optional: '{default_value}' +page_02_textfield_disabled: '' +page_02_textfield_enabled: '' +page_02_textfield_visible: '{default_value}' +page_02_textfield_visible_slide: '{default_value}' +page_02_textfield_invisible: '' +page_02_textfield_invisible_slide: '' +page_02_checkbox_checked: 1 +page_02_checkbox_unchecked: 0"); + } + +} diff --git a/web/modules/webform/src/Tests/Views/WebformViewsBulkFormTest.php b/web/modules/webform/src/Tests/Views/WebformViewsBulkFormTest.php index e913c26a39..7da8a42fb0 100644 --- a/web/modules/webform/src/Tests/Views/WebformViewsBulkFormTest.php +++ b/web/modules/webform/src/Tests/Views/WebformViewsBulkFormTest.php @@ -20,24 +20,20 @@ class WebformViewsBulkFormTest extends WebformTestBase { */ public static $modules = ['webform', 'webform_test_views']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests the webform views bulk form. */ public function testViewsBulkForm() { - $this->drupalLogin($this->adminSubmissionUser); + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + /**************************************************************************/ + + $this->drupalLogin($admin_submission_user); // Check no submissions. - $this->drupalGet('admin/structure/webform/test/views_bulk_form'); + $this->drupalGet('/admin/structure/webform/test/views_bulk_form'); $this->assertRaw('No submissions available.'); // Create a test submission. @@ -46,7 +42,7 @@ public function testViewsBulkForm() { $sid = $this->postSubmissionTest($webform); $webform_submission = $this->loadSubmission($sid); - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); // Check make sticky action. $this->assertFalse($webform_submission->isSticky(), 'Webform submission is not sticky'); @@ -97,7 +93,7 @@ public function testViewsBulkForm() { $this->assertNull($webform_submission, '1: Webform submission has been deleted'); // Check no submissions. - $this->drupalGet('admin/structure/webform/test/views_bulk_form'); + $this->drupalGet('/admin/structure/webform/test/views_bulk_form'); $this->assertRaw('No submissions available.'); } diff --git a/web/modules/webform/src/Tests/WebformAlterHooksTest.php b/web/modules/webform/src/Tests/WebformAlterHooksTest.php index 457eadde0e..1224b70100 100644 --- a/web/modules/webform/src/Tests/WebformAlterHooksTest.php +++ b/web/modules/webform/src/Tests/WebformAlterHooksTest.php @@ -23,7 +23,7 @@ class WebformAlterHooksTest extends WebformNodeTestBase { */ public function testWebformAlterHooks() { // Check webform alter hooks. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw("hook_webform_submission_form_alter(): 'webform_submission_contact_add_form' executed."); $this->assertRaw("hook_form_alter(): 'webform_submission_contact_add_form' executed."); $this->assertRaw("hook_form_webform_submission_BASE_FORM_ID_form_alter(): 'webform_submission_contact_add_form' executed."); diff --git a/web/modules/webform/src/Tests/WebformEditorTest.php b/web/modules/webform/src/Tests/WebformEditorTest.php new file mode 100644 index 0000000000..95f40e4fe5 --- /dev/null +++ b/web/modules/webform/src/Tests/WebformEditorTest.php @@ -0,0 +1,258 @@ +<?php + +namespace Drupal\webform\Tests; + +use Drupal\file\Entity\File; +use Drupal\webform\Entity\Webform; + +/** + * Tests for webform editor. + * + * @group Webform + */ +class WebformEditorTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['file', 'webform']; + + /** + * File usage manager. + * + * @var \Drupal\file\FileUsage\FileUsageInterface + */ + protected $fileUsage; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->fileUsage = $this->container->get('file.usage'); + } + + /** + * Tests webform entity settings files. + */ + public function testWebformSettingsFiles() { + $this->drupalLogin($this->rootUser); + + // Create three test images. + /** @var \Drupal\file\FileInterface[] $images */ + $images = $this->drupalGetTestFiles('image'); + $images = array_slice($images, 0, 5); + foreach ($images as $index => $image_file) { + $images[$index] = File::create((array) $image_file); + $images[$index]->save(); + } + + // Check that all images are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Upload the first image. + $edit = [ + 'description[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + + // Check that first image is not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check create first image file usage. + $this->assertIdentical(['editor' => ['webform' => ['contact' => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + + // Upload the second image. + $edit = [ + 'description[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/><img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + + // Check that first and second image are not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical(['editor' => ['webform' => ['contact' => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + $this->assertIdentical(['editor' => ['webform' => ['contact' => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Remove the first image. + $edit = [ + 'description[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + + // Check that first is temporary and second image is not temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical([], $this->fileUsage->listUsage($images[0]), 'The file has 0 usage.'); + $this->assertIdentical(['editor' => ['webform' => ['contact' => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Set all files back to temporary. + $edit = [ + 'description[value]' => '', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + + // Check that first and second image are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Stop marking unused files as temporary. + \Drupal::configFactory()->getEditable('webform.settings') + ->set('html_editor.make_unused_managed_files_temporary', FALSE) + ->save(); + $this->assertTrue($images[0]->isTemporary()); + + // Check uploaded file is NOT temporary. + $this->assertTrue($images[0]->isTemporary()); + $edit = [ + 'description[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + $this->assertFalse($images[0]->isTemporary()); + + // Check unused file is NOT temporary. + $edit = [ + 'description[value]' => '', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + $this->assertFalse($images[0]->isTemporary()); + + // Start marking unused files as temporary. + \Drupal::configFactory()->getEditable('webform.settings') + ->set('html_editor.make_unused_managed_files_temporary', TRUE) + ->save(); + + $edit = [ + 'description[value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/manage/contact/settings', $edit, t('Save')); + $this->reloadImages($images); + + // Check that upload file is not temporary. + $this->assertFalse($images[0]->isTemporary()); + + // Delete the webform. + Webform::load('contact')->delete(); + $this->reloadImages($images); + + // Check that file is temporary after the webform is deleted. + $this->assertTrue($images[0]->isTemporary()); + } + + /** + * Tests webform configuration files. + */ + public function testWebformConfigurationFiles() { + $this->drupalLogin($this->rootUser); + + // Create three test images. + /** @var \Drupal\file\FileInterface[] $images */ + $images = $this->drupalGetTestFiles('image'); + $images = array_slice($images, 0, 5); + foreach ($images as $index => $image_file) { + $images[$index] = File::create((array) $image_file); + $images[$index]->save(); + } + + // Check that all images are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Upload the first image. + $edit = [ + 'form_settings[default_form_open_message][value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/config', $edit, t('Save configuration')); + $this->reloadImages($images); + + // Check that first image is not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check create first image file usage. + $this->assertIdentical(['editor' => ['config' => ['webform.settings' => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + + // Upload the second image. + $edit = [ + 'form_settings[default_form_open_message][value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[0]->uuid() . '"/><img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/config', $edit, t('Save configuration')); + $this->reloadImages($images); + + // Check that first and second image are not temporary. + $this->assertFalse($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical(['editor' => ['config' => ['webform.settings' => '1']]], $this->fileUsage->listUsage($images[0]), 'The file has 1 usage.'); + $this->assertIdentical(['editor' => ['config' => ['webform.settings' => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Remove the first image. + $edit = [ + 'form_settings[default_form_open_message][value]' => '<img data-entity-type="file" data-entity-uuid="' . $images[1]->uuid() . '"/>', + ]; + $this->drupalPostForm('/admin/structure/webform/config', $edit, t('Save configuration')); + $this->reloadImages($images); + + // Check that first is temporary and second image is not temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertFalse($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + + // Check first and second image file usage. + $this->assertIdentical([], $this->fileUsage->listUsage($images[0]), 'The file has 0 usage.'); + $this->assertIdentical(['editor' => ['config' => ['webform.settings' => '1']]], $this->fileUsage->listUsage($images[1]), 'The file has 1 usage.'); + + // Simulate deleting webform.settings.yml during webform uninstall. + // @see webform_uninstall() + $config = \Drupal::configFactory()->get('webform.settings'); + _webform_config_delete($config); + $this->reloadImages($images); + + // Check that first and second image are temporary. + $this->assertTrue($images[0]->isTemporary()); + $this->assertTrue($images[1]->isTemporary()); + $this->assertTrue($images[2]->isTemporary()); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Reload images. + * + * @param array $images + * An array of image files. + */ + protected function reloadImages(array &$images) { + \Drupal::entityTypeManager()->getStorage('file')->resetCache(); + foreach ($images as $index => $image) { + $images[$index] = File::load($image->id()); + } + } + +} diff --git a/web/modules/webform/src/Tests/WebformEmailProviderTest.php b/web/modules/webform/src/Tests/WebformEmailProviderTest.php new file mode 100644 index 0000000000..40ace012b1 --- /dev/null +++ b/web/modules/webform/src/Tests/WebformEmailProviderTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\webform\Tests; + +/** + * Tests for webform email provider. + * + * @group Webform + */ +class WebformEmailProviderTest extends WebformTestBase { + + /** + * Test webform email provider. + */ + public function testEmailProvider() { + // Revert system.mail back to php_mail. + $this->container->get('config.factory') + ->getEditable('system.mail') + ->set('interface.default', 'php_mail') + ->save(); + + /** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */ + $email_provider = \Drupal::service('webform.email_provider'); + + $this->drupalLogin($this->rootUser); + + // Check Default PHP mailer is enabled because we manually changed the + // system.mail configuration. + $this->drupalGet('/admin/reports/status'); + $this->assertRaw('Provided by php_mail mail plugin.'); + $this->assertNoRaw("Webform PHP mailer: Sends the message as plain text or HTML, using PHP's native mail() function."); + $this->assertRaw('Default PHP mailer: Sends the message as plain text, using PHP\'s native mail() function.'); + + // Check Webform PHP mailer enabled after email provider check. + $email_provider->check(); + $this->drupalGet('/admin/reports/status'); + $this->assertRaw('Provided by the Webform module.'); + $this->assertRaw("Webform PHP mailer: Sends the message as plain text or HTML, using PHP's native mail() function."); + + // Check Mail System: Default PHP mailer after mailsystem module installed. + \Drupal::service('module_installer')->install(['mailsystem']); + $this->drupalGet('/admin/reports/status'); + $this->assertRaw('Provided by the Mail System module.'); + $this->assertNoRaw("Webform PHP mailer: Sends the message as plain text or HTML, using PHP's native mail() function."); + $this->assertRaw('Default PHP mailer: Sends the message as plain text, using PHP\'s native mail() function.'); + + // Check Webform PHP mailer enabled after mailsystem module uninstalled. + \Drupal::service('module_installer')->uninstall(['mailsystem']); + $this->drupalGet('/admin/reports/status'); + $this->assertRaw("Webform PHP mailer: Sends the message as plain text or HTML, using PHP's native mail() function."); + } + +} diff --git a/web/modules/webform/src/Tests/WebformEntityTranslationTest.php b/web/modules/webform/src/Tests/WebformEntityTranslationTest.php index 2bdae12fd7..fef62cc8ab 100644 --- a/web/modules/webform/src/Tests/WebformEntityTranslationTest.php +++ b/web/modules/webform/src/Tests/WebformEntityTranslationTest.php @@ -17,7 +17,7 @@ class WebformEntityTranslationTest extends WebformTestBase { * * @var array */ - public static $modules = ['block', 'webform', 'webform_test_translation']; + public static $modules = ['block', 'webform', 'webform_ui', 'webform_test_translation']; /** * {@inheritdoc} @@ -36,6 +36,10 @@ public function testTranslate() { // Login admin user. $this->drupalLogin($this->rootUser); + // Set [site:name] to 'Test Website' and translate it into Spanish. + $this->drupalPostForm('/admin/config/system/site-information', ['site_name' => 'Test Website'], t('Save configuration')); + $this->drupalPostForm('/admin/config/system/site-information/translate/es/add', ['translation[config_names][system.site][name]' => 'Sitio web de prueba'], t('Save translation')); + /** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */ $translation_manager = \Drupal::service('webform.translation_manager'); @@ -44,43 +48,120 @@ public function testTranslate() { $elements = Yaml::decode($elements_raw); // Check translate tab. - $this->drupalGet('admin/structure/webform/manage/test_translation'); + $this->drupalGet('/admin/structure/webform/manage/test_translation'); $this->assertRaw('>Translate<'); // Check translations. - $this->drupalGet('admin/structure/webform/manage/test_translation/translate'); + $this->drupalGet('/admin/structure/webform/manage/test_translation/translate'); + $this->assertRaw('<a href="' . base_path() . 'webform/test_translation"><strong>English (original)</strong></a>'); + $this->assertRaw('<a href="' . base_path() . 'es/webform/test_translation" hreflang="es">Spanish</a>'); + $this->assertNoRaw('<a href="' . base_path() . 'fr/webform/test_translation" hreflang="fr">French</a>'); $this->assertRaw('<a href="' . base_path() . 'admin/structure/webform/manage/test_translation/translate/es/edit">Edit</a>'); - // Check Spanish translations. - $this->drupalGet('admin/structure/webform/manage/test_translation/translate/es/edit'); + // Check Spanish translation. + $this->drupalGet('/admin/structure/webform/manage/test_translation/translate/es/edit'); $this->assertFieldByName('translation[config_names][webform.webform.test_translation][title]', 'Prueba: Traducción'); $this->assertField('translation[config_names][webform.webform.test_translation][elements]'); + // Check form builder is not translated. + $this->drupalGet('/es/admin/structure/webform/manage/test_translation'); + $this->assertLink('Text field'); + $this->assertNoLink('Campo de texto'); + + // Check form builder is not translated when reset. + $this->drupalPostAjaxForm('es/admin/structure/webform/manage/test_translation', [], ['op' => t('Reset')]); + $this->assertLink('Text field'); + $this->assertNoLink('Campo de texto'); + + // Check element edit form is not translated. + $this->drupalGet('/es/admin/structure/webform/manage/test_translation/element/textfield/edit'); + $this->assertFieldByName('properties[title]', 'Text field'); + $this->assertNoFieldByName('properties[title]', 'Campo de texto'); + // Check translated webform options. - $this->drupalGet('es/webform/test_translation'); + $this->drupalGet('/es/webform/test_translation'); $this->assertRaw('<label for="edit-textfield">Campo de texto</label>'); $this->assertRaw('<option value="1">Uno</option>'); $this->assertRaw('<option value="4">Las cuatro</option>'); // Check translated webform custom composite. - $this->drupalGet('es/webform/test_translation'); - $this->assertRaw('<label for="edit-composite">Compuesto</label>'); + $this->drupalGet('/es/webform/test_translation'); + $this->assertRaw('<label>Compuesto</label>'); $this->assertRaw('<th class="composite-table--first_name webform-multiple-table--first_name">Nombre</th>'); $this->assertRaw('<th class="composite-table--last_name webform-multiple-table--last_name">Apellido</th>'); $this->assertRaw('<th class="composite-table--age webform-multiple-table--age">Edad</th>'); $this->assertRaw('<span class="field-suffix">años. antiguo</span>'); + // Check translated webform address. + $this->drupalGet('/es/webform/test_translation'); + $this->assertRaw('<span class="visually-hidden fieldset-legend">Dirección</span>'); + $this->assertRaw('<label for="edit-address-address">Dirección</label>'); + $this->assertRaw('<label for="edit-address-address-2">Dirección 2</label>'); + $this->assertRaw('<label for="edit-address-city">Ciudad / Pueblo</label>'); + $this->assertRaw('<label for="edit-address-state-province">Estado / Provincia</label>'); + $this->assertRaw('<label for="edit-address-postal-code">ZIP / Código Postal</label>'); + $this->assertRaw('<label for="edit-address-country">Acciones de país</label>'); + + // Check translated webform token. + $this->assertRaw('Site name: Sitio web de prueba'); + // Check that webform is not translated into French. - $this->drupalGet('fr/webform/test_translation'); + $this->drupalGet('/fr/webform/test_translation'); $this->assertRaw('<label for="edit-textfield">Text field</label>'); $this->assertRaw('<option value="1">One</option>'); $this->assertRaw('<option value="4">Four</option>'); + $this->assertRaw('Site name: Test Website'); // Check that French config elements returns the default languages elements. // Please note: This behavior might change. $translation_element = $translation_manager->getElements($webform, 'fr', TRUE); $this->assertEqual($elements, $translation_element); + // Translate [site:name] into French. + $this->drupalPostForm('/admin/config/system/site-information/translate/fr/add', ['translation[config_names][system.site][name]' => 'Site Web de test'], t('Save translation')); + + // Check default elements. + $this->drupalGet('/admin/structure/webform/manage/test_translation/translate/fr/add'); + $this->assertRaw('<textarea lang="fr" data-drupal-selector="edit-translation-config-names-webformwebformtest-translation-elements" aria-describedby="edit-translation-config-names-webformwebformtest-translation-elements--description" class="js-webform-codemirror webform-codemirror yaml form-textarea resize-vertical" data-webform-codemirror-mode="text/x-yaml" id="edit-translation-config-names-webformwebformtest-translation-elements" name="translation[config_names][webform.webform.test_translation][elements]" rows="48" cols="60">textfield: + '#title': 'Text field' +select_options: + '#title': 'Select (options)' +select_custom: + '#title': 'Select (custom)' + '#options': + 4: Four + 5: Five + 6: Six + '#other__option_label': 'Custom number…' +details: + '#title': Details +markup: + '#markup': 'This is some HTML markup.' +composite: + '#title': Composite + '#element': + first_name: + '#title': 'First name' + last_name: + '#title': 'Last name' + age: + '#title': Age + '#field_suffix': ' yrs. old' +address: + '#title': Address + '#address__title': Address + '#address_2__title': 'Address 2' + '#city__title': City/Town + '#state_province__title': State/Province + '#postal_code__title': 'ZIP/Postal Code' + '#country__title': Country +token: + '#title': 'Computed (token)' +actions: + '#title': 'Submit button(s)' + '#submit__label': 'Send message'</textarea> +</div>'); + // Create French translation. $translation_elements = [ 'textfield' => [ @@ -97,8 +178,15 @@ public function testTranslate() { $this->drupalPostForm('admin/structure/webform/manage/test_translation/translate/fr/add', $edit, t('Save translation')); // Check French translation. - $this->drupalGet('fr/webform/test_translation'); + $this->drupalGet('/fr/webform/test_translation'); $this->assertRaw('<label for="edit-textfield">French</label>'); + $this->assertRaw('Site name: Site Web de test'); + + // Check translations. + $this->drupalGet('/admin/structure/webform/manage/test_translation/translate'); + $this->assertRaw('<a href="' . base_path() . 'webform/test_translation"><strong>English (original)</strong></a>'); + $this->assertRaw('<a href="' . base_path() . 'es/webform/test_translation" hreflang="es">Spanish</a>'); + $this->assertRaw('<a href="' . base_path() . 'fr/webform/test_translation" hreflang="fr">French</a>'); // Check French config elements only contains translated properties and // custom properties are removed. @@ -110,21 +198,33 @@ public function testTranslate() { /**************************************************************************/ // Check English table headers are not translated. - $this->drupalGet('admin/structure/webform/manage/test_translation/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_translation/results/submissions'); $this->assertRaw('>Text field<'); $this->assertRaw('>Select (options)<'); $this->assertRaw('>Select (custom)<'); $this->assertRaw('>Composite<'); // Check Spanish table headers are translated. - $this->drupalGet('es/admin/structure/webform/manage/test_translation/results/submissions'); + $this->drupalGet('/es/admin/structure/webform/manage/test_translation/results/submissions'); $this->assertRaw('>Campo de texto<'); $this->assertRaw('>Seleccione (opciones)<'); $this->assertRaw('>Seleccione (personalizado)<'); $this->assertRaw('>Compuesto<'); + // Create translated submissions. + $this->drupalPostForm('webform/test_translation', ['textfield' => 'English Submission'], 'Send message'); + $this->drupalPostForm('es/webform/test_translation', ['textfield' => 'Spanish Submission'], 'Enviar mensaje'); + $this->drupalPostForm('fr/webform/test_translation', ['textfield' => 'French Submission'], 'Send message'); + + // Check computed token is NOT translated for each language because only + // one language can be loaded for a config translation. + $this->drupalGet('/admin/structure/webform/manage/test_translation/results/submissions'); + $this->assertRaw('Site name: Test Website'); + $this->assertNoRaw('Site name: Sitio web de prueba'); + $this->assertNoRaw('Site name: Sitio web de prueba'); + /**************************************************************************/ - // Site wide language + // Site wide language. /**************************************************************************/ // Make sure the site language is English (en). @@ -132,30 +232,30 @@ public function testTranslate() { $language_manager = \Drupal::languageManager(); - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('en')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('en')]); $this->assertRaw('<label for="edit-textfield">Text field</label>'); // Check Spanish translation. - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('es')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('es')]); $this->assertRaw('<label for="edit-textfield">Campo de texto</label>'); // Check French translation. - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('fr')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('fr')]); $this->assertRaw('<label for="edit-textfield">French</label>'); // Change site language to French (fr). \Drupal::configFactory()->getEditable('system.site')->set('default_langcode', 'fr')->save(); // Check English translation. - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('en')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('en')]); $this->assertRaw('<label for="edit-textfield">Text field</label>'); // Check Spanish translation. - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('es')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('es')]); $this->assertRaw('<label for="edit-textfield">Campo de texto</label>'); // Check French translation. - $this->drupalGet('webform/test_translation', ['language' => $language_manager->getLanguage('fr')]); + $this->drupalGet('/webform/test_translation', ['language' => $language_manager->getLanguage('fr')]); $this->assertRaw('<label for="edit-textfield">French</label>'); /**************************************************************************/ @@ -170,16 +270,16 @@ public function testTranslate() { ]; $this->drupalPostForm('admin/structure/webform/manage/test_translation/duplicate', $edit, t('Save')); - // Check duplicate English translation. - $this->drupalGet('webform/duplicate', ['language' => $language_manager->getLanguage('en')]); + // Check duplicate English translation. + $this->drupalGet('/webform/duplicate', ['language' => $language_manager->getLanguage('en')]); $this->assertRaw('<label for="edit-textfield">Text field</label>'); // Check duplicate Spanish translation. - $this->drupalGet('webform/duplicate', ['language' => $language_manager->getLanguage('es')]); + $this->drupalGet('/webform/duplicate', ['language' => $language_manager->getLanguage('es')]); $this->assertRaw('<label for="edit-textfield">Campo de texto</label>'); // Check duplicate French translation. - $this->drupalGet('webform/duplicate', ['language' => $language_manager->getLanguage('fr')]); + $this->drupalGet('/webform/duplicate', ['language' => $language_manager->getLanguage('fr')]); $this->assertRaw('<label for="edit-textfield">French</label>'); } diff --git a/web/modules/webform/src/Tests/WebformHelpTest.php b/web/modules/webform/src/Tests/WebformHelpTest.php index 05d08df2c2..7dc184f355 100644 --- a/web/modules/webform/src/Tests/WebformHelpTest.php +++ b/web/modules/webform/src/Tests/WebformHelpTest.php @@ -32,28 +32,42 @@ public function testHelp() { $this->drupalLogin($this->rootUser); // Check notifications, promotion, and welcome messages displayed. - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->assertRaw('This is a warning notification.'); $this->assertRaw('This is an info notification.'); $this->assertRaw('The Drupal Association brings value to Drupal and to you.'); $this->assertRaw('Welcome to the Webform module for Drupal 8.'); // Close all notifications, promotion, and welcome messages. - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->clickLink('×', 0); - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->clickLink('×', 0); - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->clickLink('×', 0); - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->clickLink('×', 0); // Check notifications, promotion, and welcome messages closed. - $this->drupalGet('admin/structure/webform'); + $this->drupalGet('/admin/structure/webform'); $this->assertNoRaw('This is a warning notification.'); $this->assertNoRaw('This is an info notification.'); $this->assertNoRaw('The Drupal Association brings value to Drupal and to you.'); $this->assertNoRaw('Welcome to the Webform module for Drupal 8.'); + + // Check that help is enabled. + $this->drupalGet('/admin/structure/webform/config/advanced'); + $this->assertRaw('block block-help block-help-block'); + $this->assertRaw('The <strong>Advanced configuration</strong> page allows an administrator to enable/disable UI behaviors, manage requirements and define data used for testing webforms.'); + + // Disable help via the UI which will clear the cached help block. + $this->drupalPostForm('admin/structure/webform/config/advanced', ['ui[help_disabled]' => TRUE], t('Save configuration')); + + // Check that help is disabled. + $this->drupalGet('/admin/structure/webform/config/advanced'); + $this->assertNoRaw('block block-help block-help-block'); + $this->assertNoRaw('The <strong>Advanced configuration</strong> page allows an administrator to enable/disable UI behaviors, manage requirements and define data used for testing webforms.'); + } } diff --git a/web/modules/webform/src/Tests/WebformLibrariesTest.php b/web/modules/webform/src/Tests/WebformLibrariesTest.php index d8a2e7c4ff..90c7b354df 100644 --- a/web/modules/webform/src/Tests/WebformLibrariesTest.php +++ b/web/modules/webform/src/Tests/WebformLibrariesTest.php @@ -23,16 +23,6 @@ class WebformLibrariesTest extends WebformTestBase { */ protected static $testWebforms = ['test_libraries_optional']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests webform libraries. */ @@ -50,16 +40,16 @@ public function testLibraries() { // Enable jquery.chosen and jquery.icheck. $edit = [ - 'libraries[excluded_libraries][jquery.chosen]' => TRUE, - 'libraries[excluded_libraries][jquery.icheck]' => TRUE, + 'excluded_libraries[jquery.chosen]' => TRUE, + 'excluded_libraries[jquery.icheck]' => TRUE, ]; $this->drupalPostForm('admin/structure/webform/config/libraries', $edit, t('Save configuration')); // Check optional libraries are included. - $this->drupalGet('webform/test_libraries_optional'); + $this->drupalGet('/webform/test_libraries_optional'); $this->assertRaw('/select2.min.js'); - $this->assertRaw('/chosen.jquery.js'); - $this->assertRaw('/jquery.word-and-character-counter.min.js'); + $this->assertRaw('/chosen.jquery.min.js'); + $this->assertRaw('/textcounter.min.js'); $this->assertRaw('/intlTelInput.min.js'); $this->assertRaw('/jquery.inputmask.bundle.min.js'); $this->assertRaw('/icheck.js'); @@ -74,25 +64,25 @@ public function testLibraries() { // Exclude optional libraries. $edit = [ - 'libraries[excluded_libraries][ckeditor.fakeobjects]' => FALSE, - 'libraries[excluded_libraries][ckeditor.image]' => FALSE, - 'libraries[excluded_libraries][ckeditor.link]' => FALSE, - 'libraries[excluded_libraries][codemirror]' => FALSE, - 'libraries[excluded_libraries][jquery.icheck]' => FALSE, - 'libraries[excluded_libraries][jquery.inputmask]' => FALSE, - 'libraries[excluded_libraries][jquery.intl-tel-input]' => FALSE, - 'libraries[excluded_libraries][jquery.select2]' => FALSE, - 'libraries[excluded_libraries][jquery.chosen]' => FALSE, - 'libraries[excluded_libraries][jquery.timepicker]' => FALSE, - 'libraries[excluded_libraries][jquery.word-and-character-counter]' => FALSE, + 'excluded_libraries[ckeditor.fakeobjects]' => FALSE, + 'excluded_libraries[ckeditor.image]' => FALSE, + 'excluded_libraries[ckeditor.link]' => FALSE, + 'excluded_libraries[codemirror]' => FALSE, + 'excluded_libraries[jquery.icheck]' => FALSE, + 'excluded_libraries[jquery.inputmask]' => FALSE, + 'excluded_libraries[jquery.intl-tel-input]' => FALSE, + 'excluded_libraries[jquery.select2]' => FALSE, + 'excluded_libraries[jquery.chosen]' => FALSE, + 'excluded_libraries[jquery.timepicker]' => FALSE, + 'excluded_libraries[jquery.textcounter]' => FALSE, ]; $this->drupalPostForm('admin/structure/webform/config/libraries', $edit, t('Save configuration')); // Check optional libraries are excluded. - $this->drupalGet('webform/test_libraries_optional'); + $this->drupalGet('/webform/test_libraries_optional'); $this->assertNoRaw('/select2.min.js'); - $this->assertNoRaw('/chosen.jquery.js'); - $this->assertNoRaw('/jquery.word-and-character-counter.min.js'); + $this->assertNoRaw('/chosen.jquery.min.js'); + $this->assertNoRaw('/textcounter.min.js'); $this->assertNoRaw('/intlTelInput.min.js'); $this->assertNoRaw('/jquery.inputmask.bundle.min.js'); $this->assertNoRaw('/icheck.js'); @@ -106,25 +96,24 @@ public function testLibraries() { } // Check that status report excludes optional libraries. - $this->drupalGet('admin/reports/status'); - $this->assertText('The CKEditor: Fakeobjects library is excluded.'); - $this->assertText('The CKEditor: Image library is excluded.'); - $this->assertText('The CKEditor: Link library is excluded.'); - $this->assertText('The Code Mirror library is excluded.'); - $this->assertText('The jQuery: iCheck library is excluded.'); - $this->assertText('The jQuery: Input Mask library is excluded.'); - $this->assertText('The jQuery: Select2 library is excluded.'); - $this->assertText('The jQuery: Chosen library is excluded.'); - $this->assertText('The jQuery: Timepicker library is excluded.'); - $this->assertText('The jQuery: Word and character counter plug-in! library is excluded.'); + $this->drupalGet('/admin/reports/status'); + $this->assertNoText('CKEditor: Fakeobjects library '); + $this->assertNoText('CKEditor: Image library '); + $this->assertNoText('CKEditor: Link library '); + $this->assertNoText('Code Mirror library '); + $this->assertNoText('jQuery: iCheck library '); + $this->assertNoText('jQuery: Input Mask library '); + $this->assertNoText('jQuery: Select2 library '); + $this->assertNoText('jQuery: Chosen library '); + $this->assertNoText('jQuery: Timepicker library '); + $this->assertNoText('jQuery: Text Counter library '); // Issue #2934542: Fix broken Webform.Drupal\webform\Tests\WebformLibrariesTest // @see https://www.drupal.org/project/webform/issues/2934542 /* // Exclude element types that require libraries. $edit = [ - 'excluded_elements[webform_image_select]' => FALSE, - 'excluded_elements[webform_location]' => FALSE, + 'excluded_elements[webform_location_geocomplete]' => FALSE, 'excluded_elements[webform_rating]' => FALSE, 'excluded_elements[webform_signature]' => FALSE, 'excluded_elements[webform_toggle]' => FALSE, @@ -133,13 +122,34 @@ public function testLibraries() { $this->drupalPostForm('admin/structure/webform/config/elements', $edit, t('Save configuration')); // Check that status report excludes libraries required by element types. - $this->drupalGet('admin/reports/status'); - $this->assertText('The jQuery: Geocoding and Places Autocomplete Plugin library is excluded because required element types (webform_location) are excluded.'); - $this->assertText('The jQuery: Image Picker library is excluded because required element types (webform_image_select) are excluded.'); - $this->assertText('The jQuery: RateIt library is excluded because required element types (webform_rating) are excluded.'); - $this->assertText('The jQuery: Toggles library is excluded because required element types (webform_toggle; webform_toggles) are excluded.'); - $this->assertText('The Signature Pad library is excluded because required element types (webform_signature) are excluded.'); + $this->drupalGet('/admin/reports/status'); + $this->assertNoText('jQuery: Geocoding and Places Autocomplete Plugin library'); + $this->assertNoText('jQuery: Image Picker library'); + $this->assertNoText('jQuery: RateIt library'); + $this->assertNoText('jQuery: Toggles library'); + $this->assertNoText('Signature Pad library'); */ + + // Check that chosen and select2 using webform's CDN URLs. + $edit = [ + 'excluded_libraries[jquery.select2]' => TRUE, + 'excluded_libraries[jquery.chosen]' => TRUE, + ]; + $this->drupalPostForm('admin/structure/webform/config/libraries', $edit, t('Save configuration')); + $this->drupalGet('/webform/test_libraries_optional'); + $this->assertRaw('https://cdnjs.cloudflare.com/ajax/libs/chosen'); + $this->assertRaw('https://cdnjs.cloudflare.com/ajax/libs/select2'); + + // Install chosen and select2 modules. + \Drupal::service('module_installer')->install(['chosen', 'chosen_lib', 'select2']); + drupal_flush_all_caches(); + + // Check that chosen and select2 using module's path and not CDN. + $this->drupalGet('/webform/test_libraries_optional'); + $this->assertNoRaw('https://cdnjs.cloudflare.com/ajax/libs/chosen'); + $this->assertNoRaw('https://cdnjs.cloudflare.com/ajax/libs/select2'); + $this->assertRaw('/modules/contrib/chosen/css/chosen-drupal.css'); + $this->assertRaw('/libraries/select2/dist/css/select2.min.css'); } } diff --git a/web/modules/webform/src/Tests/WebformOptionsTest.php b/web/modules/webform/src/Tests/WebformOptionsTest.php index fc319d18f9..b113381a3e 100644 --- a/web/modules/webform/src/Tests/WebformOptionsTest.php +++ b/web/modules/webform/src/Tests/WebformOptionsTest.php @@ -26,21 +26,23 @@ class WebformOptionsTest extends WebformTestBase { */ protected static $testWebforms = ['test_options']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests webform options entity. */ public function testWebformOptions() { - $this->drupalLogin($this->normalUser); + $normal_user = $this->drupalCreateUser(); + + $admin_user = $this->drupalCreateUser([ + 'access site reports', + 'administer site configuration', + 'administer webform', + 'create webform', + 'administer users', + ]); + + /**************************************************************************/ + + $this->drupalLogin($normal_user); // Check get element options. $yes_no_options = ['Yes' => 'Yes', 'No' => 'No']; @@ -82,7 +84,7 @@ public function testWebformOptions() { // Check hook_webform_options_alter() && hook_webform_options_WEBFORM_OPTIONS_ID_alter(). // Check that the default value can be set from the alter hook. - $this->drupalGet('webform/test_options'); + $this->drupalGet('/webform/test_options'); $this->assertRaw('<select data-drupal-selector="edit-custom" id="edit-custom" name="custom" class="form-select"><option value="">- None -</option><option value="one" selected="selected">One</option><option value="two">Two</option><option value="three">Three</option></select>'); $this->assertRaw('<select data-drupal-selector="edit-test" id="edit-test" name="test" class="form-select"><option value="" selected="selected">- None -</option><option value="four">Four</option><option value="five">Five</option><option value="six">Six</option></select>'); @@ -93,11 +95,11 @@ public function testWebformOptions() { $webform_test_options->save(); $this->debug($webform_test_options->getOptions()); - $this->drupalGet('webform/test_options'); + $this->drupalGet('/webform/test_options'); $this->assertRaw('<select data-drupal-selector="edit-test" id="edit-test" name="test" class="form-select"><option value="" selected="selected">- None -</option><option value="red">Red</option><option value="white">White</option><option value="blue">Blue</option><option value="four">Four</option><option value="five">Five</option><option value="six">Six</option></select>'); // Check custom options set via alter hook(). - $this->drupalGet('webform/test_options'); + $this->drupalGet('/webform/test_options'); $this->assertRaw('<select data-drupal-selector="edit-test" id="edit-test" name="test" class="form-select"><option value="" selected="selected">- None -</option><option value="red">Red</option><option value="white">White</option><option value="blue">Blue</option><option value="four">Four</option><option value="five">Five</option><option value="six">Six</option></select>'); // Check that 'Afghanistan' is the first option. @@ -116,16 +118,16 @@ public function testWebformOptions() { $this->assertEqual(reset($options), 'Switzerland'); // Check admin user access denied. - $this->drupalGet('admin/structure/webform/config/options/manage'); + $this->drupalGet('/admin/structure/webform/config/options/manage'); $this->assertResponse(403); - $this->drupalGet('admin/structure/webform/config/options/manage/add'); + $this->drupalGet('/admin/structure/webform/config/options/manage/add'); $this->assertResponse(403); // Check admin user access. - $this->drupalLogin($this->adminWebformUser); - $this->drupalGet('admin/structure/webform/config/options/manage'); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/structure/webform/config/options/manage'); $this->assertResponse(200); - $this->drupalGet('admin/structure/webform/config/options/manage/add'); + $this->drupalGet('/admin/structure/webform/config/options/manage/add'); $this->assertResponse(200); } diff --git a/web/modules/webform/src/Tests/WebformRenderingTest.php b/web/modules/webform/src/Tests/WebformRenderingTest.php index edbd547059..94b75dde0e 100644 --- a/web/modules/webform/src/Tests/WebformRenderingTest.php +++ b/web/modules/webform/src/Tests/WebformRenderingTest.php @@ -88,7 +88,7 @@ public function testRendering() { // $this->assertContains($html_email['params']['body'], '<b><em>textfield_markup</em></b><br /><em>{prefix}</em>{default_value}<em>{suffix}</em><br /><br />'); // $this->assertContains($html_email['params']['body'], '<b>textfield_special_characters (&><#)</b><br />(&><#){default_value}(&><#)<br /><br />'); // $this->assertContains($html_email['params']['body'], '<b>text_format_basic_html</b><br /><p><em>{default_value}</em></p><br /><br />'); - + // Check plain text email. $this->assertEqual($text_email['subject'], 'submission label (&>'); $this->assertEqual($text_email['params']['subject'], 'submission <em>label</em> (&><#)'); diff --git a/web/modules/webform/src/Tests/WebformResultsDisabledTest.php b/web/modules/webform/src/Tests/WebformResultsDisabledTest.php index 9f956f1e14..b1e28c8e80 100644 --- a/web/modules/webform/src/Tests/WebformResultsDisabledTest.php +++ b/web/modules/webform/src/Tests/WebformResultsDisabledTest.php @@ -31,14 +31,14 @@ public function testSettings() { $this->assertFalse($webform_submission, 'Submission not saved to the database.'); // Check that error message is displayed and form is available for admins. - $this->drupalGet('webform/test_form_results_disabled'); + $this->drupalGet('/webform/test_form_results_disabled'); $this->assertRaw(t('This webform is currently not saving any submitted data.')); $this->assertFieldByName('op', 'Submit'); $this->assertNoRaw(t('Unable to display this webform. Please contact the site administrator.')); // Check that error message not displayed and form is disabled for everyone. $this->drupalLogout(); - $this->drupalGet('webform/test_form_results_disabled'); + $this->drupalGet('/webform/test_form_results_disabled'); $this->assertNoRaw(t('This webform is currently not saving any submitted data.')); $this->assertNoFieldByName('op', 'Submit'); $this->assertRaw(t('Unable to display this webform. Please contact the site administrator.')); @@ -49,18 +49,18 @@ public function testSettings() { $this->drupalLogin($this->rootUser); // Check that no error message is displayed and form is available for admins. - $this->drupalGet('webform/test_form_results_disabled'); + $this->drupalGet('/webform/test_form_results_disabled'); $this->assertNoRaw(t('This webform is currently not saving any submitted data.')); $this->assertNoRaw(t('Unable to display this webform. Please contact the site administrator.')); $this->assertFieldByName('op', 'Submit'); // Check that results tab is not accessible. - $this->drupalGet('admin/structure/webform/manage/test_form_results_disabled/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_form_results_disabled/results/submissions'); $this->assertResponse(403); // Check that error message not displayed and form is enabled for everyone. $this->drupalLogout(); - $this->drupalGet('webform/test_form_results_disabled'); + $this->drupalGet('/webform/test_form_results_disabled'); $this->assertNoRaw(t('This webform is currently not saving any submitted data.')); $this->assertNoRaw(t('Unable to display this webform. Please contact the site administrator.')); $this->assertFieldByName('op', 'Submit'); @@ -73,7 +73,7 @@ public function testSettings() { $this->drupalLogin($this->rootUser); // Check that results tab is accessible. - $this->drupalGet('admin/structure/webform/manage/test_form_results_disabled/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_form_results_disabled/results/submissions'); $this->assertResponse(200); // Post a submission. @@ -81,7 +81,7 @@ public function testSettings() { $webform_submission = WebformSubmission::load($sid); // Check that submission is available. - $this->drupalGet('admin/structure/webform/manage/test_form_results_disabled/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_form_results_disabled/results/submissions'); $this->assertNoRaw('This webform is currently not saving any submitted data'); $this->assertRaw('>' . $webform_submission->serial() . '<'); @@ -90,7 +90,7 @@ public function testSettings() { $webform_results_disabled->save(); // Check that submission is still available with warning. - $this->drupalGet('admin/structure/webform/manage/test_form_results_disabled/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_form_results_disabled/results/submissions'); $this->assertRaw('This webform is currently not saving any submitted data'); $this->assertRaw('>' . $webform_submission->serial() . '<'); @@ -98,7 +98,7 @@ public function testSettings() { $webform_submission->delete(); // Check that results tab is not accessible. - $this->drupalGet('admin/structure/webform/manage/test_form_results_disabled/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/test_form_results_disabled/results/submissions'); $this->assertResponse(403); } diff --git a/web/modules/webform/src/Tests/WebformResultsExportDownloadTest.php b/web/modules/webform/src/Tests/WebformResultsExportDownloadTest.php index 9c386bffd5..63033e32b7 100644 --- a/web/modules/webform/src/Tests/WebformResultsExportDownloadTest.php +++ b/web/modules/webform/src/Tests/WebformResultsExportDownloadTest.php @@ -28,16 +28,6 @@ class WebformResultsExportDownloadTest extends WebformTestBase { */ protected static $testWebforms = ['test_element_managed_file']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests download files. */ diff --git a/web/modules/webform/src/Tests/WebformResultsExportOptionsTest.php b/web/modules/webform/src/Tests/WebformResultsExportOptionsTest.php index 38df7b1890..ccd11509cf 100644 --- a/web/modules/webform/src/Tests/WebformResultsExportOptionsTest.php +++ b/web/modules/webform/src/Tests/WebformResultsExportOptionsTest.php @@ -25,20 +25,16 @@ class WebformResultsExportOptionsTest extends WebformTestBase { */ protected static $testWebforms = ['test_submissions']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests export options. */ public function testExportOptions() { + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + /**************************************************************************/ + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_submissions'); /** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */ @@ -46,7 +42,7 @@ public function testExportOptions() { /** @var \Drupal\node\NodeInterface[] $node */ $nodes = array_values(\Drupal::entityTypeManager()->getStorage('node')->loadByProperties(['type' => 'webform_test_submissions'])); - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); // Check default options. $this->getExport($webform); @@ -131,11 +127,11 @@ public function testExportOptions() { // Check composite w/o header prefix. $this->getExport($webform, ['header_format' => 'label', 'header_prefix' => TRUE]); - $this->assertRaw('"Address: Address","Address: Address 2","Address: City/Town","Address: State/Province","Address: Zip/Postal Code","Address: Country"'); + $this->assertRaw('"Address: Address","Address: Address 2","Address: City/Town","Address: State/Province","Address: ZIP/Postal Code","Address: Country"'); // Check composite w header prefix. $this->getExport($webform, ['header_format' => 'label', 'header_prefix' => FALSE]); - $this->assertRaw('Address,"Address 2",City/Town,State/Province,"Zip/Postal Code",Country'); + $this->assertRaw('Address,"Address 2",City/Town,State/Province,"ZIP/Postal Code",Country'); // Check limit. $this->getExport($webform, ['range_type' => 'latest', 'range_latest' => 2]); @@ -170,7 +166,7 @@ public function testExportOptions() { $this->assertNoRaw('Hillary,Clinton'); // Check entity type and id hidden. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/download'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/download'); $this->assertNoFieldById('edit-entity-type'); // Change submission 0 & 1 to be submitted user account. @@ -178,7 +174,7 @@ public function testExportOptions() { $submissions[1]->set('entity_type', 'user')->set('entity_id', '2')->save(); // Check entity type and id visible. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/download'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/download'); $this->assertFieldById('edit-entity-type'); // Check entity type limit. @@ -193,8 +189,9 @@ public function testExportOptions() { $this->assertNoRaw('Abraham,Lincoln'); $this->assertNoRaw('Hillary,Clinton'); - // Check changing default exporter to 'table' settings. $this->drupalLogin($this->rootUser); + + // Check changing default exporter to 'table' settings. $edit = [ 'exporter' => 'table', ]; @@ -203,7 +200,6 @@ public function testExportOptions() { $this->assertPattern('#<td>George</td>\s+<td>Washington</td>\s+<td>Male</td>#ms'); // Check changing default export (delimiter) settings. - $this->drupalLogin($this->rootUser); $edit = [ 'exporter' => 'delimited', 'exporters[delimited][delimiter]' => '|', diff --git a/web/modules/webform/src/Tests/WebformSubmissionAccessTest.php b/web/modules/webform/src/Tests/WebformSubmissionAccessTest.php deleted file mode 100644 index 49fe4ca448..0000000000 --- a/web/modules/webform/src/Tests/WebformSubmissionAccessTest.php +++ /dev/null @@ -1,146 +0,0 @@ -<?php - -namespace Drupal\webform\Tests; - -use Drupal\webform\Entity\Webform; - -/** - * Tests for webform submission access. - * - * @group Webform - */ -class WebformSubmissionAccessTest extends WebformTestBase { - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - - /** - * Test webform submission access permissions. - */ - public function testPermissions() { - global $base_path; - - $webform_id = 'contact'; - $webform = Webform::load('contact'); - - /**************************************************************************/ - // Own submission permissions (authenticated). - /**************************************************************************/ - - $this->drupalLogin($this->ownWebformSubmissionUser); - - $edit = ['subject' => '{subject}', 'message' => '{message}']; - $sid_1 = $this->postSubmission($webform, $edit); - - // Check view own previous submission message. - $this->drupalGet('webform/' . $webform->id()); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions/{$sid_1}\">View your previous submission</a>."); - - // Check 'view own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}"); - $this->assertResponse(200); - - // Check 'edit own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}/edit"); - $this->assertResponse(200); - - // Check 'delete own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}/delete"); - $this->assertResponse(200); - - $sid_2 = $this->postSubmission($webform, $edit); - - // Check view own previous submissions message. - $this->drupalGet('webform/' . $webform->id()); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions\">View your previous submissions</a>"); - - // Check view own previous submissions. - $this->drupalGet("webform/{$webform_id}/submissions"); - $this->assertResponse(200); - $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_1}"); - $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_2}"); - - // Check webform submission allowed. - $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); - $this->assertResponse(200); - - // Check webform results access denied. - $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); - $this->assertResponse(403); - - // Check all results access denied. - $this->drupalGet('/admin/structure/webform/submissions/manage'); - $this->assertResponse(403); - - /**************************************************************************/ - // Any submission permissions. - /**************************************************************************/ - - // Login as any user. - $this->drupalLogin($this->anyWebformSubmissionUser); - - // Check webform results access allowed. - $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/results/submissions"); - $this->assertResponse(200); - $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); - $this->assertLinkByHref("{$base_path}admin/structure/webform/manage/{$webform_id}/submission/{$sid_2}"); - - // Check webform submission access allowed. - $this->drupalGet("/admin/structure/webform/manage/{$webform_id}/submission/{$sid_1}"); - $this->assertResponse(200); - - // Check all results access allowed. - $this->drupalGet('/admin/structure/webform/submissions/manage'); - $this->assertResponse(200); - - /**************************************************************************/ - // Own submission permissions (anonymous). - /**************************************************************************/ - - $this->addWebformSubmissionOwnPermissionsToAnonymous(); - $this->drupalLogout(); - - $edit = ['name' => '{name}', 'email' => 'example@example.com', 'subject' => '{subject}', 'message' => '{message}']; - $sid_1 = $this->postSubmission($webform, $edit); - - // Check view own previous submission message. - $this->drupalGet('webform/' . $webform->id()); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions/{$sid_1}\">View your previous submission</a>."); - - // Check 'view own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}"); - $this->assertResponse(200); - - // Check 'edit own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}/edit"); - $this->assertResponse(200); - - // Check 'delete own submission' permission. - $this->drupalGet("webform/{$webform_id}/submissions/{$sid_1}/delete"); - $this->assertResponse(200); - - $sid_2 = $this->postSubmission($webform, $edit); - - // Check view own previous submissions message. - $this->drupalGet('webform/' . $webform->id()); - $this->assertRaw('You have already submitted this webform.'); - $this->assertRaw("<a href=\"{$base_path}webform/{$webform_id}/submissions\">View your previous submissions</a>"); - - // Check view own previous submissions. - $this->drupalGet("webform/{$webform_id}/submissions"); - $this->assertResponse(200); - $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_1}"); - $this->assertLinkByHref("{$base_path}webform/{$webform_id}/submissions/{$sid_2}"); - } - -} diff --git a/web/modules/webform/src/Tests/WebformSubmissionApiTest.php b/web/modules/webform/src/Tests/WebformSubmissionApiTest.php index 34813c56db..deb81212cc 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionApiTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionApiTest.php @@ -21,27 +21,18 @@ class WebformSubmissionApiTest extends WebformTestBase { */ protected static $testWebforms = ['test_form_wizard_advanced', 'test_form_limit']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test webform API. */ public function testApi() { + $normal_user = $this->drupalCreateUser(); + + $contact_webform = Webform::load('contact'); /**************************************************************************/ // Basic form. /**************************************************************************/ - $contact_webform = Webform::load('contact'); - // Check submitting a simple webform. $values = [ 'webform_id' => 'contact', @@ -104,7 +95,7 @@ public function testApi() { $test_form_wizard_advanced_webform = Webform::load('test_form_wizard_advanced'); - // Check submitting a multistep form with required fields. + // Check submitting a multi-step form with required fields. $values = [ 'webform_id' => 'test_form_wizard_advanced', 'data' => [ @@ -119,7 +110,7 @@ public function testApi() { $webform_submission = WebformSubmissionForm::submitFormValues($values); $this->assertEqual($webform_submission->id(), $this->getLastSubmissionId($test_form_wizard_advanced_webform)); - // Check validating a multistep form with required fields. + // Check validating a multi-step form with required fields. $values = [ 'webform_id' => 'test_form_wizard_advanced', 'data' => [ @@ -133,7 +124,7 @@ public function testApi() { 'email' => 'The email address <em class="placeholder">invalid</em> is not valid.', ]); - // Check validating a multistep form with invalid #options. + // Check validating a multi-step form with invalid #options. $values = [ 'webform_id' => 'test_form_wizard_advanced', 'data' => [ @@ -149,14 +140,14 @@ public function testApi() { WebformElementHelper::convertRenderMarkupToStrings($errors); // $this->debug($errors); $this->assertEqual($errors, [ - 'gender' => 'An illegal choice has been detected. Please contact the site administrator.', + 'gender' => 'An illegal choice has been detected. Please contact the site administrator.', ]); /**************************************************************************/ // Submission limit form. /**************************************************************************/ - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $test_form_limit_webform = Webform::load('test_form_limit'); @@ -190,7 +181,7 @@ public function testApi() { // Check form closed message. $test_form_limit_webform->setStatus(FALSE)->save(); $result = WebformSubmissionForm::isOpen($test_form_limit_webform); - $this->assertEqual($result['#markup'], 'Sorry...This form is closed to new submissions.'); + $this->assertEqual($result['#markup'], 'Sorry…This form is closed to new submissions.'); } } diff --git a/web/modules/webform/src/Tests/WebformSubmissionGenerateTest.php b/web/modules/webform/src/Tests/WebformSubmissionGenerateTest.php index 5bac11bd03..809b305c46 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionGenerateTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionGenerateTest.php @@ -37,7 +37,7 @@ public function testWebformSubmissionGenerate() { $this->assertEqual($data['subject'], $test_data['subject']); // Check test form classes and values. - $this->drupalGet('webform/contact/test'); + $this->drupalGet('/webform/contact/test'); $this->assertCssSelect('.webform-submission-form.webform-submission-test-form.webform-submission-contact-form.webform-submission-contact-test-form'); foreach ($test_data as $name => $value) { $this->assertFieldByName($name, $value); @@ -48,14 +48,14 @@ public function testWebformSubmissionGenerate() { /**************************************************************************/ // Check add form classes and empty values. - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertCssSelect('.webform-submission-form.webform-submission-add-form.webform-submission-contact-form.webform-submission-contact-add-form'); foreach ($test_data as $name => $value) { $this->assertNoFieldByName($name, $value); } // Check add form classes and values with querystring parameter. - $this->drupalGet('webform/contact', ['query' => ['_webform_test' => 'contact']]); + $this->drupalGet('/webform/contact', ['query' => ['_webform_test' => 'contact']]); $this->assertCssSelect('.webform-submission-form.webform-submission-test-form.webform-submission-contact-form.webform-submission-contact-test-form'); foreach ($test_data as $name => $value) { $this->assertFieldByName($name, $value); diff --git a/web/modules/webform/src/Tests/WebformSubmissionListBuilderTest.php b/web/modules/webform/src/Tests/WebformSubmissionListBuilderTest.php index e77df1fa07..200a6100bf 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionListBuilderTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionListBuilderTest.php @@ -25,29 +25,37 @@ class WebformSubmissionListBuilderTest extends WebformTestBase { */ protected static $testWebforms = ['test_submissions']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Tests results. */ public function testResults() { global $base_path; + $admin_user = $this->drupalCreateUser([ + 'administer webform', + ]); + + $own_submission_user = $this->drupalCreateUser([ + 'view own webform submission', + 'edit own webform submission', + 'delete own webform submission', + 'access webform submission user', + ]); + + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_submissions'); + /** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */ $submissions = array_values(\Drupal::entityTypeManager()->getStorage('webform_submission')->loadByProperties(['webform_id' => 'test_submissions'])); - // Login the normal user. - $this->drupalLogin($this->ownWebformSubmissionUser); + /**************************************************************************/ + + // Login the own submission user. + $this->drupalLogin($own_submission_user); // Make the second submission to be starred (aka sticky). $submissions[1]->setSticky(TRUE)->save(); @@ -55,11 +63,11 @@ public function testResults() { // Make the third submission to be locked. $submissions[2]->setLocked(TRUE)->save(); - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); /* Filter */ - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); // Check state options with totals. $this->assertRaw('<select data-drupal-selector="edit-state" id="edit-state" name="state" class="form-select"><option value="" selected="selected">All [4]</option><option value="starred">Starred [1]</option><option value="unstarred">Unstarred [3]</option><option value="locked">Locked [1]</option><option value="unlocked">Unlocked [3]</option></select>'); @@ -73,9 +81,16 @@ public function testResults() { $this->assertRaw($submissions[2]->getElementData('first_name')); $this->assertNoFieldById('edit-reset', 'reset'); + // Check results filtered by uuid. + $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/submissions', ['search' => $submissions[0]->get('uuid')->value], t('Filter')); + $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?search=' . $submissions[0]->get('uuid')->value); + $this->assertRaw($submissions[0]->getElementData('first_name')); + $this->assertNoRaw($submissions[1]->getElementData('first_name')); + $this->assertNoRaw($submissions[2]->getElementData('first_name')); + // Check results filtered by key(word). $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/submissions', ['search' => $submissions[0]->getElementData('first_name')], t('Filter')); - $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?search=' . $submissions[0]->getElementData('first_name') . '&state='); + $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?search=' . $submissions[0]->getElementData('first_name')); $this->assertRaw($submissions[0]->getElementData('first_name')); $this->assertNoRaw($submissions[1]->getElementData('first_name')); $this->assertNoRaw($submissions[2]->getElementData('first_name')); @@ -83,7 +98,7 @@ public function testResults() { // Check results filtered by state:starred. $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/submissions', ['state' => 'starred'], t('Filter')); - $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?search=&state=starred'); + $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?state=starred'); $this->assertRaw('<option value="starred" selected="selected">Starred [1]</option>'); $this->assertNoRaw($submissions[0]->getElementData('first_name')); $this->assertRaw($submissions[1]->getElementData('first_name')); @@ -92,7 +107,7 @@ public function testResults() { // Check results filtered by state:starred. $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/submissions', ['state' => 'locked'], t('Filter')); - $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?search=&state=locked'); + $this->assertUrl('admin/structure/webform/manage/' . $webform->id() . '/results/submissions?state=locked'); $this->assertRaw('<option value="locked" selected="selected">Locked [1]</option>'); $this->assertNoRaw($submissions[0]->getElementData('first_name')); $this->assertNoRaw($submissions[1]->getElementData('first_name')); @@ -104,17 +119,17 @@ public function testResults() { /**************************************************************************/ // Check that access is denied to custom results table. - $this->drupalLogin($this->adminSubmissionUser); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); + $this->drupalLogin($admin_submission_user); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); $this->assertResponse(403); // Check that access is allowed to custom results table. - $this->drupalLogin($this->adminWebformUser); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); + $this->drupalLogin($admin_user); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom'); $this->assertResponse(200); // Check that created is visible and changed is hidden. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); $this->assertRaw('sort by Created'); $this->assertNoRaw('sort by Changed'); @@ -124,12 +139,17 @@ public function testResults() { // Check that no pager is being displayed. $this->assertNoRaw('<nav class="pager" role="navigation" aria-labelledby="pagination-heading">'); - // Check that table is sorted by serial. - $this->assertRaw('<th specifier="serial" aria-sort="descending" class="is-active">'); + // Check that table is sorted by created. + $this->assertRaw('<th specifier="created" class="priority-medium is-active" aria-sort="descending">'); // Check the table results order by sid. $this->assertPattern('#Hillary.+Abraham.+George#ms'); + // Check the table links to canonical view. + $this->assertRaw('data-webform-href="' . $submissions[0]->toUrl()->toString() . '"'); + $this->assertRaw('data-webform-href="' . $submissions[1]->toUrl()->toString() . '"'); + $this->assertRaw('data-webform-href="' . $submissions[2]->toUrl()->toString() . '"'); + // Customize to results table. $edit = [ 'columns[created][checkbox]' => FALSE, @@ -139,12 +159,19 @@ public function testResults() { 'sort' => 'element__first_name', 'direction' => 'desc', 'limit' => 20, + 'link_type' => 'table', ]; $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/submissions/custom', $edit, t('Save')); $this->assertRaw('The customized table has been saved.'); + // Check that table now link to table. + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->assertRaw('data-webform-href="' . $submissions[0]->toUrl('table')->toString() . '"'); + $this->assertRaw('data-webform-href="' . $submissions[1]->toUrl('table')->toString() . '"'); + $this->assertRaw('data-webform-href="' . $submissions[2]->toUrl('table')->toString() . '"'); + // Check that sid is hidden and changed is visible. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); $this->assertNoRaw('sort by Created'); $this->assertRaw('sort by Changed'); @@ -158,7 +185,7 @@ public function testResults() { $webform->setState('results.custom.limit', 1); // Check that only one result (Hillary #2) is displayed with pager. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); $this->assertNoRaw('George'); $this->assertNoRaw('Abraham'); $this->assertNoRaw('Hillary'); @@ -169,7 +196,7 @@ public function testResults() { $webform->setState('results.custom.limit', 20); // Check Header label and element value display. - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); // Check user header and value. $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/manage/' . $webform->id() . '/results/submissions?sort=asc&order=User" title="sort by User">User</a>'); @@ -185,7 +212,7 @@ public function testResults() { 'element_format' => 'raw', ]); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); // Check user header and value. $this->assertRaw('<a href="' . $base_path . 'admin/structure/webform/manage/' . $webform->id() . '/results/submissions?sort=asc&order=uid" title="sort by uid">uid</a>'); @@ -199,22 +226,22 @@ public function testResults() { // Customize user results. /**************************************************************************/ - $this->drupalLogin($this->ownWebformSubmissionUser); + $this->drupalLogin($own_submission_user); // Check view own submissions. $this->drupalget('/webform/' . $webform->id() . '/submissions'); - $this->assertRaw('<th specifier="serial" aria-sort="descending" class="is-active">'); - $this->assertRaw('<th specifier="created" class="priority-medium">'); + $this->assertRaw('<th specifier="serial">'); + $this->assertRaw('<th specifier="created" class="priority-medium is-active" aria-sort="descending">'); $this->assertRaw('<th specifier="remote_addr" class="priority-low">'); - // Display on first name and last name columns. + // Display only first name and last name columns. $webform->setSetting('submission_user_columns', ['element__first_name', 'element__last_name']) ->save(); // Check view own submissions only include first name and last name. $this->drupalget('/webform/' . $webform->id() . '/submissions'); - $this->assertNoRaw('<th specifier="serial" aria-sort="descending" class="is-active">'); - $this->assertNoRaw('<th specifier="created" class="priority-medium">'); + $this->assertNoRaw('<th specifier="serial">'); + $this->assertNoRaw('<th specifier="created" class="priority-medium is-active" aria-sort="descending">'); $this->assertNoRaw('<th specifier="remote_addr" class="priority-low">'); $this->assertRaw('<th specifier="element__first_name" aria-sort="ascending" class="is-active">'); $this->assertRaw('<th specifier="element__last_name">'); diff --git a/web/modules/webform/src/Tests/WebformSubmissionStorageTest.php b/web/modules/webform/src/Tests/WebformSubmissionStorageTest.php index d81e3b9b42..98ac360b35 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionStorageTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionStorageTest.php @@ -59,15 +59,19 @@ public function testSubmissionStorage() { $this->assertEqual($storage->getTotal($webform, NULL, $user2), 3); // Check next submission. + $this->drupalLogin($user1); $this->assertEqual($storage->getNextSubmission($user1_submissions[0], NULL, $user1)->id(), $user1_submissions[1]->id(), "User 1 can navigate forward to user 1's next submission"); $this->assertNull($storage->getNextSubmission($user1_submissions[2], NULL, $user1), "User 1 can't navigate forward to user 2's next submission"); + $this->drupalLogin($user2); $this->assertNull($storage->getNextSubmission($user2_submissions[0], NULL, $user2), "User 2 can't navigate forward to user 2's next submission because of missing 'view own webform submission' permission"); $this->drupalLogin($admin_user); $this->assertEqual($storage->getNextSubmission($user1_submissions[2], NULL)->id(), $user2_submissions[0]->id(), "Admin user can navigate between user submissions"); $this->drupalLogout(); // Check previous submission. + $this->drupalLogin($user1); $this->assertEqual($storage->getPreviousSubmission($user1_submissions[1], NULL, $user1)->id(), $user1_submissions[0]->id(), "User 1 can navigate backward to user 1's previous submission"); + $this->drupalLogin($user2); $this->assertNull($storage->getPreviousSubmission($user2_submissions[0], NULL, $user2), "User 2 can't navigate backward to user 1's previous submission"); $this->drupalLogin($admin_user); $this->assertEqual($storage->getPreviousSubmission($user2_submissions[0], NULL)->id(), $user1_submissions[2]->id(), "Admin user can navigate between user submissions"); diff --git a/web/modules/webform/src/Tests/WebformSubmissionTest.php b/web/modules/webform/src/Tests/WebformSubmissionTest.php index 536dbe3a75..192fa4450f 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionTest.php @@ -30,37 +30,41 @@ class WebformSubmissionTest extends WebformTestBase { * Tests webform submission entity. */ public function testWebformSubmission() { + $normal_user = $this->drupalCreateUser(); + /** @var \Drupal\webform\WebformInterface $webform */ $webform = Webform::load('test_submissions'); + /** @var \Drupal\webform\WebformSubmissionInterface[] $submissions */ $submissions = array_values(\Drupal::entityTypeManager()->getStorage('webform_submission')->loadByProperties(['webform_id' => 'test_submissions'])); /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = reset($submissions); + /**************************************************************************/ + // Check create submission. $this->assert($webform_submission instanceof WebformSubmission, '$webform_submission instanceof WebformSubmission'); // Check get webform. $this->assertEqual($webform_submission->getWebform()->id(), $webform->id()); - // Check that YAML source entity is NULL. + // Check that source entity is NULL. $this->assertNull($webform_submission->getSourceEntity()); - // Check get YAML source URL without uri, which will still return + // Check getting source URL without uri, which will still return // the webform. $webform_submission ->set('uri', NULL) ->save(); $this->assertEqual($webform_submission->getSourceUrl()->toString(), $webform->toUrl('canonical', ['absolute' => TRUE])->toString()); - // Check get YAML source URL set to user 1. - $this->createUsers(); + // Check get source URL set to user 1. $webform_submission ->set('entity_type', 'user') - ->set('entity_id', $this->normalUser->id()) + ->set('entity_id', $normal_user->id()) ->save(); - $this->assertEqual($webform_submission->getSourceUrl()->toString(), $this->normalUser->toUrl('canonical', ['absolute' => TRUE])->toString()); + $this->assertEqual($webform_submission->getSourceUrl()->toString(), $normal_user->toUrl('canonical', ['absolute' => TRUE])->toString()); // Check missing webform_id exception. try { @@ -78,14 +82,27 @@ public function testWebformSubmission() { // Check submission label. $webform_submission->save(); $this->assertEqual($webform_submission->label(), $webform->label() . ': Submission #' . $webform_submission->serial()); + + // Check test submission URI. + // @see \Drupal\webform\WebformSubmissionForm::save + $this->drupalLogin($this->rootUser); + $sid = $this->postSubmissionTest($webform); + $webform_submission = WebformSubmission::load($sid); + $this->assertEqual($webform_submission->getSourceUrl()->toString(), $webform->toUrl('canonical', ['absolute' => TRUE])->toString()); + $this->drupalLogout(); } /** * Tests duplicating webform submission. */ public function testDuplicate() { - $this->createUsers(); - $this->drupalLogin($this->adminSubmissionUser); + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + /**************************************************************************/ + + $this->drupalLogin($admin_submission_user); $webform = Webform::load('contact'); $sid = $this->postSubmission($webform, [ diff --git a/web/modules/webform/src/Tests/WebformSubmissionTokenUpdateTest.php b/web/modules/webform/src/Tests/WebformSubmissionTokenUpdateTest.php index 070fa5d91c..6f904baca3 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionTokenUpdateTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionTokenUpdateTest.php @@ -19,29 +19,23 @@ class WebformSubmissionTokenUpdateTest extends WebformTestBase { */ protected static $testWebforms = ['test_token_update']; - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create users. - $this->createUsers(); - } - /** * Test updating webform submission using tokenized URL. */ public function testTokenUpdateTest() { + $normal_user = $this->drupalCreateUser(); + $webform = Webform::load('test_token_update'); + /**************************************************************************/ + // Post test submission. $this->drupalLogin($this->rootUser); $sid = $this->postSubmissionTest($webform); $webform_submission = WebformSubmission::load($sid); // Check token update access allowed. - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $this->drupalGet($webform_submission->getTokenUrl()); $this->assertResponse(200); $this->assertRaw('Submission information'); @@ -49,7 +43,7 @@ public function testTokenUpdateTest() { // Check token update access denied. $webform->setSetting('token_update', FALSE)->save(); - $this->drupalLogin($this->normalUser); + $this->drupalLogin($normal_user); $this->drupalGet($webform_submission->getTokenUrl()); $this->assertResponse(200); $this->assertNoRaw('Submission information'); @@ -66,7 +60,7 @@ public function testTokenUpdateTest() { $webform->save(); // Check that access is denied for anonymous user. - $this->drupalGet('webform/test_token_update'); + $this->drupalGet('/webform/test_token_update'); $this->assertResponse(403); // Check token update access allowed for anonymous user. diff --git a/web/modules/webform/src/Tests/WebformSubmissionViewTest.php b/web/modules/webform/src/Tests/WebformSubmissionViewTest.php index 3a8f61b2fb..0211b516a7 100644 --- a/web/modules/webform/src/Tests/WebformSubmissionViewTest.php +++ b/web/modules/webform/src/Tests/WebformSubmissionViewTest.php @@ -34,9 +34,6 @@ class WebformSubmissionViewTest extends WebformTestBase { public function setUp() { parent::setUp(); - // Create users. - $this->createUsers(); - // Create filters. $this->createFilters(); } @@ -45,21 +42,28 @@ public function setUp() { * Tests view submissions. */ public function testView() { + $admin_submission_user = $this->drupalCreateUser([ + 'administer webform submission', + ]); + + /**************************************************************************/ + $account = User::load(1); $webform_element = Webform::load('test_element'); $sid = $this->postSubmission($webform_element); $submission = WebformSubmission::load($sid); - $this->drupalLogin($this->adminSubmissionUser); + $this->drupalLogin($admin_submission_user); - $this->drupalGet('admin/structure/webform/manage/test_element/submission/' . $submission->id()); + $this->drupalGet('/admin/structure/webform/manage/test_element/submission/' . $submission->id()); // Check displayed values. $elements = [ 'hidden' => '{hidden}', 'value' => '{value}', 'textarea' => "{textarea line 1}<br />\n{textarea line 2}", + 'empty' => '{Empty}', 'textfield' => '{textfield}', 'select' => 'one', 'select_multiple' => 'one, two', diff --git a/web/modules/webform/src/Tests/WebformSubmissionViewsTest.php b/web/modules/webform/src/Tests/WebformSubmissionViewsTest.php new file mode 100644 index 0000000000..1b5911e5ea --- /dev/null +++ b/web/modules/webform/src/Tests/WebformSubmissionViewsTest.php @@ -0,0 +1,182 @@ +<?php + +namespace Drupal\webform\Tests; + +use Drupal\webform\Entity\Webform; + +/** + * Tests for webform submission views integration. + * + * @group Webform + */ +class WebformSubmissionViewsTest extends WebformTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['views', 'webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_submission_views']; + + /** + * Tests submissions views. + */ + public function testSubmissionViewsAccess() { + // Check administer view. + $user = $this->drupalCreateUser(['administer webform submission']); + $this->drupalLogin($user); + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_administer'); + + // Check 200 response. + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions/admin'); + $this->assertResponse(200); + + // Check manage view. + $user = $this->drupalCreateUser(['edit any webform submission', 'view any webform submission']); + $this->drupalLogin($user); + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_manage'); + + // Check 403 access denied response. + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions/admin'); + $this->assertResponse(403); + + // Check 404 not found response. + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions/not_found'); + $this->assertResponse(404); + + // Check review view. + $user = $this->drupalCreateUser(['view any webform submission']); + $this->drupalLogin($user); + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_review'); + } + + /** + * Tests submissions views. + */ + public function testSubmissionViews() { + $uid = $this->rootUser->id(); + $this->drupalLogin($this->rootUser); + + /**************************************************************************/ + // Global. + /**************************************************************************/ + + // Setup global submissions and user submissions views. + \Drupal::configFactory()->getEditable('webform.settings') + ->set('settings.default_submission_views', [ + 'global' => [ + 'view' => 'webform_submissions:embed_default', + 'title' => 'Global submissions', + 'global_routes' => ['entity.webform_submission.collection'], + 'webform_routes' => [], + 'node_routes' => [], + ], + 'user' => [ + 'view' => 'webform_submissions:embed_default', + 'title' => 'User submissions', + 'global_routes' => ['entity.webform_submission.user'], + 'webform_routes' => [], + 'node_routes' => [], + ], + ]) + ->save(); + + // Check global submissions entity list is replaced by the view. + $this->drupalGet('/admin/structure/webform/submissions/manage'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Check user submissions entity list is replaced by the view. + $this->drupalGet("/user/$uid/submissions"); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Clear global submission views replace. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('settings.default_submission_views_replace.global_routes', []) + ->save(); + + // Check global submissions entity list is displayed. + $this->drupalGet('/admin/structure/webform/submissions/manage'); + $this->assertRaw('webform-results-table'); + $this->assertNoRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Check global submissions views is moved to dedicated path. + $this->clickLink('Global submissions'); + $this->assertUrl('/admin/structure/webform/submissions/manage/global'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Check user submissions entity list is displayed. + $this->drupalGet("/user/$uid/submissions"); + $this->assertRaw('webform-results-table'); + $this->assertNoRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Check user submissions entity list is moved to dedicated path. + $this->clickLink('User submissions'); + $this->assertUrl("/user/$uid/submissions/user"); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + + /**************************************************************************/ + // Webform. + /**************************************************************************/ + + // Post a submission and save a draft. + $webform = Webform::load('test_submission_views'); + $this->postSubmission($webform); + $this->postSubmission($webform); + $this->postSubmission($webform, [], t('Save Draft')); + $this->postSubmission($webform, [], t('Save Draft')); + + // Check webform submissions views. + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_administer'); + + // Check webform user views. + $this->drupalGet('/webform/test_submission_views/drafts'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Clear global webform views replace. + \Drupal::configFactory() + ->getEditable('webform.settings') + ->set('settings.default_submission_views_replace.webform_routes', []) + ->save(); + + // Check webform submissions entity list is displayed. + $this->drupalGet('/admin/structure/webform/manage/test_submission_views/results/submissions'); + $this->assertRaw('webform-results-table'); + $this->assertNoRaw('view-id-webform_submissions view-display-id-embed_administer'); + + // Check webform submissions views is moved to dedicated path. + $this->clickLink('Administer submissions'); + $this->assertUrl('/admin/structure/webform/manage/test_submission_views/results/submissions/admin'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_administer'); + + // Check webform submissions entity list is displayed. + $this->drupalGet('/webform/test_submission_views/drafts'); + $this->assertRaw('webform-results-table'); + $this->assertNoRaw('view-id-webform_submissions view-display-id-embed_default'); + + // Check webform submissions entity list is moved to dedicated path. + $this->clickLink('User submissions'); + $this->assertUrl('/webform/test_submission_views/drafts/user'); + $this->assertNoRaw('webform-results-table'); + $this->assertRaw('view-id-webform_submissions view-display-id-embed_default'); + } + +} diff --git a/web/modules/webform/src/Tests/WebformTestBase.php b/web/modules/webform/src/Tests/WebformTestBase.php index 55713b1298..5dc15cd6e8 100644 --- a/web/modules/webform/src/Tests/WebformTestBase.php +++ b/web/modules/webform/src/Tests/WebformTestBase.php @@ -10,7 +10,6 @@ use Drupal\simpletest\WebTestBase; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; -use Drupal\user\Entity\Role; use Drupal\webform\WebformInterface; use Drupal\webform\Entity\Webform; @@ -51,146 +50,6 @@ public function tearDown() { parent::tearDown(); } - /****************************************************************************/ - // User. - /****************************************************************************/ - - /** - * A normal user to submit webforms. - * - * @var \Drupal\user\UserInterface - */ - protected $normalUser; - - /** - * A webform administrator. - * - * @var \Drupal\user\UserInterface - */ - protected $adminWebformUser; - - /** - * A webform submission administrator. - * - * @var \Drupal\user\UserInterface - */ - protected $adminSubmissionUser; - - /** - * A webform own access. - * - * @var \Drupal\user\UserInterface - */ - protected $ownWebformUser; - - /** - * A webform any access. - * - * @var \Drupal\user\UserInterface - */ - protected $anyWebformUser; - - /** - * A webform submission own access. - * - * @var \Drupal\user\UserInterface - */ - protected $ownWebformSubmissionUser; - - /** - * A webform submission any access. - * - * @var \Drupal\user\UserInterface - */ - protected $anyWebformSubmissionUser; - - /** - * Create webform test users. - */ - protected function createUsers() { - // Default user permissions. - $default_user_permissions = []; - $default_user_permissions[] = 'access user profiles'; - if (in_array('webform_node', static::$modules)) { - $default_user_permissions[] = 'access content'; - } - - // Normal user. - $normal_user_permissions = $default_user_permissions; - $this->normalUser = $this->drupalCreateUser($normal_user_permissions); - - // Admin webform user. - $admin_form_user_permissions = array_merge($default_user_permissions, [ - 'access site reports', - 'administer site configuration', - 'administer webform', - 'access webform submission log', - 'create webform', - 'administer users', - ]); - if (in_array('block', static::$modules)) { - $admin_form_user_permissions[] = 'administer blocks'; - } - if (in_array('webform_node', static::$modules)) { - $admin_form_user_permissions[] = 'administer nodes'; - } - if (in_array('webform_test_translation', static::$modules)) { - $admin_form_user_permissions[] = 'translate configuration'; - } - $this->adminWebformUser = $this->drupalCreateUser($admin_form_user_permissions); - - // Own webform user. - $this->ownWebformUser = $this->drupalCreateUser(array_merge($default_user_permissions, [ - 'access webform overview', - 'create webform', - 'edit own webform', - 'delete own webform', - 'view own webform submission', - 'edit own webform submission', - 'delete own webform submission', - ])); - - // Any webform user. - $this->anyWebformUser = $this->drupalCreateUser(array_merge($default_user_permissions, [ - 'access webform overview', - 'create webform', - 'edit any webform', - 'delete any webform', - ])); - - // Own webform submission user. - $this->ownWebformSubmissionUser = $this->drupalCreateUser(array_merge($default_user_permissions, [ - 'view own webform submission', - 'edit own webform submission', - 'delete own webform submission', - ])); - - // Any webform submission user. - $this->anyWebformSubmissionUser = $this->drupalCreateUser(array_merge($default_user_permissions, [ - 'view any webform submission', - 'edit any webform submission', - 'delete any webform submission', - ])); - - // Admin submission user. - $this->adminSubmissionUser = $this->drupalCreateUser(array_merge($default_user_permissions, [ - 'access webform submission log', - 'administer webform submission', - ])); - } - - /** - * Add webform submission own permissions to anonymous role. - */ - protected function addWebformSubmissionOwnPermissionsToAnonymous() { - /** @var \Drupal\user\RoleInterface $anonymous_role */ - $anonymous_role = Role::load('anonymous'); - $anonymous_role->grantPermission('view own webform submission') - ->grantPermission('edit own webform submission') - ->grantPermission('delete own webform submission') - ->save(); - } - /****************************************************************************/ // Block. /****************************************************************************/ @@ -365,77 +224,18 @@ protected function postSubmission(WebformInterface $webform, array $edit = [], $ * Submission values. * @param string $submit * Value of the submit button whose click is to be emulated. - * @param $options + * @param array $options * Options to be forwarded to the url generator. * * @return int * The created test submission's sid. */ - protected function postSubmissionTest(WebformInterface $webform, array $edit = [], $submit = NULL, $options = []) { + protected function postSubmissionTest(WebformInterface $webform, array $edit = [], $submit = NULL, array $options = []) { $submit = $this->getWebformSubmitButtonLabel($webform, $submit); $this->drupalPostForm('webform/' . $webform->id() . '/test', $edit, $submit, $options); return $this->getLastSubmissionId($webform); } - /****************************************************************************/ - // Log. - /****************************************************************************/ - - /** - * Get the last submission id. - * - * @return int - * The last submission id. - */ - protected function getLastSubmissionLog() { - $query = \Drupal::database()->select('webform_submission_log', 'l'); - $query->leftJoin('webform_submission', 'ws', 'l.sid = ws.sid'); - $query->fields('l', [ - 'lid', - 'uid', - 'sid', - 'handler_id', - 'operation', - 'message', - 'timestamp', - ]); - $query->fields('ws', [ - 'webform_id', - 'entity_type', - 'entity_id', - ]); - $query->orderBy('l.lid', 'DESC'); - $query->range(0, 1); - return $query->execute()->fetch(); - } - - /** - * Get the entire submission log. - * - * @return int - * The last submission id. - */ - protected function getSubmissionLog() { - $query = \Drupal::database()->select('webform_submission_log', 'l'); - $query->leftJoin('webform_submission', 'ws', 'l.sid = ws.sid'); - $query->fields('l', [ - 'lid', - 'uid', - 'sid', - 'handler_id', - 'operation', - 'message', - 'timestamp', - ]); - $query->fields('ws', [ - 'webform_id', - 'entity_type', - 'entity_id', - ]); - $query->orderBy('l.lid', 'DESC'); - return $query->execute()->fetchAll(); - } - /****************************************************************************/ // Export. /****************************************************************************/ @@ -452,7 +252,7 @@ protected function getExport(WebformInterface $webform, array $options = []) { /** @var \Drupal\webform\WebformSubmissionExporterInterface $exporter */ $exporter = \Drupal::service('webform_submission.exporter'); $options += $exporter->getDefaultExportOptions(); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/download', ['query' => $options]); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/download', ['query' => $options]); } /** @@ -499,7 +299,7 @@ protected function getLastEmail() { /** * Passes if the substring is contained within text, fails otherwise. */ - protected function assertContains($haystack, $needle, $message = '', $group = 'Other') { + protected function assertContains($needle, $haystack, $message = '') { if (!$message) { $t_args = [ '@haystack' => Unicode::truncate($haystack, 150, TRUE, TRUE), @@ -511,13 +311,13 @@ protected function assertContains($haystack, $needle, $message = '', $group = 'O if (!$result) { $this->verbose($haystack); } - return $this->assert($result, $message, $group); + $this->assert($result, $message); } /** * Passes if the substring is not contained within text, fails otherwise. */ - protected function assertNotContains($haystack, $needle, $message = '', $group = 'Other') { + protected function assertNotContains($needle, $haystack, $message = '') { if (!$message) { $t_args = [ '@haystack' => Unicode::truncate($haystack, 150, TRUE, TRUE), @@ -530,7 +330,7 @@ protected function assertNotContains($haystack, $needle, $message = '', $group = if (!$result) { $this->verbose($haystack); } - return $this->assert($result, $message, $group); + $this->assert($result, $message); } /** diff --git a/web/modules/webform/src/Tests/WebformTestTrait.php b/web/modules/webform/src/Tests/WebformTestTrait.php index fed6e694f1..4697391a49 100644 --- a/web/modules/webform/src/Tests/WebformTestTrait.php +++ b/web/modules/webform/src/Tests/WebformTestTrait.php @@ -161,6 +161,7 @@ protected function getLastSubmissionId(WebformInterface $webform) { $entity_ids = \Drupal::entityQuery('webform_submission') ->sort('sid', 'DESC') ->condition('webform_id', $webform->id()) + ->accessCheck(FALSE) ->execute(); return reset($entity_ids); } diff --git a/web/modules/webform/src/Tests/WebformThirdPartySettingsTest.php b/web/modules/webform/src/Tests/WebformThirdPartySettingsTest.php index 0027551d1e..0c6d3a67c6 100644 --- a/web/modules/webform/src/Tests/WebformThirdPartySettingsTest.php +++ b/web/modules/webform/src/Tests/WebformThirdPartySettingsTest.php @@ -23,22 +23,22 @@ public function testThirdPartySettings() { $this->drupalLogin($this->rootUser); // Check 'Webform: Settings' shows no modules installed. - $this->drupalGet('admin/structure/webform/config'); + $this->drupalGet('/admin/structure/webform/config'); $this->assertRaw('There are no third party settings available.'); // Check 'Contact: Settings' does not show 'Third party settings'. - $this->drupalGet('admin/structure/webform/manage/contact/settings'); + $this->drupalGet('/admin/structure/webform/manage/contact/settings'); $this->assertNoRaw('Third party settings'); // Install test third party settings module. \Drupal::service('module_installer')->install(['webform_test_third_party_settings']); // Check 'Webform: Settings' shows no modules installed. - $this->drupalGet('admin/structure/webform/config'); + $this->drupalGet('/admin/structure/webform/config'); $this->assertNoRaw('There are no third party settings available.'); // Check 'Contact: Settings' shows 'Third party settings'. - $this->drupalGet('admin/structure/webform/manage/contact/settings'); + $this->drupalGet('/admin/structure/webform/manage/contact/settings'); $this->assertRaw('Third party settings'); // Check 'Webform: Settings' message. @@ -46,7 +46,7 @@ public function testThirdPartySettings() { 'third_party_settings[webform_test_third_party_settings][message]' => 'Message for all webforms', ]; $this->drupalPostForm('admin/structure/webform/config', $edit, t('Save configuration')); - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('Message for all webforms'); // Check that webform.settings.yml contain message. @@ -60,12 +60,12 @@ public function testThirdPartySettings() { 'third_party_settings[webform_test_third_party_settings][message]' => 'Message for only this webform', ]; $this->drupalPostForm('admin/structure/webform/manage/contact/settings', $edit, t('Save')); - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertRaw('Message for only this webform'); // Uninstall test third party settings module. \Drupal::service('module_installer')->uninstall(['webform_test_third_party_settings']); - $this->drupalGet('webform/contact'); + $this->drupalGet('/webform/contact'); $this->assertNoRaw('Message for only this webform'); // Check that webform.settings.yml no longer contains message or diff --git a/web/modules/webform/src/Tests/WebformTokenSubmissionValueTest.php b/web/modules/webform/src/Tests/WebformTokenSubmissionValueTest.php index dd9daa6096..51f55ef4dd 100644 --- a/web/modules/webform/src/Tests/WebformTokenSubmissionValueTest.php +++ b/web/modules/webform/src/Tests/WebformTokenSubmissionValueTest.php @@ -40,15 +40,12 @@ public function setUp() { */ public function testWebformTokenSubmissionValue() { $webform = Webform::load('test_token_submission_value'); - $this->postSubmission($webform); + // Check anonymous token handling. + $this->postSubmission($webform); $tokens = [ - // Emails. 'webform_submission:values:email' => 'example@example.com', - 'webform_submission:values:emails' => '- one@example.com -- two@example.com -- three@example.com', 'webform_submission:values:emails:0' => 'one@example.com', 'webform_submission:values:emails:1' => 'two@example.com', 'webform_submission:values:emails:2' => 'three@example.com', @@ -66,6 +63,10 @@ public function testWebformTokenSubmissionValue() { 'webform_submission:values:users:0:entity:account-name' => 'admin', 'webform_submission:values:users:99:entity:account-name' => '', + // Current users. + 'current-user:display-name' => '', + 'current-user:missing' => '', + // Terms. 'webform_submission:values:term' => 'Parent 1 (1)', 'webform_submission:values:terms' => 'Parent 1 (1), Parent 1: Child 1 (2)', @@ -104,6 +105,13 @@ public function testWebformTokenSubmissionValue() { 'webform_submission:values:contacts:0:email:html' => '<a href="mailto:john@example.com">john@example.com</a>', 'webform_submission:values:contacts:1:email:raw:html' => 'jane@example.com', + // Containers. + 'webform_submission:values:fieldset' => '<pre>fieldset +-------- +first_name: John +last_name: Smith +</pre>', + // Submission limits. 'webform_submission:limit:webform' => '100', 'webform_submission:total:webform' => '1', @@ -117,17 +125,42 @@ public function testWebformTokenSubmissionValue() { // Clear. 'webform_submission:values:missing' => '[webform_submission:values:missing]', 'webform_submission:values:missing:clear' => '', + 'webform:random:missing' => '[webform:random:missing]', + 'webform:random:missing:clear' => '', + + // HTML decode. + 'webform_submission:values:markup' => '<b>Bold</b> &amp; UPPERCASE', + 'webform_submission:values:markup:htmldecode' => '<b>Bold</b> & UPPERCASE', + 'webform_submission:values:markup:htmldecode:striptags' => 'Bold & UPPERCASE', + 'webform_submission:values:script' => '<script>alert('hi');</script>', + 'webform_submission:values:script:htmldecode' => 'alert('hi');', + + // URL encode. + 'webform_submission:values:url' => 'http://example.com?query=param', + 'webform_submission:values:url:urlencode' => 'http%3A%2F%2Fexample.com%3Fquery%3Dparam', ]; foreach ($tokens as $token => $value) { $this->assertRaw("<tr><th width=\"50%\">$token</th><td width=\"50%\">$value</td></tr>"); } - // Containers. + // Check containers. $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset</th><td width="50%"><pre>fieldset'); $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:html</th><td width="50%"><fieldset class="webform-container webform-container-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" id="test_token_submission_value--fieldset">'); - $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:header:html</th><td width="50%"><section id="test_token_submission_value--fieldset" class="js-form-item form-item js-form-wrapper form-wrapper webform-section">'); - $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:details:html</th><td width="50%"><details data-webform-element-id="test_token_submission_value--fieldset" class="webform-container webform-container-type-details js-form-wrapper form-wrapper" id="test_token_submission_value--fieldset" open="open">'); + $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:header:html</th><td width="50%"><section class="js-form-item form-item js-form-wrapper form-wrapper webform-section" id="test_token_submission_value--fieldset">'); + $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:details:html</th><td width="50%"><details class="webform-container webform-container-type-details js-form-wrapper form-wrapper" data-webform-element-id="test_token_submission_value--fieldset" id="test_token_submission_value--fieldset" open="open">'); $this->assertRaw('<tr><th width="50%">webform_submission:values:fieldset:fieldset:html</th><td width="50%"><fieldset class="webform-container webform-container-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" id="test_token_submission_value--fieldset">'); + + // Check authenticated token handling. + $this->drupalLogin($this->rootUser); + $this->postSubmission($webform); + $tokens = [ + // Current users. + 'current-user:display-name' => 'admin', + 'current-user:missing' => '', + ]; + foreach ($tokens as $token => $value) { + $this->assertRaw("<tr><th width=\"50%\">$token</th><td width=\"50%\">$value</td></tr>"); + } } } diff --git a/web/modules/webform/src/Tests/WebformTokenSuffixesTest.php b/web/modules/webform/src/Tests/WebformTokenSuffixesTest.php new file mode 100644 index 0000000000..16120a489c --- /dev/null +++ b/web/modules/webform/src/Tests/WebformTokenSuffixesTest.php @@ -0,0 +1,98 @@ +<?php + +namespace Drupal\webform\Tests; + +/** + * Tests for webform token suffixes. + * + * @group Webform + */ +class WebformTokenSuffixesTest extends WebformTestBase { + + /** + * Test webform token suffixes. + */ + public function testTokenSuffixes() { + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + + $tests = [ + // Default. + [ + 'site_name' => 'Testing', + 'text' => '[site:name]', + 'expected' => 'Testing', + 'message' => 'Basic token', + ], + // :clear. + [ + 'text' => '[missing]', + 'expected' => '[missing]', + 'message' => 'Missing token', + ], + [ + 'text' => '[missing:clear]', + 'expected' => '', + 'message' => 'Missing token cleared', + ], + [ + 'text' => '[missing:clear]', + 'expected' => '[missing:clear]', + 'message' => 'Clear disabled', + 'options' => ['suffixes' => ['clear' => FALSE]], + ], + // :htmldecode. + [ + 'site_name' => '<b>Testing</b>', + 'text' => '[site:name]', + 'expected' => '<b>Testing</b>', + 'message' => 'Basic token with encoded HTML markup', + ], + [ + 'site_name' => '<b>Testing</b>', + 'text' => '[site:name:htmldecode]', + 'expected' => '<b>Testing</b>', + 'message' => 'Basic token with decoded HTML markup', + ], + // :striptags. + [ + 'site_name' => '<b>Testing</b>', + 'text' => '[site:name:htmldecode:striptags]', + 'expected' => 'Testing', + 'message' => 'Basic token with decoded HTML markup', + 'options' => [], + ], + // :xmlencode. + [ + 'site_name' => '<b>Testing</b>', + 'text' => '[site:name:xmlencode]', + 'expected' => '&lt;b&gt;Testing&lt;/b&gt;', + 'message' => 'XML encode', + ], + [ + 'site_name' => '<b>Testing</b>', + 'text' => '[site:name:htmldecode:xmlencode]', + 'expected' => '<b>Testing</b>', + 'message' => 'HTML decode and then XML encode', + ], + ]; + foreach ($tests as $test) { + // Set default options. + $test += [ + 'options' => [], + ]; + + // Set site name. + if (!empty($test['site_name'])) { + \Drupal::configFactory() + ->getEditable('system.site') + ->set('name', $test['site_name']) + ->save(); + } + $result = $token_manager->replace($test['text'], NULL, [], $test['options']); + $this->assertEqual($result, $test['expected'], $test['message']); + } + + } + +} diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardAccessTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardAccessTest.php index a76a2c69bb..82a371de89 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardAccessTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardAccessTest.php @@ -25,7 +25,7 @@ public function testConditionalWizard() { $webform = Webform::load('test_form_wizard_access'); // Check anonymous user can access 'All' and 'Anonymous' form pages. - $this->drupalGet('webform/test_form_wizard_access'); + $this->drupalGet('/webform/test_form_wizard_access'); $this->assertRaw('<b>All</b>'); $this->assertRaw('<b>Anonymous</b>'); $this->assertNoRaw('<b>Authenticated</b>'); @@ -54,7 +54,7 @@ public function testConditionalWizard() { $this->drupalLogin($this->rootUser); // Check authenticated user can access 'All', 'Authenticated', and 'Private' form pages. - $this->drupalGet('webform/test_form_wizard_access'); + $this->drupalGet('/webform/test_form_wizard_access'); $this->assertRaw('<b>All</b>'); $this->assertNoRaw('<b>Anonymous</b>'); $this->assertRaw('<b>Authenticated</b>'); diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardAdvancedTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardAdvancedTest.php index 8acada5e20..cf54fa8607 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardAdvancedTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardAdvancedTest.php @@ -26,7 +26,7 @@ public function testAdvancedWizard() { $webform = Webform::load('test_form_wizard_advanced'); // Get initial wizard start page (Your Information). - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); // Check progress bar is set to 'Your Information'. $this->assertPattern('#<li data-webform-page="information" class="webform-progress-bar__page webform-progress-bar__page--current">\s+<b>Your Information</b><span></span></li>#'); // Check progress pages. @@ -120,7 +120,7 @@ public function testAdvancedWizard() { ]; $this->drupalPostForm(NULL, $edit, t('Save Draft')); // Complete reload the webform. - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); // Check progress bar is still set to 'Contact Information'. $this->assertCurrentPage('Contact Information', 'contact'); @@ -158,7 +158,7 @@ public function testAdvancedWizard() { $this->assertRaw('<a href="mailto:janesmith@example.com">janesmith@example.com</a>'); $this->assertRaw('<label>Phone</label>'); $this->assertRaw('<a href="tel:111-111-1111">111-111-1111</a>'); - $this->assertRaw('<div id="test_form_wizard_advanced--comments" class="webform-element webform-element-type-textarea js-form-item form-item js-form-type-item form-type-item js-form-item-comments form-item-comments form-no-label">'); + $this->assertRaw('<div class="webform-element webform-element-type-textarea js-form-item form-item js-form-type-item form-type-item js-form-item-comments form-item-comments form-no-label" id="test_form_wizard_advanced--comments">'); $this->assertRaw('This is working fine.'); // Submit the webform. @@ -225,7 +225,7 @@ public function testAdvancedWizard() { 'wizard_progress_percentage' => TRUE, ] + $webform->getSettings()); $webform->save(); - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); // Check no progress bar. $this->assertNoRaw('class="webform-progress-bar"'); @@ -242,7 +242,7 @@ public function testAdvancedWizard() { \Drupal::configFactory()->getEditable('webform.settings') ->set('settings.default_wizard_confirmation_label', '{global complete}') ->save(); - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); $this->assertRaw('{global complete}'); // Check webform complete label. @@ -251,7 +251,7 @@ public function testAdvancedWizard() { 'wizard_confirmation_label' => '{webform complete}', ] + $webform->getSettings()); $webform->save(); - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); $this->assertRaw('{webform complete}'); // Check webform exclude complete. @@ -259,13 +259,13 @@ public function testAdvancedWizard() { 'wizard_confirmation' => FALSE, ] + $webform->getSettings()); $webform->save(); - $this->drupalGet('webform/test_form_wizard_advanced'); + $this->drupalGet('/webform/test_form_wizard_advanced'); // Check complete label. $this->assertRaw('class="webform-progress-bar"'); // Check complete is missing from confirmation page. $this->assertNoRaw('{webform complete}'); - $this->drupalGet('webform/test_form_wizard_advanced/confirmation'); + $this->drupalGet('/webform/test_form_wizard_advanced/confirmation'); $this->assertNoRaw('class="webform-progress-bar"'); } diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardBasicTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardBasicTest.php index 159edc71c3..886e55a552 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardBasicTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardBasicTest.php @@ -19,7 +19,7 @@ class WebformWizardBasicTest extends WebformWizardTestBase { protected static $testWebforms = ['test_form_wizard_basic']; /** - * Test webform advanced wizard. + * Test webform basic wizard. */ public function testBasicWizard() { $this->drupalLogin($this->rootUser); @@ -53,7 +53,7 @@ public function testBasicWizard() { ->save(); // Check next page. - $this->drupalGet('webform/test_form_wizard_basic'); + $this->drupalGet('/webform/test_form_wizard_basic'); $this->assertNoRaw('data-webform-wizard-current-page'); $this->assertRaw('data-webform-wizard-page="page_2" data-drupal-selector="edit-wizard-next"'); @@ -75,7 +75,7 @@ public function testBasicWizard() { $wizard_webform->setSetting('wizard_track', 'index')->save(); // Check next page. - $this->drupalGet('webform/test_form_wizard_basic'); + $this->drupalGet('/webform/test_form_wizard_basic'); $this->assertRaw('data-webform-wizard-page="2" data-drupal-selector="edit-wizard-next"'); // Check next and previous page. diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardConditionalTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardConditionalTest.php index 8541da6024..15418b3fc8 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardConditionalTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardConditionalTest.php @@ -23,7 +23,7 @@ class WebformWizardConditionalTest extends WebformWizardTestBase { */ public function testConditionalWizard() { $webform = Webform::load('test_form_wizard_conditional'); - $this->drupalGet('webform/test_form_wizard_conditional'); + $this->drupalGet('/webform/test_form_wizard_conditional'); // Check hiding page 1, 3, and 5. $edit = [ diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardCustomTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardCustomTest.php index ef60554e60..45000eea15 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardCustomTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardCustomTest.php @@ -28,7 +28,7 @@ class WebformWizardCustomTest extends WebformWizardTestBase { */ public function testCustomWizard() { // Check current page is #1. - $this->drupalGet('webform/test_form_wizard_custom'); + $this->drupalGet('/webform/test_form_wizard_custom'); $this->assertCurrentPage('Wizard page #1', 'wizard_1'); // Check next page is #2. diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardLinksTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardLinksTest.php new file mode 100644 index 0000000000..642d900bff --- /dev/null +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardLinksTest.php @@ -0,0 +1,85 @@ +<?php + +namespace Drupal\webform\Tests\Wizard; + +use Drupal\webform\Entity\Webform; + +/** + * Tests for webform wizard progress and preview links. + * + * @group Webform + */ +class WebformWizardLinksTest extends WebformWizardTestBase { + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_form_wizard_links']; + + /** + * Test webform wizard progress and preview links. + */ + public function testWizardLinks() { + $this->drupalLogin($this->rootUser); + + $wizard_webform = Webform::load('test_form_wizard_links'); + + // Check that first page has no links. + $this->drupalGet('/webform/test_form_wizard_links'); + $this->assertCssSelect('.webform-wizard-pages-links'); + $this->assertNoFieldByName('webform_wizard_page-page_1', t('Edit')); + $this->assertNoFieldByName('webform_wizard_page-page_2', t('Edit')); + + // Check that second page links to first page. + $this->drupalPostForm('webform/test_form_wizard_links', [], t('Next Page >')); + $this->assertCssSelect('.webform-wizard-pages-links'); + $this->assertFieldByName('webform_wizard_page-page_1', t('Edit')); + $this->assertNoFieldByName('webform_wizard_page-page_2', t('Edit')); + + // Check that preview links to first and second page. + $this->drupalPostForm('webform/test_form_wizard_links', [], t('Preview')); + $this->assertCssSelect('.webform-wizard-pages-links'); + $this->assertFieldByName('webform_wizard_page-page_1', t('Edit')); + $this->assertFieldByName('webform_wizard_page-page_2', t('Edit')); + + // Check that preview links are not wrapper in .form-actions. + $this->assertNoCssSelect('.webform-wizard-pages-links.form-actions'); + + // Check 'wizard_progress_link' setting. + $this->assertCssSelect('.webform-wizard-pages-links[data-wizard-progress-link="true"]'); + + // Check 'wizard_preview_link' setting. + $this->assertCssSelect('.webform-wizard-pages-links[data-wizard-preview-link="true"]'); + + // Set preview links to FALSE. + $wizard_webform->setSetting('wizard_preview_link', FALSE)->save(); + + // Check preview page is not linked. + $this->drupalGet('/webform/test_form_wizard_links'); + $this->assertCssSelect('.webform-wizard-pages-links[data-wizard-progress-link="true"]'); + $this->assertNoCssSelect('.webform-wizard-pages-links[data-wizard-preview-link="true"]'); + + // Set progress bar links to FALSE. + $wizard_webform + ->setSetting('wizard_progress_link', FALSE) + ->setSetting('wizard_preview_link', TRUE) + ->save(); + + // Check progress bar is not linked. + $this->drupalGet('/webform/test_form_wizard_links'); + $this->assertNoCssSelect('.webform-wizard-pages-links[data-wizard-progress-link="true"]'); + $this->assertCssSelect('.webform-wizard-pages-links[data-wizard-preview-link="true"]'); + + // Set progress bar links and preview page to FALSE. + $wizard_webform + ->setSetting('wizard_progress_link', FALSE) + ->setSetting('wizard_preview_link', FALSE) + ->save(); + + $this->drupalGet('/webform/test_form_wizard_links'); + $this->assertNoCssSelect('.webform-wizard-pages-links'); + } + +} diff --git a/web/modules/webform/src/Tests/Wizard/WebformWizardValidateTest.php b/web/modules/webform/src/Tests/Wizard/WebformWizardValidateTest.php index da39a0a30d..53723b4048 100644 --- a/web/modules/webform/src/Tests/Wizard/WebformWizardValidateTest.php +++ b/web/modules/webform/src/Tests/Wizard/WebformWizardValidateTest.php @@ -27,7 +27,7 @@ class WebformWizardValidateTest extends WebformWizardTestBase { * Test webform wizard validation. */ public function testWizardValidate() { - $this->drupalGet('webform/test_form_wizard_validate'); + $this->drupalGet('/webform/test_form_wizard_validate'); /**************************************************************************/ // Basic validation. @@ -255,5 +255,4 @@ public function testWizardValidate() { $this->assertRaw($raw); } - } diff --git a/web/modules/webform/src/Twig/TwigExtension.php b/web/modules/webform/src/Twig/TwigExtension.php index 1c17fe4e49..020c873414 100644 --- a/web/modules/webform/src/Twig/TwigExtension.php +++ b/web/modules/webform/src/Twig/TwigExtension.php @@ -52,12 +52,25 @@ public function getName() { * @see \Drupal\Core\Utility\Token::replace */ public function webformToken($token, EntityInterface $entity = NULL, array $data = [], array $options = []) { + static $processing = []; + // Allow the webform_token function to be tested during validation without // a valid entity. if (!$entity) { return $token; } + // Prevent token replacement recursion. + $original_token = $token; + $processing += [$original_token => 0]; + $processing[$original_token]++; + if ($processing[$original_token] > 100) { + // Cancel token processing by settings the processing token to FALSE. + $processing[$original_token] = FALSE; + // Throw exception which is caught by ::renderTwigTemplate. + throw new \LogicException(sprintf('The "%s" token is being called recursively.', $token)); + } + // Parse options included in the token. // @see \Drupal\webform\Twig\TwigExtension::renderTwigTemplate foreach (static::$options as $option_name => $option_setting) { @@ -72,8 +85,16 @@ public function webformToken($token, EntityInterface $entity = NULL, array $data // ISSUE. This TwigExtension is loaded on every page load, even when a // website is in maintenance mode. // @see https://www.drupal.org/node/2907960 - /** @var \Drupal\webform\WebformTokenManagerInterface $value */ - $value = \Drupal::service('webform.token_manager')->replace($token, $entity, $data, $options); + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $value = $token_manager->replace($token, $entity, $data, $options); + + // If token replacement caused a recursion exception return an empty value. + if ($processing[$original_token] === FALSE) { + return ''; + } + + $processing[$original_token]--; return (WebformHtmlHelper::containsHtml($value)) ? ['#markup' => $value] : $value; } @@ -88,27 +109,34 @@ public function webformToken($token, EntityInterface $entity = NULL, array $data /** * Build reusable Twig help. * + * @param array $variables + * An array of available variable names. + * * @return array * A renderable array container Twig help. */ - public static function buildTwigHelp() { + public static function buildTwigHelp(array $variables = []) { /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); $field_definitions = $submission_storage->getFieldDefinitions(); - $items = [ + + // Bold all the passed variable names. + foreach ($variables as $index => $item) { + $variables[$index] = ['#markup' => $item, '#prefix' => '<strong>', '#suffix' => '</strong>']; + } + + $variables = array_merge($variables, [ '{{ webform }}', '{{ webform_submission }}', '{{ elements }}', '{{ elements_flattened }}', - // @todo Dynamically generate examples for all elements. - // This could be overkill. '{{ data.element_key }}', '{{ data.element_key.delta }}', '{{ data.composite_element_key.subelement_key }}', '{{ data.composite_element_key.delta.subelement_key }}', - ]; + ]); foreach (array_keys($field_definitions) as $field_name) { - $items[] = "{{ $field_name }}"; + $variables[] = "{{ $field_name }}"; } $t_args = [ @@ -124,13 +152,13 @@ public static function buildTwigHelp() { ]; $output[] = [ '#theme' => 'item_list', - '#items' => $items, + '#items' => $variables, ]; $output[] = [ '#markup' => '<p>' . t("You can also output tokens using the <code>webform_token()</code> function.") . '</p>', ]; $output[] = [ - '#markup' => "<pre>{{ webform_token('[webform_submission:values:element_value]', webform_submission) }}</pre>", + '#markup' => "<pre>{{ webform_token('[webform_submission:values:element_value]', webform_submission, [], options) }}</pre>", ]; if (\Drupal::currentUser()->hasPermission('administer modules') && !\Drupal::moduleHandler()->moduleExists('twig_tweak')) { $t_args = [ @@ -160,7 +188,9 @@ public static function buildTwigHelp() { * @param string $template * A inline Twig template. * @param array $options - * Template and token options. + * (optional) Template and token options. + * @param array $context + * (optional) Context to be passed to inline Twig template. * * @return string * The fully rendered Twig template. @@ -168,7 +198,36 @@ public static function buildTwigHelp() { * @see \Drupal\webform\Element\WebformComputedTwig::processValue * @see \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler::getMessage */ - public static function renderTwigTemplate(WebformSubmissionInterface $webform_submission, $template, array $options) { + public static function renderTwigTemplate(WebformSubmissionInterface $webform_submission, $template, array $options = [], array $context = []) { + try { + $build = self::buildTwigTemplate($webform_submission, $template, $options, $context); + return \Drupal::service('renderer')->renderPlain($build); + } + catch (\Exception $exception) { + if ($webform_submission->getWebform()->access('update')) { + \Drupal::messenger()->addError($exception->getMessage()); + } + \Drupal::logger('webform')->error($exception->getMessage()); + return ''; + } + } + + /** + * Build a Twig template with a webform submission. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param string $template + * A inline Twig template. + * @param array $options + * (optional) Template, token options, and context. + * @param array $context + * (optional) Context to be passed to inline Twig template. + * + * @return array + * A renderable containing an inline twig template. + */ + public static function buildTwigTemplate(WebformSubmissionInterface $webform_submission, $template, array $options = [], array $context = []) { $options += [ 'html' => FALSE, 'email' => FALSE, @@ -182,35 +241,34 @@ public static function renderTwigTemplate(WebformSubmissionInterface $webform_su } } - $context = [ + // If the template does NOT use the webform_token() function, but contains + // simple tokens, convert the simple tokens to use + // the webform_token() function. + if (strpos($template, 'webform_token(') === FALSE + && strpos($template, '[webform') !== FALSE) { + $template = preg_replace('#([^"\']|^)(\[[^]]+\])([^"\']|$)#', '\1{{ webform_token(\'\2\', webform_submission) }}\3', $template); + } + + $context += [ 'webform_submission' => $webform_submission, 'webform' => $webform_submission->getWebform(), 'elements' => $webform_submission->getWebform()->getElementsDecoded(), 'elements_flattened' => $webform_submission->getWebform()->getElementsDecodedAndFlattened(), + 'options' => $options, ] + $webform_submission->toArray(TRUE); - $build = [ + return [ '#type' => 'inline_template', '#template' => $template, '#context' => $context, ]; - - try { - return \Drupal::service('renderer')->renderPlain($build); - } - catch (\Exception $exception) { - if ($webform_submission->getWebform()->access('update')) { - drupal_set_message(t('Failed to render computed Twig value due to error "%error"', ['%error' => $exception->getMessage()]), 'error'); - } - return ''; - } } /** - * Determine if the current user can edit Twig templates. + * Determine if the current user can edit Twig templates. * * @return bool - * TRUE if the current user can edit Twig templates. + * TRUE if the current user can edit Twig templates. */ public static function hasEditTwigAccess() { return (\Drupal::currentUser()->hasPermission('edit webform twig') || \Drupal::currentUser()->hasPermission('administer webform')); diff --git a/web/modules/webform/src/Utility/Mail.php b/web/modules/webform/src/Utility/Mail.php new file mode 100644 index 0000000000..3ed9a4778d --- /dev/null +++ b/web/modules/webform/src/Utility/Mail.php @@ -0,0 +1,71 @@ +<?php + +namespace Drupal\webform\Utility; + +use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\Unicode; + +/** + * Provides helpers to ensure emails are compliant with RFCs. + * + * @todo Remove this utility class once only Drupal 8.7.x is supported. + * @see https://www.drupal.org/node/3015116 + */ +class Mail { + + /** + * RFC-2822 "specials" characters. + */ + const RFC_2822_SPECIALS = '()<>[]:;@\,."'; + + /** + * Return a RFC-2822 compliant "display-name" component. + * + * The "display-name" component is used in mail header "Originator" fields + * (From, Sender, Reply-to) to give a human-friendly description of the + * address, i.e. From: My Display Name <xyz@example.org>. RFC-822 and + * RFC-2822 define its syntax and rules. This method gets as input a string + * to be used as "display-name" and formats it to be RFC compliant. + * + * @param string $string + * A string to be used as "display-name". + * + * @return string + * A RFC compliant version of the string, ready to be used as + * "display-name" in mail originator header fields. + */ + public static function formatDisplayName($string) { + // Make sure we don't process html-encoded characters. They may create + // unneeded trouble if left encoded, besides they will be correctly + // processed if decoded. + $string = Html::decodeEntities($string); + + // If string contains non-ASCII characters it must be (short) encoded + // according to RFC-2047. The output of a "B" (Base64) encoded-word is + // always safe to be used as display-name. + $safe_display_name = Unicode::mimeHeaderEncode($string, TRUE); + + // Encoded-words are always safe to be used as display-name because don't + // contain any RFC 2822 "specials" characters. However + // Unicode::mimeHeaderEncode() encodes a string only if it contains any + // non-ASCII characters, and leaves its value untouched (un-encoded) if + // ASCII only. For this reason in order to produce a valid display-name we + // still need to make sure there are no "specials" characters left. + if (preg_match('/[' . preg_quote(Mail::RFC_2822_SPECIALS) . ']/', $safe_display_name)) { + + // If string is already quoted, it may or may not be escaped properly, so + // don't trust it and reset. + if (preg_match('/^"(.+)"$/', $safe_display_name, $matches)) { + $safe_display_name = str_replace(['\\\\', '\\"'], ['\\', '"'], $matches[1]); + } + + // Transform the string in a RFC-2822 "quoted-string" by wrapping it in + // double-quotes. Also make sure '"' and '\' occurrences are escaped. + $safe_display_name = '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $safe_display_name) . '"'; + + } + + return $safe_display_name; + } + +} diff --git a/web/modules/webform/src/Utility/WebformAccessibilityHelper.php b/web/modules/webform/src/Utility/WebformAccessibilityHelper.php new file mode 100644 index 0000000000..806c07a044 --- /dev/null +++ b/web/modules/webform/src/Utility/WebformAccessibilityHelper.php @@ -0,0 +1,72 @@ +<?php + +namespace Drupal\webform\Utility; + +/** + * Helper class webform accessibility methods. + */ +class WebformAccessibilityHelper { + + /** + * Visually hide text using .visually-hidden class. + * + * The .visually-hidden class is used to render invisible content just for + * screen reader users. + * + * @param string|array $title + * Text or #markup that should be visually hidden. + * + * @return array + * A renderable array with the text wrapped in + * <span class="visually-hidden"> + * + * @see https://webaim.org/techniques/css/invisiblecontent/ + */ + public static function buildVisuallyHidden($title) { + if (is_array($title)) { + return $title + [ + '#prefix' => '<span class="visually-hidden">', + '#suffix' => '</span>', + ]; + } + else { + return [ + '#markup' => $title, + '#prefix' => '<span class="visually-hidden">', + '#suffix' => '</span>', + ]; + } + } + + /** + * Aria hide text using aria-hidden attribute. + * + * The aria-hidden property tells screen-readers if they + * should ignore the element. + * + * @param string|array $title + * Text or #markup that should be aria-hidden. + * + * @return array + * A renderable array with the text wrapped in + * <span aria-hidden="true"> + * + * @see https://www.w3.org/TR/wai-aria-1.1/#aria-hidden + */ + public static function buildAriaHidden($title) { + if (is_array($title)) { + return $title + [ + '#prefix' => '<span aria-hidden="true">', + '#suffix' => '</span>', + ]; + } + else { + return [ + '#markup' => $title, + '#prefix' => '<span aria-hidden="true">', + '#suffix' => '</span>', + ]; + } + } + +} diff --git a/web/modules/webform/src/Utility/WebformArrayHelper.php b/web/modules/webform/src/Utility/WebformArrayHelper.php index 096d241ef0..b20528b22d 100644 --- a/web/modules/webform/src/Utility/WebformArrayHelper.php +++ b/web/modules/webform/src/Utility/WebformArrayHelper.php @@ -378,4 +378,23 @@ public static function insertAfter(array &$array, $target_key, $new_key, $new_va $array = $new; } + /** + * Remove value from an array. + * + * @param array &$array + * An array. + * @param mixed $value + * A value. + * + * @see https://stackoverflow.com/questions/7225070/php-array-delete-by-value-not-key + */ + public static function removeValue(array &$array, $value) { + if (($key = array_search($value, $array)) !== FALSE) { + unset($array[$key]); + } + if (static::isSequential($array)) { + array_values($array); + } + } + } diff --git a/web/modules/webform/src/Utility/WebformDateHelper.php b/web/modules/webform/src/Utility/WebformDateHelper.php index c946856264..9a6951e758 100644 --- a/web/modules/webform/src/Utility/WebformDateHelper.php +++ b/web/modules/webform/src/Utility/WebformDateHelper.php @@ -2,8 +2,10 @@ namespace Drupal\webform\Utility; +use Drupal\Core\Datetime\DateHelper; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Form\OptGroup; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** * Helper class webform date helper methods. @@ -44,15 +46,15 @@ class WebformDateHelper { * the user interface language for the page. * * @return string - * A translated date string in the requested format. An empty string - * will be returned for empty timestamps. + * A translated date string in the requested format. An empty string will be + * returned for empty timestamps. * * @see \Drupal\Core\Datetime\DateFormatterInterface::format */ public static function format($timestamp, $type = 'fallback', $format = '', $timezone = NULL, $langcode = NULL) { /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ $date_formatter = \Drupal::service('date.formatter'); - return $timestamp ? $date_formatter->format($timestamp, $type) : ''; + return $timestamp ? $date_formatter->format($timestamp, $type, $format, $timezone, $langcode) : ''; } /** @@ -65,25 +67,34 @@ public static function format($timestamp, $type = 'fallback', $format = '', $tim * The date/time object format as 'Y-m-d\TH:i:s'. */ public static function formatStorage(DrupalDateTime $date) { - return $date->format(DATETIME_DATETIME_STORAGE_FORMAT); + return $date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT); } /** - * Check if date/time string is using a valid date/time format. + * Creates a date object from an input format with a translated date string. * - * @param string $time - * A date/time string. * @param string $format - * Format accepted by date(). + * PHP date() type format for parsing the input. + * @param mixed $time + * A date string. + * @param mixed $timezone + * PHP DateTimeZone object, string or NULL allowed. + * @param array $settings + * An array of settings. * - * @return bool - * TRUE is $time is in the accepted format. + * @return \Drupal\Core\Datetime\DrupalDateTime|bool + * A new DateTimePlus object or FALSE if invalid date string. * - * @see http://stackoverflow.com/questions/19271381/correctly-determine-if-date-string-is-a-valid-date-in-that-format + * @see \Drupal\Core\Datetime\DrupalDateTime::__construct */ - public static function isValidDateFormat($time, $format = 'Y-m-d') { - $datetime = \DateTime::createFromFormat($format, $time); - return ($datetime && $datetime->format($format) === $time); + public static function createFromFormat($format, $time, $timezone = NULL, array $settings = []) { + $english_time = WebformDateHelper::convertDateStringToEnglish($format, $time); + try { + return DrupalDateTime::createFromFormat($format, $english_time, $timezone, $settings); + } + catch (\Exception $exception) { + return FALSE; + } } /** @@ -185,4 +196,65 @@ protected static function initIntervalOptions() { } } + /** + * Convert date string to English so that it can be parsed. + * + * @param string $format + * PHP date() type format for parsing the input. + * @param string $value + * A date string. + * + * @return string + * A date string translated to English. + * + * @see https://stackoverflow.com/questions/36498186/php-datetimecreatefromformat-and-multi-languages + * @see core/modules/locale/locale.datepicker.js + */ + protected static function convertDateStringToEnglish($format, $value) { + // Do not convert English date strings. + if (\Drupal::languageManager()->getCurrentLanguage()->getId() === 'en') { + return $value; + } + + // F = A full textual representation of a month, such as January or March. + if (strpos($format, 'F') !== FALSE) { + $month_names_untranslated = DateHelper::monthNamesUntranslated(); + $month_names_translated = DateHelper::monthNames(); + foreach ($month_names_untranslated as $index => $month_name_untranslated) { + $value = str_ireplace((string) $month_names_translated[$index], $month_name_untranslated, $value); + } + + } + + // M = A short textual representation of a month, three letters. + if (strpos($format, 'M') !== FALSE) { + $month_names_abbr_untranslated = DateHelper::monthNamesAbbrUntranslated(); + $month_names_abbr_translated = DateHelper::monthNamesAbbr(); + foreach ($month_names_abbr_untranslated as $index => $month_name_abbr_untranslated) { + $value = str_ireplace((string) $month_names_abbr_translated[$index], $month_name_abbr_untranslated, $value); + } + } + + // l = A full textual representation of the day of the week. + if (strpos($format, 'l') !== FALSE) { + $week_days_untranslated = DateHelper::weekDaysUntranslated(); + $week_days_translated = DateHelper::weekDays(); + foreach ($week_days_untranslated as $index => $week_day_untranslated) { + $value = str_ireplace((string) $week_days_translated[$index], $week_day_untranslated, $value); + } + } + + // D = A textual representation of a day, three letters. + if (strpos($format, 'D') !== FALSE) { + $week_days_abbr_untranslated = DateHelper::weekDaysUntranslated(); + $week_days_abbr_translated = DateHelper::weekDaysAbbr(); + foreach ($week_days_abbr_untranslated as $index => $week_day_abbr_untranslated) { + $week_days_abbr_untranslated[$index] = (string) substr($week_days_abbr_untranslated[$index], 0, 3); + $value = str_ireplace((string) $week_days_abbr_translated[$index], $week_days_abbr_untranslated[$index], $value); + } + } + + return $value; + } + } diff --git a/web/modules/webform/src/Utility/WebformDialogHelper.php b/web/modules/webform/src/Utility/WebformDialogHelper.php index 786a186aba..0fc2da05cb 100644 --- a/web/modules/webform/src/Utility/WebformDialogHelper.php +++ b/web/modules/webform/src/Utility/WebformDialogHelper.php @@ -21,7 +21,7 @@ class WebformDialogHelper { /** * Width for normal dialog. (modal: 800px; off-canvas: 600px) * - * Used by: Add and edit element/handler, etc... + * Used by: Add and edit element/handler, etc… * * @var string */ @@ -30,7 +30,7 @@ class WebformDialogHelper { /** * Width for narrow dialog. (modal: 700px; off-canvas: 500px) * - * Used by: Duplicate and delete entity, notes, etc... + * Used by: Duplicate and delete entity, notes, etc… * * @var string */ @@ -43,7 +43,7 @@ class WebformDialogHelper { * TRUE if outside_in.module is enabled and system trays are not disabled. */ public static function useOffCanvas() { - return ((floatval(\Drupal::VERSION) >= 8.5) && !\Drupal::config('webform.settings')->get('ui.offcanvas_disabled')) ? TRUE : FALSE; + return (!\Drupal::config('webform.settings')->get('ui.offcanvas_disabled')) ? TRUE : FALSE; } /** @@ -54,6 +54,9 @@ public static function useOffCanvas() { */ public static function attachLibraries(array &$build) { $build['#attached']['library'][] = 'webform/webform.admin.dialog'; + if (static::useOffCanvas()) { + $build['#attached']['library'][] = 'webform/webform.off_canvas'; + } // @see \Drupal\webform\Element\WebformHtmlEditor::preRenderWebformHtmlEditor if (\Drupal::moduleHandler()->moduleExists('imce') && \Drupal\imce\Imce::access()) { $build['#attached']['library'][] = 'imce/drupal.imce.ckeditor'; @@ -90,9 +93,9 @@ public static function getModalDialogAttributes($width = self::DIALOG_NORMAL, ar 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode([ 'width' => $width, - // .webform-modal is used to set the dialog's top position. + // .webform-ui-dialog is used to set the dialog's top position. // @see modules/sandbox/webform/css/webform.ajax.css - 'dialogClass' => 'webform-modal', + 'dialogClass' => 'webform-ui-dialog', ]), ]; } diff --git a/web/modules/webform/src/Utility/WebformElementHelper.php b/web/modules/webform/src/Utility/WebformElementHelper.php index 4519448c05..4f7d7ee345 100644 --- a/web/modules/webform/src/Utility/WebformElementHelper.php +++ b/web/modules/webform/src/Utility/WebformElementHelper.php @@ -71,6 +71,86 @@ class WebformElementHelper { */ protected static $ignoredSubPropertiesRegExp; + /** + * Determine if an element and its key is a renderable array. + * + * @param array|mixed $element + * An element. + * @param string $key + * The element key. + * + * @return bool + * TRUE if an element and its key is a renderable array. + */ + public static function isElement($element, $key) { + return (Element::child($key) && is_array($element)); + } + + /** + * Determine if an element has children. + * + * @param array|mixed $element + * An element. + * + * @return bool + * TRUE if an element has children. + * + * @see \Drupal\Core\Render\Element::children + */ + public static function hasChildren($element) { + foreach ($element as $key => $value) { + if ($key === '' || $key[0] !== '#') { + return TRUE; + } + } + return FALSE; + } + + /** + * Determine if an element is a webform element and should be enhanced. + * + * @param array $element + * An element. + * + * @return bool + * TRUE if an element is a webform element. + */ + public static function isWebformElement(array $element) { + if (isset($element['#webform_key']) || isset($element['#webform_element'])) { + return TRUE; + } + elseif (\Drupal::service('webform.request')->isWebformAdminRoute()) { + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Determine if a webform element is a specified #type. + * + * @param array $element + * A webform element. + * @param string|array $type + * An element type. + * + * @return bool + * TRUE if a webform element is a specified #type. + */ + public static function isType(array $element, $type) { + if (!isset($element['#type'])) { + return FALSE; + } + + if (is_array($type)) { + return in_array($element['#type'], $type); + } + else { + return ($element['#type'] === $type); + } + } + /** * Determine if a webform element's title is displayed. * @@ -139,52 +219,22 @@ public static function setPropertyRecursive(array &$element, $property_key, $pro } /** - * Enhance select menu with the Select2 or the Chosen library. + * Process a form element and apply webform element specific enhancements. * - * Please Note: Select2 is preferred library for Webform administrative - * forms. + * This method allows any form API element to be enhanced using webform + * specific features include custom validation, external libraries, + * accessibility improvements, etc… * * @param array $element - * A select element. - * @param bool $library - * Flag to automatically detect and apply library. + * An associative array containing an element with a #type property. * * @return array - * The select element with Select2 or Chosen class and library attached. + * The processed form element with webform element specific enhancements. */ - public static function enhanceSelect(array &$element, $library = FALSE) { - // If automatic is FALSE, look at the element's #select2 and #chosen - // property. - if (!$library) { - if (isset($element['#select2'])) { - $library = 'select2'; - } - elseif (isset($element['#chosen'])) { - $library = 'chosen'; - } - } - - if ($library === FALSE) { - return $element; - } - - /** @var \Drupal\webform\WebformLibrariesManagerInterface $libaries_manager */ - $libaries_manager = \Drupal::service('webform.libraries_manager'); - - // Add select2 library and classes. - if (($library === TRUE || $library === 'select2') && $libaries_manager->isIncluded('jquery.select2')) { - $element['#attached']['library'][] = 'webform/webform.element.select2'; - $element['#attributes']['class'][] = 'js-webform-select2'; - $element['#attributes']['class'][] = 'webform-select2'; - } - // Add chosen library and classes. - elseif (($library === TRUE || $library === 'chosen') && $libaries_manager->isIncluded('jquery.chosen')) { - $element['#attached']['library'][] = 'webform/webform.element.chosen'; - $element['#attributes']['class'][] = 'js-webform-chosen'; - $element['#attributes']['class'][] = 'webform-chosen'; - } - - return $element; + public static function process(array &$element) { + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + return $element_manager->processElement($element); } /** @@ -353,6 +403,11 @@ public static function merge(array &$elements, array $source_elements) { * An associative array of translated element properties. */ public static function applyTranslation(array &$element, array $translation) { + // Apply all translated properties to the element. + // This allows default properties to be translated, which includes + // composite element titles. + $element += $translation; + foreach ($element as $key => &$value) { // Make sure to only merge properties. if (!Element::property($key) || empty($translation[$key])) { @@ -385,7 +440,7 @@ public static function applyTranslation(array &$element, array $translation) { public static function getFlattened(array $elements) { $flattened_elements = []; foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -442,7 +497,7 @@ public static function convertToString($element) { /****************************************************************************/ // ISSUE: Hidden elements still need to call #element_validate because // certain elements, including managed_file, checkboxes, password_confirm, - // etc..., will also massage the submitted values via #element_validate. + // etc…, will also massage the submitted values via #element_validate. // // SOLUTION: Call #element_validate for all hidden elements but suppresses // #element_validate errors. @@ -492,7 +547,8 @@ public static function triggerElementValidate(array &$element, FormStateInterfac // @see \Drupal\Core\Form\FormValidator::doValidateForm foreach ($element['#_element_validate'] as $callback) { $complete_form = &$form_state->getCompleteForm(); - call_user_func_array($form_state->prepareCallback($callback), [&$element, &$form_state, &$complete_form]); + $arguments = [&$element, &$form_state, &$complete_form]; + call_user_func_array($form_state->prepareCallback($callback), $arguments); } } @@ -513,7 +569,8 @@ public static function suppressElementValidate(array &$element, FormStateInterfa // @see \Drupal\Core\Form\FormValidator::doValidateForm foreach ($element['#_element_validate'] as $callback) { $complete_form = &$form_state->getCompleteForm(); - call_user_func_array($form_state->prepareCallback($callback), [&$element, &$temp_form_state, &$complete_form]); + $arguments = [&$element, &$temp_form_state, &$complete_form]; + call_user_func_array($form_state->prepareCallback($callback), $arguments); } // Get the temp webform state's values. @@ -554,9 +611,12 @@ public static function setRequiredError(array $element, FormStateInterface $form * @return array * An associative array containing an element's states. */ - public static function getStates(array $element) { - // Composite and multiple elements use use a custom states wrapper - // which will changes '#states' to '#_webform_states'. + public static function &getStates(array &$element) { + // Processed elements store the original #states in '#_webform_states'. + // @see \Drupal\webform\WebformSubmissionConditionsValidator::buildForm + // + // Composite and multiple elements use a custom states wrapper + // which will change '#states' to '#_webform_states'. // @see \Drupal\webform\Utility\WebformElementHelper::fixStatesWrapper if (!empty($element['#_webform_states'])) { return $element['#_webform_states']; @@ -565,7 +625,10 @@ public static function getStates(array $element) { return $element['#states']; } else { - return []; + // Return empty states variable to prevent the below notice. + // 'Only variable references should be returned by reference'. + $empty_states = []; + return $empty_states; } } @@ -594,4 +657,33 @@ public static function getRequiredFromVisibleStates(array $element) { return $required_states; } + /** + * Randomoize an associative array of element values and disable page caching. + * + * @param array $values + * An associative array of element values. + * + * @return array + * Randomized associative array of element values. + */ + public static function randomize(array $values) { + // Make sure randomized elements and options are never cached by the + // current page. + \Drupal::service('page_cache_kill_switch')->trigger(); + return WebformArrayHelper::shuffle($values); + } + + /** + * Form API callback. Remove unchecked options and returns an array of values. + */ + public static function filterValues(array &$element, FormStateInterface $form_state, array &$completed_form) { + $values = $element['#value']; + $values = array_filter($values, function ($value) { + return $value !== 0; + }); + $values = array_values($values); + $element['#value'] = $values; + $form_state->setValueForElement($element, $values); + } + } diff --git a/web/modules/webform/src/Utility/WebformFormHelper.php b/web/modules/webform/src/Utility/WebformFormHelper.php index 08a5af2f14..ea2bdefc57 100644 --- a/web/modules/webform/src/Utility/WebformFormHelper.php +++ b/web/modules/webform/src/Utility/WebformFormHelper.php @@ -104,6 +104,7 @@ public static function buildTabs(array $form, array $tabs, $active_tab = '') { '#group' => 'tabs', '#attributes' => [ 'id' => 'webform-tab--' . $tab_name, + 'class' => ['webform-tab'], ], ]; } @@ -137,7 +138,7 @@ public static function buildTabs(array $form, array $tabs, $active_tab = '') { * * @return array * The values without default keys like - * 'form_build_id', 'form_token', 'form_id', 'op', 'actions', etc... + * 'form_build_id', 'form_token', 'form_id', 'op', 'actions', etc… */ public static function cleanupFormStateValues(array $values, array $keys = []) { // Remove default FAPI values. @@ -190,7 +191,7 @@ public static function &flattenElements(array &$build) { */ protected static function flattenElementsRecursive(array &$build, array &$elements, array &$duplicate_element_keys) { foreach ($build as $key => &$element) { - if (Element::child($key) && is_array($element)) { + if (WebformElementHelper::isElement($element, $key)) { // If there are duplicate element keys create an array of referenced // elements. if (isset($elements[$key])) { diff --git a/web/modules/webform/src/Utility/WebformObjectHelper.php b/web/modules/webform/src/Utility/WebformObjectHelper.php new file mode 100644 index 0000000000..4aefe50c1f --- /dev/null +++ b/web/modules/webform/src/Utility/WebformObjectHelper.php @@ -0,0 +1,25 @@ +<?php + +namespace Drupal\webform\Utility; + +/** + * Provides helper to operate on objects. + */ +class WebformObjectHelper { + + /** + * Sort object by properties. + * + * @param object $object + * An object. + * + * @return object + * Object sorted by properties. + */ + public static function sortByProperty($object) { + $array = (array) $object; + ksort($array); + return (object) $array; + } + +} diff --git a/web/modules/webform/src/Utility/WebformOptionsHelper.php b/web/modules/webform/src/Utility/WebformOptionsHelper.php index 3ed405df87..06e16055c3 100644 --- a/web/modules/webform/src/Utility/WebformOptionsHelper.php +++ b/web/modules/webform/src/Utility/WebformOptionsHelper.php @@ -17,7 +17,7 @@ class WebformOptionsHelper { const DESCRIPTION_DELIMITER = ' -- '; /** - * Append option value to option text + * Append option value to option text. * * @param array $options * An associative array of options. diff --git a/web/modules/webform/src/Utility/WebformTextHelper.php b/web/modules/webform/src/Utility/WebformTextHelper.php new file mode 100644 index 0000000000..dbe71ff358 --- /dev/null +++ b/web/modules/webform/src/Utility/WebformTextHelper.php @@ -0,0 +1,82 @@ +<?php + +namespace Drupal\webform\Utility; + +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; + +/** + * Provides helper to operate on strings. + */ +class WebformTextHelper { + + /** + * CamelCase to Underscore name converter. + * + * @var \Symfony\Component\Serializer\NameConverter\NameConverterInterface + */ + protected static $converter; + + /** + * Get camel case to snake case converter. + * + * @return \Symfony\Component\Serializer\NameConverter\NameConverterInterface + * Camel case to snake case converter. + */ + protected static function getCamelCaseToSnakeCaseNameConverter() { + if (!isset(static::$converter)) { + static::$converter = new CamelCaseToSnakeCaseNameConverter(); + } + return static::$converter; + } + + /** + * Converts camel case to snake case (i.e. underscores). + * + * @param string $string + * String to be converted. + * + * @return string + * String with camel case converted to snake case. + */ + public static function camelToSnake($string) { + return static::getCamelCaseToSnakeCaseNameConverter()->normalize($string); + } + + /** + * Converts snake case to camel case. + * + * @param string $string + * String to be converted. + * + * @return string + * String with snake case converted to camel case. + */ + public static function snakeToCamel($string) { + return static::getCamelCaseToSnakeCaseNameConverter()->denormalize($string); + } + + /** + * Counts the number of words inside a string. + * + * This counts the number of words by counting the space between the words. + * + * str_word_count() is locale dependent and returns varying word counts + * based on the current language. + * + * This approach matches how the jQuery Text Counter Plugin counts words. + * + * @param string $string + * The string. + * + * @return int + * The number of words inside the string. + * + * @see str_word_count() + * @see https://github.com/ractoon/jQuery-Text-Counter + * @see $.textcounter.wordCount + */ + public static function wordCount($string) { + return count(explode(' ', preg_replace('#\s+#', ' ', trim($string)))); + } + +} diff --git a/web/modules/webform/src/Utility/WebformXss.php b/web/modules/webform/src/Utility/WebformXss.php new file mode 100644 index 0000000000..ae268e1072 --- /dev/null +++ b/web/modules/webform/src/Utility/WebformXss.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\webform\Utility; + +use Drupal\Component\Utility\Xss; + +/** + * Provides webform helper to filter for cross-site scripting. + */ +class WebformXss { + + /** + * Gets the list of HTML tags allowed by Xss::filterAdmin() with missing <label>, <fieldset>, <legend>, <font> tags. + * + * @return array + * The list of HTML tags allowed by filterAdmin() with missing + * <label>, <fieldset>, <legend>, <font> tags. + */ + public static function getAdminTagList() { + $allowed_tags = Xss::getAdminTagList(); + $allowed_tags[] = 'label'; + $allowed_tags[] = 'fieldset'; + $allowed_tags[] = 'legend'; + $allowed_tags[] = 'font'; + return $allowed_tags; + } + + /** + * Gets the standard list of HTML tags allowed by Xss::filter() with missing <font> tag. + * + * @return array + * The list of HTML tags allowed by Xss::filter() with missing <font> tag. + */ + public static function getHtmlTagList() { + $allowed_tags = Xss::getHtmlTagList(); + $allowed_tags[] = 'font'; + return $allowed_tags; + } + +} diff --git a/web/modules/webform/src/Utility/WebformYaml.php b/web/modules/webform/src/Utility/WebformYaml.php index cc709ae5c4..c3eaf86b41 100644 --- a/web/modules/webform/src/Utility/WebformYaml.php +++ b/web/modules/webform/src/Utility/WebformYaml.php @@ -2,13 +2,42 @@ namespace Drupal\webform\Utility; +use Drupal\Component\Serialization\SerializationInterface; use Drupal\Core\Serialization\Yaml; -use Symfony\Component\Yaml\Unescaper; +use Symfony\Component\Yaml\Dumper; +use Symfony\Component\Yaml\Yaml as SymfonyYaml; /** * Provides YAML tidy function. */ -class WebformYaml { +class WebformYaml implements SerializationInterface { + + /** + * {@inheritdoc} + */ + public static function encode($data) { + $dumper = new Dumper(2); + $yaml = $dumper->dump($data, PHP_INT_MAX, 0, SymfonyYaml::DUMP_EXCEPTION_ON_INVALID_TYPE | SymfonyYaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + + // Remove return after array delimiter. + $yaml = preg_replace('#((?:\n|^)[ ]*-)\n[ ]+(\w|[\'"])#', '\1 \2', $yaml); + + return trim($yaml); + } + + /** + * {@inheritdoc} + */ + public static function decode($raw) { + return Yaml::decode($raw); + } + + /** + * {@inheritdoc} + */ + public static function getFileExtension() { + return 'yml'; + } /** * Determine if string is valid YAML. @@ -16,7 +45,7 @@ class WebformYaml { * @param string $yaml * A YAML string. * - * @return boolean + * @return bool * TRUE if string is valid YAML. */ public static function isValid($yaml) { @@ -51,50 +80,17 @@ public static function validate($yaml) { * * @return string * The encoded data. + * + * @see https://www.drupal.org/project/drupal/issues/2844452 + * @see \Drupal\Component\Serialization\YamlSymfony::encode */ public static function tidy($yaml) { - static $unescaper; - if (!isset($unescaper)) { - $unescaper = new Unescaper(); - } - - // Remove return after array delimiter. - $yaml = preg_replace('#((?:\n|^)[ ]*-)\n[ ]+(\w|[\'"])#', '\1 \2', $yaml); - - // Support YAML newlines preserved syntax via pipe (|). - $lines = explode(PHP_EOL, $yaml); - foreach ($lines as $index => $line) { - if (empty($line) || strpos($line, '\n') === FALSE) { - continue; - } - - if (preg_match('/^([ ]*(?:- )?)([a-z_]+|\'[^\']+\'|"[^"]+"): (\'|")(.+)\3$/', $line, $match)) { - $prefix = $match[1]; - $indent = str_repeat(' ', strlen($prefix)); - $name = $match[2]; - $quote = $match[3]; - $value = $match[4]; - - if ($quote == "'") { - $value = rtrim($unescaper->unescapeSingleQuotedString($value)); - } - else { - $value = rtrim($unescaper->unescapeDoubleQuotedString($value)); - } - - if (strpos($value, '<') === FALSE) { - $lines[$index] = $prefix . $name . ": |\n$indent " . str_replace(PHP_EOL, "\n$indent ", $value); - } - else { - $value = preg_replace('~\R~u', PHP_EOL, $value); - $value = preg_replace('#\s*</p>#', '</p>', $value); - $value = str_replace(PHP_EOL, "\n$indent ", $value); - $lines[$index] = $prefix . $name . ": |\n$indent " . $value; - } - } - } - $yaml = implode(PHP_EOL, $lines); - return trim($yaml); + // Converting carriage returns (\r\n) to basic returns (\n). + // [Yaml] don't split lines on carriage returns when dumping #25864. + // @see https://github.com/symfony/symfony/pull/25864 + $yaml = str_replace('\r\n', '\n', $yaml); + $data = self::decode($yaml); + return self::encode($data); } } diff --git a/web/modules/webform/src/WebformAccessRulesManager.php b/web/modules/webform/src/WebformAccessRulesManager.php new file mode 100644 index 0000000000..feae2f058c --- /dev/null +++ b/web/modules/webform/src/WebformAccessRulesManager.php @@ -0,0 +1,193 @@ +<?php + +namespace Drupal\webform; + +use Drupal\Component\Utility\SortArray; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\webform\Access\WebformAccessResult; + +/** + * The webform access rules manager service. + */ +class WebformAccessRulesManager implements WebformAccessRulesManagerInterface { + + use StringTranslationTrait; + + /** + * Module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * WebformAccessRulesManager constructor. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * Module handler service. + */ + public function __construct(ModuleHandlerInterface $module_handler) { + $this->moduleHandler = $module_handler; + } + + /** + * {@inheritdoc} + */ + public function checkWebformAccess($operation, AccountInterface $account, WebformInterface $webform) { + $access_rules = $this->getAccessRules($webform); + $cache_per_user = $this->cachePerUser($access_rules); + + $condition = $this->checkAccessRules($operation, $account, $access_rules); + return WebformAccessResult::allowedIf($condition, $webform, $cache_per_user); + } + + /** + * {@inheritdoc} + */ + public function checkWebformSubmissionAccess($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission) { + $webform = $webform_submission->getWebform(); + + $access_rules = $this->getAccessRules($webform); + $cache_per_user = $this->cachePerUser($access_rules); + + // Check operation. + if ($this->checkAccessRules($operation, $account, $access_rules)) { + return WebformAccessResult::allowed($webform_submission, $cache_per_user); + } + + // Check *_own operation. + if ($webform_submission->isOwner($account) + && isset($access_rules[$operation . '_own']) + && $this->checkAccessRule($access_rules[$operation . '_own'], $account)) { + return WebformAccessResult::allowed($webform_submission, $cache_per_user); + } + + // Check *_any operation. + if (isset($access_rules[$operation . '_any']) + && $this->checkAccessRule($access_rules[$operation . '_any'], $account)) { + return WebformAccessResult::allowed($webform_submission, $cache_per_user); + } + + return WebformAccessResult::neutral($webform_submission, $cache_per_user); + } + + /****************************************************************************/ + // Get access rules methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function getDefaultAccessRules() { + $access_rules = []; + + foreach ($this->getAccessRulesInfo() as $access_rule => $info) { + $access_rules[$access_rule] = [ + 'roles' => $info['roles'], + 'users' => $info['users'], + 'permissions' => $info['permissions'], + ]; + } + + return $access_rules; + } + + /** + * {@inheritdoc} + */ + public function getAccessRulesInfo() { + $access_rules = $this->moduleHandler->invokeAll('webform_access_rules'); + $this->moduleHandler->alter('webform_access_rules', $access_rules); + + // Set access rule default values. + foreach ($access_rules as $access_rule => $info) { + $access_rules[$access_rule] += [ + 'title' => NULL, + 'description' => NULL, + 'weight' => 0, + 'roles' => [], + 'users' => [], + 'permissions' => [], + ]; + } + + uasort($access_rules, [SortArray::class, 'sortByWeightElement']); + + return $access_rules; + } + + /** + * {@inheritdoc} + */ + public function getAccessRules(WebformInterface $webform) { + return $webform->getAccessRules() + $this->getDefaultAccessRules(); + } + + /****************************************************************************/ + // Check access rules methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function checkAccessRules($operation, AccountInterface $account, array $access_rules) { + // Check administer access rule and grant full access to user. + if ($this->checkAccessRule($access_rules['administer'], $account)) { + return TRUE; + } + + // Check operation. + if (isset($access_rules[$operation]) + && $this->checkAccessRule($access_rules[$operation], $account)) { + return TRUE; + } + + return FALSE; + } + + /** + * Checks an access rule against a user account's roles and id. + * + * @param array $access_rule + * An access rule. + * @param \Drupal\Core\Session\AccountInterface $account + * The user session for which to check access. + * + * @return bool + * Returns a TRUE if access is allowed. + * + * @see \Drupal\webform\Plugin\WebformElementBase::checkAccessRule + */ + protected function checkAccessRule(array $access_rule, AccountInterface $account) { + if (!empty($access_rule['roles']) && array_intersect($access_rule['roles'], $account->getRoles())) { + return TRUE; + } + elseif (!empty($access_rule['users']) && in_array($account->id(), $access_rule['users'])) { + return TRUE; + } + elseif (!empty($access_rule['permissions'])) { + foreach ($access_rule['permissions'] as $permission) { + if ($account->hasPermission($permission)) { + return TRUE; + } + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function cachePerUser(array $access_rules) { + foreach ($access_rules as $access_rule) { + if (!empty($access_rule['users'])) { + return TRUE; + } + } + return FALSE; + } + +} diff --git a/web/modules/webform/src/WebformAccessRulesManagerInterface.php b/web/modules/webform/src/WebformAccessRulesManagerInterface.php new file mode 100644 index 0000000000..ce5bc6b1d0 --- /dev/null +++ b/web/modules/webform/src/WebformAccessRulesManagerInterface.php @@ -0,0 +1,119 @@ +<?php + +namespace Drupal\webform; + +use Drupal\Core\Session\AccountInterface; + +/** + * Interface of webform access rules manager. + */ +interface WebformAccessRulesManagerInterface { + + /** + * Check if operation is allowed through access rules for a given webform. + * + * @param string $operation + * Operation to check. + * @param \Drupal\Core\Session\AccountInterface $account + * Account who is requesting the operation. + * @param \Drupal\webform\WebformInterface $webform + * Webform on which the operation is requested. + * + * @return \Drupal\Core\Access\AccessResultInterface + * Access result. + */ + public function checkWebformAccess($operation, AccountInterface $account, WebformInterface $webform); + + /** + * Check if operation is allowed through access rules for a submission. + * + * @param string $operation + * Operation to check. + * @param \Drupal\Core\Session\AccountInterface $account + * Account who is requesting the operation. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * Webform submission on which the operation is requested. + * + * @return \Drupal\Core\Access\AccessResultInterface + * Access result. + */ + public function checkWebformSubmissionAccess($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission); + + /****************************************************************************/ + // Get access rules methods. + /****************************************************************************/ + + /** + * Returns the webform default access rules. + * + * @return array + * A structured array containing all the webform default access rules. + */ + public function getDefaultAccessRules(); + + /** + * Retrieve a list of access rules from a webform. + * + * @param \Drupal\webform\WebformInterface $webform + * Webform whose access rules to retrieve. + * + * @return array + * Associative array of access rules contained in the provided webform. Keys + * are operation names whereas values are sub arrays with the following + * structure: + * - roles: (array) Array of roles that should have access to this operation + * - users: (array) Array of UIDs that should have access to this operation + * - permissions: (array) Array of permissions that should grant access to + * this operation + */ + public function getAccessRules(WebformInterface $webform); + + /****************************************************************************/ + // Check access rules methods. + /****************************************************************************/ + + /** + * Check access for a given operation and set of access rules. + * + * @param string $operation + * Operation that is being requested. + * @param \Drupal\Core\Session\AccountInterface $account + * Account that is requesting access to the operation. + * @param array $access_rules + * A set of access rules to check against. + * + * @return bool + * TRUE if access is allowed and FALSE is access is denied. + */ + public function checkAccessRules($operation, AccountInterface $account, array $access_rules); + + /** + * Collect metadata on known access rules. + * + * @return array + * Array that describes all known access rules. It will be keyed by access + * rule machine-name and will contain sub arrays with the following + * structure: + * - title: (string) Human-friendly translated string that describes the + * meaning of this access rule. + * - description: (array) Renderable array that explains what this access rule + * stands for. Defaults to an empty array. + * - roles: (string[]) Array of role IDs that should be granted this access + * rule by default. Defaults to an empty array. + * - permissions: (string[]) Array of permissions that should be granted this + * access rule by default. Defaults to an empty array. + */ + public function getAccessRulesInfo(); + + /** + * Determine if access rules should be cached per user. + * + * @param array $access_rules + * A set of access rules. + * + * @return bool + * TRUE if access rules should be cached per user. + */ + public function cachePerUser(array $access_rules); + +} diff --git a/web/modules/webform/src/WebformAddonsManager.php b/web/modules/webform/src/WebformAddonsManager.php index 2ba8cfd7c5..3f5e3566d2 100644 --- a/web/modules/webform/src/WebformAddonsManager.php +++ b/web/modules/webform/src/WebformAddonsManager.php @@ -120,6 +120,10 @@ public function getCategories() { protected function initProjects() { $projects = []; + /**************************************************************************/ + // Config. + /**************************************************************************/ + // Config: Drush CMI tools. $projects['drush_cmi_tools'] = [ 'title' => $this->t('Drush CMI tools'), @@ -128,10 +132,18 @@ protected function initProjects() { 'category' => 'config', ]; + // Config: Config Entity Revisions. + $projects['config_entity_revisions'] = [ + 'title' => $this->t('Config Entity Revisions'), + 'description' => $this->t('Provides an API for augmenting Configuration entities in Drupal 8.5 and later with revision and moderation support.'), + 'url' => Url::fromUri('https://www.drupal.org/project/config_entity_revisions'), + 'category' => 'config', + ]; + // Config: Configuration Ignore. $projects['config_ignore'] = [ 'title' => $this->t('Config Ignore'), - 'description' => $this->t('Ignore certain configuration during import'), + 'description' => $this->t('Ignore certain configuration during import.'), 'url' => Url::fromUri('https://www.drupal.org/project/config_ignore'), 'category' => 'config', ]; @@ -142,17 +154,69 @@ protected function initProjects() { 'description' => $this->t('Provides configuration filter for importing and exporting split config.'), 'url' => Url::fromUri('https://www.drupal.org/project/config_split'), 'category' => 'config', + ]; + + // Config: Multiline config. + $projects['multiline_config'] = [ + 'title' => $this->t('Multiline config'), + 'description' => $this->t('Allows configuration strings to be exported as multiline instead of one long single line.'), + 'url' => Url::fromUri('https://www.drupal.org/project/multiline_config'), + 'category' => 'config', + ]; + + // Config: Webform Config Ignore. + $projects['webform_config_ignore'] = [ + 'title' => $this->t('Webform Config Ignore'), + 'description' => $this->t('Adds a filter to configuration import and export to skip webforms and webform options.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_config_ignore'), + 'category' => 'config', + ]; + + // Config: Webform Config Key Value. + $projects[' webform_config_key_value'] = [ + 'title' => $this->t('Webform Config Key Value'), + 'description' => $this->t('Use the KeyValueStorage to save webform config instead of yaml config storage, allowing webforms to be treated more like content than configuration and are excluded from the configuration imports/exports.'), + 'url' => Url::fromUri('https://www.drupal.org/sandbox/thtas/2994250'), + 'category' => 'config', + ]; + + /**************************************************************************/ + // Element. + /**************************************************************************/ + + // Element: Address. + $projects['address'] = [ + 'title' => $this->t('Address'), + 'description' => $this->t('Provides functionality for storing, validating and displaying international postal addresses.'), + 'url' => Url::fromUri('https://www.drupal.org/project/address'), + 'category' => 'element', 'recommended' => TRUE, ]; + // Element: Loqate. + $projects['loqate'] = [ + 'title' => $this->t('Loqate'), + 'description' => $this->t('Provides the webform element called Address Loqate which integration with Loqate (previously PCA/Addressy) address lookup.'), + 'url' => Url::fromUri('https://www.drupal.org/project/loqate'), + 'category' => 'element', + ]; + + // Element: Webform Belgian National Insurance Number. + $projects['webform_rrn_nrn'] = [ + 'title' => $this->t('Webform Belgian National Insurance Number'), + 'description' => $this->t('Provides webform fieldtype for the Belgian National Insurance Number.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_rrn_nrn'), + 'category' => 'element', + ]; + // Element: Webform Composite Tools. $projects['webform_composite'] = [ 'title' => $this->t('Webform Composite Tools'), - 'description' => $this->t("Provides a reusable composite element for use on webforms."), + 'description' => $this->t('Provides a reusable composite element for use on webforms.'), 'url' => Url::fromUri('https://www.drupal.org/project/webform_composite'), 'category' => 'element', ]; - + // Element: Webform Checkboxes Table. $projects['webform_checkboxes_table'] = [ 'title' => $this->t('Webform Checkboxes Table'), @@ -169,11 +233,27 @@ protected function initProjects() { 'category' => 'element', ]; + // Element: Webform DropzoneJS. + $projects['webform_dropzonejs'] = [ + 'title' => $this->t('Webform DropzoneJS'), + 'description' => $this->t('Creates a new DropzoneJS element that you can add to webforms.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_dropzonejs'), + 'category' => 'element', + ]; + // Element: Webform Handsontable. $projects['handsontable_yml_webform'] = [ 'title' => $this->t('Webform Handsontable'), - 'description' => $this->t("Allows both the Drupal Form API and the Drupal 8 Webforms module to use the Excel-like Handsontable library."), - 'url' => Url::fromUri('https://www.drupal.org/handsontable_yml_webform'), + 'description' => $this->t('Allows both the Drupal Form API and the Drupal 8 Webforms module to use the Excel-like Handsontable library.'), + 'url' => Url::fromUri('https://www.drupal.org/project/handsontable_yml_webform'), + 'category' => 'element', + ]; + + // Element: Webform IBAN field . + $projects['webform_iban_field'] = [ + 'title' => $this->t('Webform IBAN field '), + 'description' => $this->t('Provides an IBAN Field to collect a valid IBAN number.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_iban_field'), 'category' => 'element', ]; @@ -193,6 +273,22 @@ protected function initProjects() { 'category' => 'element', ]; + // Element: Range Slider. + $projects['range_slider'] = [ + 'title' => $this->t('Range Slider'), + 'description' => $this->t('Integration with http://rangeslider.js.org.'), + 'url' => Url::fromUri('https://github.com/baikho/RangeSlider'), + 'category' => 'element', + ]; + + // Element: Webform RUT. + $projects['webform_rut'] = [ + 'title' => $this->t('Webform RUT'), + 'description' => $this->t("Provides a RUT (A unique identification number assigned to natural or legal persons of Chile) element."), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_rut'), + 'category' => 'element', + ]; + // Element: Webform Score. $projects['webform_score'] = [ 'title' => $this->t('Webform Score'), @@ -201,22 +297,161 @@ protected function initProjects() { 'category' => 'element', ]; + // Element: Webform Select Collection. + $projects['webform_select_collection'] = [ + 'title' => $this->t('Webform Select Collection'), + 'description' => $this->t('Provides a webform element that groups multiple select elements into single collection.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_select_collection'), + 'category' => 'element', + ]; + // Element: Webform Simple Hierarchical Select. $projects['webform_shs'] = [ 'title' => $this->t('Webform Simple Hierarchical Select'), - 'description' => $this->t("Integrates Simple Hierarchical Select module with Webform."), + 'description' => $this->t('Integrates Simple Hierarchical Select module with Webform.'), 'url' => Url::fromUri('https://www.drupal.org/project/webform_shs'), 'category' => 'element', ]; + /**************************************************************************/ + // Enhancement. + /**************************************************************************/ + + // Enhancement: Formset. + $projects['formset'] = [ + 'title' => $this->t('Formset'), + 'description' => $this->t('Enables the creation of webform sets.'), + 'url' => Url::fromUri('https://github.com/simesy/formset'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Confirmation File. + $projects['webform_confirmation_file'] = [ + 'title' => $this->t('Webform Confirmation File'), + 'description' => $this->t('Provides a webform handler that streams the contents of a file to a user after completing a webform.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_confirmation_file'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Counter. + $projects['webform_counter'] = [ + 'title' => $this->t('Webform Counter'), + 'description' => $this->t('Provides Submissions Counter feature for webforms.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_counter'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Embed. + $projects['webform_embed'] = [ + 'title' => $this->t('Webform Embed'), + 'description' => $this->t('Allows you to embed webforms within an iframe on another site.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_embed'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Extra Field. + $projects['webform_extra_field'] = [ + 'title' => $this->t('Webform Extra Field'), + 'description' => $this->t('Provides an extra field for placing a webform in any entity display mode.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_extra_field'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Feedback. + $projects['webform_feedback'] = [ + 'title' => $this->t('Webform Feedback'), + 'description' => $this->t('Provides a feedback button for your website which allows you to gather customer/client feedback.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_feedback'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Pre-populate. + $projects['webform_prepopulate'] = [ + 'title' => $this->t('Webform Pre-populate'), + 'description' => $this->t('Pre-populate a Webform with an external data source without disclosing information via the URL.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_prepopulate'), + 'category' => 'enhancement', + ]; + + // Enhancement: Webform Protected Downloads. + $projects['webform_protected_downloads'] = [ + 'title' => $this->t('Webform Protected Downloads'), + 'description' => $this->t('Provides protected file downloads using webforms.'), + 'url' => Url::fromUri('https://github.com/timlovrecic/Webform-Protected-Downloads'), + 'category' => 'enhancement', + ]; + // Enhancement: Webform Wizard Full Title. $projects['webform_wizard_full_title'] = [ 'title' => $this->t('Webform Wizard Full Title'), - 'description' => $this->t('Extends functionality of Webform so on wizard forms, the title of the wizard page can override the form title'), + 'description' => $this->t('Extends functionality of Webform so on wizard forms, the title of the wizard page can override the form title.'), 'url' => Url::fromUri('https://www.drupal.org/project/webform_wizard_full_title'), 'category' => 'enhancement', ]; + /**************************************************************************/ + // Integrations. + /**************************************************************************/ + + // Integrations: Webform CiviCRM Integration. + $projects['webform_civicrm'] = [ + 'title' => $this->t('Webform CiviCRM Integration'), + 'description' => $this->t('A powerful, flexible, user-friendly form builder for CiviCRM.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_civicrm'), + 'category' => 'integration', + 'recommended' => TRUE, + ]; + + /**************************************************************************/ + + // Integrations: Ansible. + $projects['ansible'] = [ + 'title' => $this->t('Ansible'), + 'description' => $this->t('Run Ansible playbooks using a Webform handler.'), + 'url' => Url::fromUri('https://www.drupal.org/project/ansible'), + 'category' => 'integration', + ]; + + // Integrations: Commerce Webform Order. + $projects['commerce_webform_order'] = [ + 'title' => $this->t('Commerce Webform Order'), + 'description' => $this->t('Integrates Webform with Drupal Commerce and it allows creating orders with the submission data of a Webform via a Webform handler.'), + 'url' => Url::fromUri('https://www.drupal.org/project/commerce_webform_order'), + 'category' => 'integration', + ]; + + // Integrations: Druminate Webforms. + $projects['druminate'] = [ + 'title' => $this->t('Druminate Webforms'), + 'description' => $this->t('Allows editors to send webform submissions to Luminate Online Surveys.'), + 'url' => Url::fromUri('https://www.drupal.org/project/druminate'), + 'category' => 'integration', + ]; + + // Integrations: Webform Eloqua. + $projects['webform_eloqua'] = [ + 'title' => $this->t('Webform Eloqua'), + 'description' => $this->t('Integrates Drupal 8 Webforms with Oracle Eloqua.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_eloqua'), + 'category' => 'integration', + ]; + + // Integrations: GraphQL Webform. + $projects['graphql_webform'] = [ + 'title' => $this->t('GraphQL Webform'), + 'description' => $this->t('Provides GraphQL integration with the Webform module.'), + 'url' => Url::fromUri('https://github.com/duartegarin/graphql_webform'), + 'category' => 'integration', + ]; + + // Integrations: Headless Ninja React Webform. + $projects['hn-react-webform'] = [ + 'title' => $this->t('Headless Ninja React Webform'), + 'description' => $this->t('With this awesome React component, you can render complete Drupal Webforms in React. With validation, easy custom styling and a modern, clean interface.'), + 'url' => Url::fromUri('https://github.com/headless-ninja/hn-react-webform'), + 'category' => 'integration', + ]; + // Integration: Webform HubSpot. $projects['hubspot'] = [ 'title' => $this->t('Webform HubSpot'), @@ -233,6 +468,22 @@ protected function initProjects() { 'category' => 'integration', ]; + // Integrations: OpenInbound for Drupal. + $projects['openinbound'] = [ + 'title' => $this->t('OpenInbound for Drupal'), + 'description' => $this->t('OpenInbound tracks contacts and their interactions on websites.'), + 'url' => Url::fromUri('https://www.drupal.org/project/openinbound'), + 'category' => 'integration', + ]; + + // Integrations: Rules Webform. + $projects['rules_webform'] = [ + 'title' => $this->t('Rules Webform'), + 'description' => $this->t("Provides integration of 'Rules' and 'Webform' modules. It enables to get access to webform submission data from rules. Also it provides possibility of altering and removing webform submission data from rules."), + 'url' => Url::fromUri('https://www.drupal.org/project/rules_webform'), + 'category' => 'integration', + ]; + // Integration: Webform iContact. $projects['webform_icontact'] = [ 'title' => $this->t('Webform iContact'), @@ -241,6 +492,14 @@ protected function initProjects() { 'category' => 'integration', ]; + // Integration: Webform Cart. + $projects['webform_cart'] = [ + 'title' => $this->t('Webform Cart'), + 'description' => $this->t('Allows you to add products to a webform submission.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_cart'), + 'category' => 'integration', + ]; + // Integrations: Webform MailChimp. $projects['webform_mailchimp'] = [ 'title' => $this->t('Webform MailChimp'), @@ -297,14 +556,16 @@ protected function initProjects() { 'category' => 'integration', ]; - // Integrations: OpenInbound for Drupal. - $projects['openinbound'] = [ - 'title' => $this->t('OpenInbound for Drupal'), - 'description' => $this->t('OpenInbound tracks contacts and their interactions on websites.'), - 'url' => Url::fromUri('https://www.drupal.org/project/openinbound'), + // Integrations: Webform User Registration. + $projects['webform_user_registration'] = [ + 'title' => $this->t('Webform User Registration'), + 'description' => $this->t('Create a new user upon form submission.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_user_registration'), 'category' => 'integration', ]; + /**************************************************************************/ + // Integrations: Salesforce Web-to-Lead Webform Data Integration. $projects['sfweb2lead_webform'] = [ 'title' => $this->t('Salesforce Web-to-Lead Webform Data Integration'), @@ -313,6 +574,26 @@ protected function initProjects() { 'category' => 'integration', ]; + // Integrations: Salesforce Marketing Cloud API Integration. + $projects['marketing_cloud'] = [ + 'title' => $this->t('Salesforce Marketing Cloud API Integration'), + 'description' => $this->t('Gives Drupal the ability to communicate with Marketing Cloud.'), + 'url' => Url::fromUri('https://www.drupal.org/project/marketing_cloud'), + 'category' => 'integration', + ]; + + // Integrations: Salesforce: Webform to Salesforce Leads. + $projects['webform_to_leads'] = [ + 'title' => $this->t('Salesforce: Webform to Salesforce Leads'), + 'description' => $this->t('Extends the Webform module to allow the creation of a webform that feeds to your Salesforce.com Account'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_to_leads'), + 'category' => 'integration', + ]; + + /**************************************************************************/ + // Mail. + /**************************************************************************/ + // Mail: Mail System. $projects['mailsystem'] = [ 'title' => $this->t('Mail System'), @@ -321,6 +602,22 @@ protected function initProjects() { 'category' => 'mail', ]; + // Mail: Mail System: SendGrid Integration. + $projects['sendgrid_integration'] = [ + 'title' => $this->t('SendGrid Integration <em>(requires Mail System)</em>'), + 'description' => $this->t('Provides SendGrid Integration for the Drupal Mail System.'), + 'url' => Url::fromUri('https://www.drupal.org/project/sendgrid_integration'), + 'category' => 'mail', + ]; + + // Mail: Mail System: Swift Mailer. + $projects['swiftmailer'] = [ + 'title' => $this->t('Swift Mailer <em>(requires Mail System)</em>'), + 'description' => $this->t('Installs Swift Mailer as a mail system.'), + 'url' => Url::fromUri('https://www.drupal.org/project/swiftmailer'), + 'category' => 'mail', + ]; + // Mail: Webform Mass Email. $projects['webform_mass_email'] = [ 'title' => $this->t('Webform Mass Email'), @@ -329,6 +626,14 @@ protected function initProjects() { 'category' => 'mail', ]; + // Mail: Webform Send Multiple Emails. + $projects['webform_send_multiple_emails'] = [ + 'title' => $this->t('Webform Send Multiple Emails'), + 'description' => $this->t('Extends the Webform module Email Handler to send individual emails when multiple recipients are added to the email "to" field.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_send_multiple_emails'), + 'category' => 'mail', + ]; + // Mail: SMTP Authentication Support. $projects['smtp'] = [ 'title' => $this->t('SMTP Authentication Support'), @@ -337,6 +642,10 @@ protected function initProjects() { 'category' => 'mail', ]; + /**************************************************************************/ + // Multilingual. + /**************************************************************************/ + // Multilingual: Lingotek Translation. $projects['lingotek'] = [ 'title' => $this->t('Lingotek Translation.'), @@ -345,6 +654,10 @@ protected function initProjects() { 'category' => 'multilingual', ]; + /**************************************************************************/ + // Migrate. + /**************************************************************************/ + // Migrate: Webform Migrate. $projects['webform_migrate'] = [ 'title' => $this->t('Webform Migrate'), @@ -354,6 +667,18 @@ protected function initProjects() { 'recommended' => TRUE, ]; + // Migrate: Webform Submission Import. + $projects['webform_submission_import'] = [ + 'title' => $this->t('Webform Submission Import'), + 'description' => $this->t('Import CSV records into webform submissions data.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_submission_import'), + 'category' => 'migrate', + ]; + + /**************************************************************************/ + // Spam. + /**************************************************************************/ + // Spam: Antibot. $projects['antibot'] = [ 'title' => $this->t('Antibot'), @@ -361,6 +686,7 @@ protected function initProjects() { 'url' => Url::fromUri('https://www.drupal.org/project/antibot'), 'category' => 'spam', 'third_party_settings' => TRUE, + 'recommended' => TRUE, ]; // Spam: CAPTCHA. @@ -372,6 +698,18 @@ protected function initProjects() { 'recommended' => TRUE, ]; + // Spam: Honeypot. + $projects['honeypot'] = [ + 'title' => $this->t('Honeypot'), + 'description' => $this->t('Mitigates spam form submissions using the honeypot method.'), + 'url' => Url::fromUri('https://www.drupal.org/project/honeypot'), + 'category' => 'spam', + 'third_party_settings' => TRUE, + 'recommended' => TRUE, + ]; + + /**************************************************************************/ + // Spam: CleanTalk. $projects['cleantalk'] = [ 'title' => $this->t('CleanTalk'), @@ -380,13 +718,33 @@ protected function initProjects() { 'category' => 'spam', ]; - // Spam: Honeypot. - $projects['honeypot'] = [ - 'title' => $this->t('Honeypot'), - 'description' => $this->t('Mitigates spam form submissions using the honeypot method.'), - 'url' => Url::fromUri('https://www.drupal.org/project/honeypot'), + // Spam: Human Presence Form Protection. + $projects['hp'] = [ + 'title' => $this->t('Human Presence Form Protection'), + 'description' => $this->t('Human Presence is a fraud prevention and form protection service that uses multiple overlapping strategies to fight form spam.'), + 'url' => Url::fromUri('https://www.drupal.org/project/hp'), 'category' => 'spam', - 'third_party_settings' => TRUE, + ]; + + /**************************************************************************/ + // Submissions. + /**************************************************************************/ + + // Submissions: Webform Analysis. + $projects['webform_analysis'] = [ + 'title' => $this->t('Webform Analysis'), + 'description' => $this->t('Used to obtain statistics on the results of form submissions.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_analysis'), + 'category' => 'submission', + 'recommended' => TRUE, + ]; + + // Submissions: Webform Query. + $projects['webform_query'] = [ + 'title' => $this->t('Webform Query'), + 'description' => $this->t('Query webform submission data.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_query'), + 'category' => 'submission', 'recommended' => TRUE, ]; @@ -399,16 +757,9 @@ protected function initProjects() { 'recommended' => TRUE, ]; - // Submissions: Webform Analysis. - $projects['webform_analysis'] = [ - 'title' => $this->t('Webform Analysis'), - 'description' => $this->t('Used to obtain statistics on the results of form submissions.'), - 'url' => Url::fromUri('https://www.drupal.org/project/webform_analysis'), - 'category' => 'submission', - 'recommended' => TRUE, - ]; + /**************************************************************************/ - // Webform Invitation. + // Submissions: Webform Invitation. $projects['webform_invitation'] = [ 'title' => $this->t('Webform Invitation'), 'description' => $this->t('Allows you to restrict submissions to a webform by generating codes (which may then be distributed e.g. by email to participants).'), @@ -424,6 +775,14 @@ protected function initProjects() { 'category' => 'submission', ]; + // Submissions: Webform Queue. + $projects['webform_queue'] = [ + 'title' => $this->t('Webform Queue'), + 'description' => $this->t('Posts form submissions into a Drupal queue.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_queue'), + 'category' => 'submission', + ]; + // Submissions: Webform Sanitize. $projects['webform_sanitize'] = [ 'title' => $this->t('Webform Sanitize'), @@ -440,14 +799,34 @@ protected function initProjects() { 'category' => 'submission', ]; - // Submissions: Webform Queue. - $projects['webform_queue'] = [ - 'title' => $this->t('Webform Queue'), - 'description' => $this->t('Posts form submissions into a Drupal queue.'), - 'url' => Url::fromUri('https://www.drupal.org/project/webform_queue'), + // Submissions: Webform Submission Change History. + $projects['webform_submission_change_history'] = [ + 'title' => $this->t('Webform Submission Change History'), + 'description' => $this->t('Allows administrators to track notes on webform submissions.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_submission_change_history'), 'category' => 'submission', ]; + // Submissions: Webform Submissions Notification. + $projects['webform_digests'] = [ + 'title' => $this->t(' Webform Submissions Notification'), + 'description' => $this->t('Adds a daily digest email for webform submissions.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_digests'), + 'category' => 'submission', + ]; + + // Submissions: Webform Submission Files Download. + $projects['webform_submission_files_download'] = [ + 'title' => $this->t(' Webform Submission Files Download'), + 'description' => $this->t('Allows you to download files attached to a single submission'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_submission_files_download'), + 'category' => 'submission', + ]; + + /**************************************************************************/ + // REST. + /**************************************************************************/ + // REST: Webform REST. $projects['webform_rest'] = [ 'title' => $this->t('Webform REST'), @@ -456,21 +835,17 @@ protected function initProjects() { 'category' => 'rest', ]; - // Utility: Webform Encrypt. - $projects['wf_encrypt'] = [ - 'title' => $this->t('Webform Encrypt'), - 'description' => $this->t('Provides encryption for webform elements.'), - 'url' => Url::fromUri('https://www.drupal.org/project/webform_encrypt'), - 'category' => 'utility', + // REST: Webform JSON Schema. + $projects['webform_jsonschema'] = [ + 'title' => $this->t('Webform JSON Schema'), + 'description' => $this->t('Expose webforms as JSON Schema, UI Schema, and Form Data. Make webforms work with react-jsonschema-form.'), + 'url' => Url::fromUri('https://github.com/AmazeeLabs/webform_jsonschema'), + 'category' => 'rest', ]; - // Utility: Webform Ip Track. - $projects['webform_ip_track'] = [ - 'title' => $this->t('Webform Ip Track'), - 'description' => $this->t('Ip Location details as custom tokens to use in webform submission values.'), - 'url' => Url::fromUri('https://www.drupal.org/project/webform_ip_track'), - 'category' => 'utility', - ]; + /**************************************************************************/ + // Utility. + /**************************************************************************/ // Utility: IMCE. $projects['imce'] = [ @@ -478,7 +853,7 @@ protected function initProjects() { 'description' => $this->t('IMCE is an image/file uploader and browser that supports personal directories and quota.'), 'url' => Url::fromUri('https://www.drupal.org/project/imce'), 'category' => 'utility', - 'install' => TRUE, + 'install' => $this->t('The IMCE module makes it easier to update images to webforms and elements.'), 'recommended' => TRUE, ]; @@ -488,10 +863,56 @@ protected function initProjects() { 'description' => $this->t('Provides a user interface for the Token API and some missing core tokens.'), 'url' => Url::fromUri('https://www.drupal.org/project/token'), 'category' => 'utility', - 'install' => TRUE, + 'install' => $this->t('The Token module allows site builders to browser available webform-related tokens.'), 'recommended' => TRUE, ]; + /**************************************************************************/ + + // Utility: Googalytics Webform. + $projects['ga_webform'] = [ + 'title' => $this->t('Googalytics Webform'), + 'description' => $this->t('Provides integration for Webform into Googalytics module.'), + 'url' => Url::fromUri('https://www.drupal.org/project/ga_webform'), + 'category' => 'utility', + ]; + + // Utility: EU Cookie Compliance. + $projects['eu_cookie_compliance'] = [ + 'title' => $this->t('EU Cookie Compliance'), + 'description' => $this->t('This module aims at making the website compliant with the new EU cookie regulation.'), + 'url' => Url::fromUri('https://www.drupal.org/project/eu_cookie_compliance'), + 'category' => 'utility', + ]; + + // Utility: General Data Protection Regulation Compliance. + $projects['gdpr_compliance'] = [ + 'title' => $this->t('General Data Protection Regulation Compliance'), + 'description' => $this->t('Provides Basic GDPR Compliance use cases via form checkboxes, pop-up alert, and a policy page.'), + 'url' => Url::fromUri('https://www.drupal.org/project/gdpr_compliance'), + 'category' => 'utility', + ]; + + // Utility: Webform Encrypt. + $projects['wf_encrypt'] = [ + 'title' => $this->t('Webform Encrypt'), + 'description' => $this->t('Provides encryption for webform elements.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_encrypt'), + 'category' => 'utility', + ]; + + // Utility: Webform Ip Track. + $projects['webform_ip_track'] = [ + 'title' => $this->t('Webform Ip Track'), + 'description' => $this->t('Ip Location details as custom tokens to use in webform submission values.'), + 'url' => Url::fromUri('https://www.drupal.org/project/webform_ip_track'), + 'category' => 'utility', + ]; + + /**************************************************************************/ + // Validation. + /**************************************************************************/ + // Validation: Clientside Validation. $projects['clientside_validation'] = [ 'title' => $this->t('Clientside Validation'), @@ -501,6 +922,14 @@ protected function initProjects() { 'recommended' => TRUE, ]; + // Validation: Telephone Validation. + $projects['telephone_validation'] = [ + 'title' => $this->t('Telephone Validation'), + 'description' => $this->t('Provides validation for tel form element.'), + 'url' => Url::fromUri('https://www.drupal.org/project/telephone_validation'), + 'category' => 'validation', + ]; + // Validation: Validators. $projects['validators'] = [ 'title' => $this->t('Validators'), @@ -509,6 +938,10 @@ protected function initProjects() { 'category' => 'validation', ]; + /**************************************************************************/ + // Workflow. + /**************************************************************************/ + // Workflow: Maestro. $projects['maestro'] = [ 'title' => $this->t('Maestro Workflow Engine'), diff --git a/web/modules/webform/src/WebformContributeManager.php b/web/modules/webform/src/WebformContributeManager.php index 6375d59953..aa6051040c 100644 --- a/web/modules/webform/src/WebformContributeManager.php +++ b/web/modules/webform/src/WebformContributeManager.php @@ -333,7 +333,6 @@ public function getMembership() { return $membership; } - /** * {@inheritdoc} */ @@ -472,7 +471,7 @@ public function getStyle() { * The remote URI. * * @return mixed|null - * The returned data. Tequests to *.json files will be decoded. + * The returned data. Requests to *.json files will be decoded. */ protected function get($uri) { if (isset($this->cachedData[$uri])) { diff --git a/web/modules/webform/src/WebformEmailProvider.php b/web/modules/webform/src/WebformEmailProvider.php index 02a62dab10..097e8ef054 100644 --- a/web/modules/webform/src/WebformEmailProvider.php +++ b/web/modules/webform/src/WebformEmailProvider.php @@ -66,7 +66,7 @@ public function getModules() { public function check() { // Don't override the system.mail.interface.webform if the default interface // is the 'test_mail_collector'. - if ($this->configFactory->get('system.mail')->get('interface.default') == 'test_mail_collector') { + if ($this->configFactory->get('system.mail')->get('interface.default') === 'test_mail_collector') { return $this->uninstall(); } @@ -81,7 +81,7 @@ public function check() { // Finally, check if the default mail interface and see if it still uses the // php_mail. This check allow unknown contrib modules to handle sending // HTML emails. - if ($this->configFactory->get('system.mail')->get('interface.default') == 'php_mail') { + if ($this->configFactory->get('system.mail')->get('interface.default') === 'php_mail') { return $this->install(); } else { @@ -93,7 +93,7 @@ public function check() { * {@inheritdoc} */ public function installed() { - return ($this->configFactory->get('system.mail')->get('interface.webform') == 'webform_php_mail'); + return ($this->configFactory->get('system.mail')->get('interface.webform') === 'webform_php_mail'); } /** @@ -102,8 +102,10 @@ public function installed() { public function install() { $config = $this->configFactory->getEditable('system.mail'); $mail_plugins = $config->get('interface'); - $mail_plugins['webform'] = 'webform_php_mail'; - $config->set('interface', $mail_plugins)->save(); + if (!isset($mail_plugins['webform']) || $mail_plugins['webform'] !== 'webform_php_mail') { + $mail_plugins['webform'] = 'webform_php_mail'; + $config->set('interface', $mail_plugins)->save(); + } } /** @@ -112,8 +114,10 @@ public function install() { public function uninstall() { $config = $this->configFactory->getEditable('system.mail'); $mail_plugins = $config->get('interface'); - unset($mail_plugins['webform']); - $config->set('interface', $mail_plugins)->save(); + if (isset($mail_plugins['webform'])) { + unset($mail_plugins['webform']); + $config->set('interface', $mail_plugins)->save(); + } } /** diff --git a/web/modules/webform/src/WebformEntityAccessControlHandler.php b/web/modules/webform/src/WebformEntityAccessControlHandler.php index 897bd689f6..3fd0b54f1a 100644 --- a/web/modules/webform/src/WebformEntityAccessControlHandler.php +++ b/web/modules/webform/src/WebformEntityAccessControlHandler.php @@ -2,13 +2,14 @@ namespace Drupal\webform; -use Drupal\Core\Access\AccessResult; +use Drupal\Core\Access\AccessResultReasonInterface; use Drupal\Core\Entity\EntityAccessControlHandler; use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\webform\Access\WebformAccessResult; use Drupal\webform\Plugin\WebformSourceEntityManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -41,6 +42,13 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple */ protected $webformSourceEntityManager; + /** + * Webform access rules manager service. + * + * @var \Drupal\webform\WebformAccessRulesManagerInterface + */ + protected $accessRulesManager; + /** * WebformEntityAccessControlHandler constructor. * @@ -52,13 +60,16 @@ class WebformEntityAccessControlHandler extends EntityAccessControlHandler imple * The entity type manager. * @param \Drupal\webform\Plugin\WebformSourceEntityManagerInterface $webform_source_entity_manager * Webform source entity plugin manager. + * @param \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager + * Webform access rules manager service. */ - public function __construct(EntityTypeInterface $entity_type, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, WebformSourceEntityManagerInterface $webform_source_entity_manager) { + public function __construct(EntityTypeInterface $entity_type, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, WebformSourceEntityManagerInterface $webform_source_entity_manager, WebformAccessRulesManagerInterface $access_rules_manager) { parent::__construct($entity_type); $this->requestStack = $request_stack; $this->entityTypeManager = $entity_type_manager; $this->webformSourceEntityManager = $webform_source_entity_manager; + $this->accessRulesManager = $access_rules_manager; } /** @@ -69,7 +80,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $entity_type, $container->get('request_stack'), $container->get('entity_type.manager'), - $container->get('plugin.manager.webform.source_entity') + $container->get('plugin.manager.webform.source_entity'), + $container->get('webform.access_rules_manager') ); } @@ -77,11 +89,15 @@ public static function createInstance(ContainerInterface $container, EntityTypeI * {@inheritdoc} */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { - if ($account->hasPermission('create webform')) { - return AccessResult::allowed()->cachePerPermissions(); + // Check 'administer webform' and 'create webform' permissions. + if ($account->hasPermission('administer webform')) { + return WebformAccessResult::allowed(); + } + elseif ($account->hasPermission('create webform')) { + return WebformAccessResult::allowed(); } else { - return parent::checkCreateAccess($account, $context, $entity_bundle); + return WebformAccessResult::neutral(); } } @@ -90,100 +106,185 @@ protected function checkCreateAccess(AccountInterface $account, array $context, */ public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\webform\WebformInterface $entity */ - // Check 'view' using 'create' custom webform submission access rules. - // Viewing a webform is the same as creating a webform submission. - if ($operation == 'view') { - return AccessResult::allowed(); + + // Check 'administer webform' permission. + if ($account->hasPermission('administer webform')) { + return WebformAccessResult::allowed(); } - $uid = $entity->getOwnerId(); - $is_owner = ($account->isAuthenticated() && $account->id() == $uid); - // Check if 'update' or 'delete' of 'own' or 'any' webform is allowed. + // Check 'administer' access rule. + if ($account->isAuthenticated()) { + $administer_access_result = $this->accessRulesManager->checkWebformAccess('administer', $account, $entity); + if ($administer_access_result->isAllowed()) { + return $administer_access_result; + } + } + + $is_owner = ($account->id() == $entity->getOwnerId()); + + // Check 'view' operation use 'submission_create' when viewing rendered + // HTML webform or use access 'configuration' when requesting a + // webform's configuration via REST or JSON API. + // @see https://www.drupal.org/project/webform/issues/2956771 + if ($operation === 'view') { + // Check is current request if for HTML. + $is_html = ($this->requestStack->getCurrentRequest()->getRequestFormat() === 'html'); + // Make sure JSON API 1.x requests format which is 'html' is + // detected properly. + // @see https://www.drupal.org/project/jsonapi/issues/2877584 + $is_jsonapi = (strpos($this->requestStack->getCurrentRequest()->getPathInfo(), '/jsonapi/') === 0) ? TRUE : FALSE; + if ($is_html && !$is_jsonapi) { + $access_result = $this->accessRulesManager->checkWebformAccess('create', $account, $entity); + } + else { + if ($account->hasPermission('access any webform configuration') || ($account->hasPermission('access own webform configuration') && $is_owner)) { + $access_result = WebformAccessResult::allowed($entity, TRUE); + } + else { + $access_result = $this->accessRulesManager->checkWebformAccess('configuration', $account, $entity); + } + } + if ($access_result instanceof AccessResultReasonInterface) { + $access_result->setReason('Access to webform configuration is required.'); + } + return $access_result->addCacheContexts(['url.path', 'request_format']); + } + + // Check if 'update', or 'delete' of 'own' or 'any' webform is allowed. if ($account->isAuthenticated()) { - $has_administer = $entity->checkAccessRules('administer', $account); switch ($operation) { case 'test': case 'update': - if ($has_administer->isAllowed() || $account->hasPermission('edit any webform') || ($account->hasPermission('edit own webform') && $is_owner)) { - return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity)->addCacheableDependency($has_administer); + if ($account->hasPermission('edit any webform') || ($account->hasPermission('edit own webform') && $is_owner)) { + return WebformAccessResult::allowed($entity, TRUE); } break; case 'duplicate': - if ($has_administer->isAllowed() || $account->hasPermission('create webform') && ($entity->isTemplate() || ($account->hasPermission('edit any webform') || ($account->hasPermission('edit own webform') && $is_owner)))) { - return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity)->addCacheableDependency($has_administer); + if ($account->hasPermission('create webform') && ($entity->isTemplate() || ($account->hasPermission('edit any webform') || ($account->hasPermission('edit own webform') && $is_owner)))) { + return WebformAccessResult::allowed($entity, TRUE); } break; case 'delete': - if ($has_administer->isAllowed() || $account->hasPermission('delete any webform') || ($account->hasPermission('delete own webform') && $is_owner)) { - return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity)->addCacheableDependency($has_administer); + if ($account->hasPermission('delete any webform') || ($account->hasPermission('delete own webform') && $is_owner)) { + return WebformAccessResult::allowed($entity, TRUE); } break; } } - // Check test operation. - if ($operation == 'test') { - $access_rules = $entity->checkAccessRules($operation, $account); - if ($access_rules->isAllowed()) { - return AccessResult::allowed()->cachePerPermissions()->cachePerUser()->addCacheableDependency($access_rules); - } + // Check webform access rules. + $rules_access_result = $this->accessRulesManager->checkWebformAccess($operation, $account, $entity); + if ($rules_access_result->isAllowed()) { + return $rules_access_result; } // Check submission_* operation. if (strpos($operation, 'submission_') === 0) { - // Allow users with 'view any webform submission' or - // 'administer webform submission' to view all submissions. - if ($operation == 'submission_view_any' && ($account->hasPermission('view any webform submission') || $account->hasPermission('administer webform submission'))) { - return AccessResult::allowed()->cachePerPermissions(); + // Grant user with administer webform submission access to do whatever he + // likes on the submission operations. + if ($account->hasPermission('administer webform submission')) { + return WebformAccessResult::allowed(); } - // Allow users with 'view own webform submission' to view own submissions. - if ($account->hasPermission('view own webform submission') && $is_owner) { - return AccessResult::allowed()->cachePerUser()->addCacheableDependency($entity); + // Allow users with 'view any webform submission' or + // 'administer webform submission' to view all submissions. + if ($operation === 'submission_view_any' && ($account->hasPermission('view any webform submission') || $account->hasPermission('administer webform submission'))) { + return WebformAccessResult::allowed(); } // Allow users with 'view own webform submission' to view own submissions. - if ($operation == 'submission_view_own' && $account->hasPermission('view own webform submission')) { - return AccessResult::allowed()->cachePerPermissions(); + if ($operation === 'submission_view_own' && $account->hasPermission('view own webform submission')) { + return WebformAccessResult::allowed(); } - // Allow (secure) token to bypass submission page and create access controls. if (in_array($operation, ['submission_page', 'submission_create'])) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ + $submission_storage = $this->entityTypeManager->getStorage('webform_submission'); + + // Check limit total unique access. + // @see \Drupal\webform\WebformSubmissionForm::setEntity + if ($entity->getSetting('limit_total_unique')) { + $source_entity = $this->webformSourceEntityManager->getSourceEntity('webform'); + $last_submission = $submission_storage->getLastSubmission($entity, $source_entity, NULL, ['in_draft' => FALSE]); + if ($last_submission && $last_submission->access('update')) { + return WebformAccessResult::allowed($last_submission); + } + } + + // Check limit user unique access. + // @see \Drupal\webform\WebformSubmissionForm::setEntity + if ($entity->getSetting('limit_user_unique')) { + // Require user to be authenticated to access a unique submission. + if (!$account->isAuthenticated()) { + return WebformAccessResult::forbidden($entity); + } + $source_entity = $this->webformSourceEntityManager->getSourceEntity('webform'); + $last_submission = $submission_storage->getLastSubmission($entity, $source_entity, $account, ['in_draft' => FALSE]); + if ($last_submission && $last_submission->access('update')) { + return WebformAccessResult::allowed($last_submission); + } + } + + // Allow (secure) token to bypass submission page and create access controls. $token = $this->requestStack->getCurrentRequest()->query->get('token'); if ($token && $entity->isOpen()) { - /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ - $submission_storage = $this->entityTypeManager->getStorage('webform_submission'); - - $source_entity = $this->webformSourceEntityManager->getSourceEntity(['webform']); + $source_entity = $this->webformSourceEntityManager->getSourceEntity('webform'); if ($submission = $submission_storage->loadFromToken($token, $entity, $source_entity)) { - return AccessResult::allowed()->addCacheableDependency($submission)->addCacheableDependency($entity)->addCacheContexts(['url']); + return WebformAccessResult::allowed($submission) + ->addCacheContexts(['url']); } } } - // Completely block access to a template if the user can't create new - // Webforms. - if ($operation == 'submission_page' && $entity->isTemplate()) { + // The "page" operation is the same as "create" but requires that the + // Webform is allowed to be displayed as dedicated page. + // Used by the 'entity.webform.canonical' route. + if ($operation === 'submission_page') { + // Completely block access to a template if the user can't create new + // Webforms. $create_access = $entity->access('create', $account, TRUE); - if (!$create_access->isAllowed()) { - return AccessResult::forbidden()->addCacheableDependency($entity)->addCacheableDependency($create_access); + if ($entity->isTemplate() && !$create_access->isAllowed()) { + return WebformAccessResult::forbidden($entity) + ->addCacheableDependency($create_access); } + + // Block access if the webform does not have a page URL. + if (!$entity->getSetting('page')) { + $source_entity = $this->webformSourceEntityManager->getSourceEntity('webform'); + if (!$source_entity) { + return WebformAccessResult::forbidden($entity); + } + } + } + + // Convert submission 'page' to corresponding 'create' access rule. + $submission_operation = str_replace('submission_page', 'submission_create', $operation); + // Remove 'submission_*' prefix. + $submission_operation = str_replace('submission_', '', $submission_operation); + + // Check webform submission access rules. + $submission_access_result = $this->accessRulesManager->checkWebformAccess($submission_operation, $account, $entity); + if ($submission_access_result->isAllowed()) { + return $submission_access_result; } - // Check custom webform submission access rules. - $update_access = $this->checkAccess($entity, 'update', $account); - $access_rules = $entity->checkAccessRules(str_replace('submission_', '', $operation), $account); - if ($update_access->isAllowed() || $access_rules->isAllowed()) { - return AccessResult::allowed()->addCacheableDependency($update_access)->addCacheableDependency($access_rules); + // Check webform 'update' access. + $update_access_result = $this->checkAccess($entity, 'update', $account); + if ($update_access_result->isAllowed()) { + return $update_access_result; } } - $access_result = parent::checkAccess($entity, $operation, $account); - // Make sure the webform is added as a cache dependency. - $access_result->addCacheableDependency($entity); - return $access_result; + // NOTE: Not calling parent::checkAccess(). + // @see \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess + if ($operation === 'delete' && $entity->isNew()) { + return WebformAccessResult::forbidden($entity); + } + else { + return WebformAccessResult::neutral($entity); + } } } diff --git a/web/modules/webform/src/WebformEntityAddForm.php b/web/modules/webform/src/WebformEntityAddForm.php index 6e5cedeba4..1de04f9eb7 100644 --- a/web/modules/webform/src/WebformEntityAddForm.php +++ b/web/modules/webform/src/WebformEntityAddForm.php @@ -87,6 +87,17 @@ public function form(array $form, FormStateInterface $form_state) { '#empty_option' => $this->t('- None -'), '#default_value' => $webform->get('category'), ]; + $form['status'] = [ + '#type' => 'radios', + '#title' => $this->t('Status'), + '#default_value' => $webform->get('status'), + '#options' => [ + WebformInterface::STATUS_OPEN => $this->t('Open'), + WebformInterface::STATUS_CLOSED => $this->t('Closed'), + ], + '#options_display' => 'side_by_side', + ]; + $form = $this->protectBundleIdElement($form); return parent::form($form, $form_state); @@ -134,7 +145,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $t_args = ['%label' => $webform->label()]; $this->logger('webform')->notice('Webform @label created.', $context); - drupal_set_message($this->t('Webform %label created.', $t_args)); + $this->messenger()->addStatus($this->t('Webform %label created.', $t_args)); } } diff --git a/web/modules/webform/src/WebformEntityDeleteForm.php b/web/modules/webform/src/WebformEntityDeleteForm.php index 7bb560e276..3f07353eeb 100644 --- a/web/modules/webform/src/WebformEntityDeleteForm.php +++ b/web/modules/webform/src/WebformEntityDeleteForm.php @@ -2,38 +2,30 @@ namespace Drupal\webform; -use Drupal\Core\Entity\EntityDeleteForm; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; -use Drupal\webform\Form\WebformDialogFormTrait; +use Drupal\webform\Form\WebformConfigEntityDeleteFormBase; /** * Provides a delete webform form. */ -class WebformEntityDeleteForm extends EntityDeleteForm { - - use WebformDialogFormTrait; +class WebformEntityDeleteForm extends WebformConfigEntityDeleteFormBase { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); - $form['confirm'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Yes, I want to delete this webform.'), - '#required' => TRUE, - '#weight' => 10, + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove configuration'), + $this->t('Delete all related submissions'), + $this->t('Affect any fields or nodes which reference this webform'), + ], + ], ]; - - return $this->buildDialogConfirmForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function getRedirectUrl() { - return Url::fromRoute('entity.webform.collection'); } } diff --git a/web/modules/webform/src/WebformEntityElementsForm.php b/web/modules/webform/src/WebformEntityElementsForm.php index 38de680b3d..1fde59422f 100644 --- a/web/modules/webform/src/WebformEntityElementsForm.php +++ b/web/modules/webform/src/WebformEntityElementsForm.php @@ -110,7 +110,7 @@ public function form(array $form, FormStateInterface $form_state) { '#element_validate' => ['::validateElementsYaml'], ]; - $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); + $form['token_tree_link'] = $this->tokenManager->buildTreeElement(); $this->tokenManager->elementValidate($form); @@ -159,7 +159,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { if ($messages = $this->elementsValidator->validate($webform)) { $form_state->setErrorByName('elements'); foreach ($messages as $message) { - drupal_set_message($message, 'error'); + $this->messenger()->addError($message); } } } @@ -179,7 +179,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $t_args = ['%label' => $webform->label()]; $this->logger('webform')->notice('Webform @label elements saved.', $context); - drupal_set_message($this->t('Webform %label elements saved.', $t_args)); + $this->messenger()->addStatus($this->t('Webform %label elements saved.', $t_args)); } /****************************************************************************/ @@ -199,7 +199,7 @@ protected function getElementsWithoutWebformTypePrefix($value) { } $this->removeWebformTypePrefixRecursive($elements); - return WebformYaml::tidy(Yaml::encode($elements)); + return WebformYaml::encode($elements); } /** @@ -236,7 +236,7 @@ protected function getElementsWithWebformTypePrefix($value) { } $this->addWebformTypePrefixRecursive($elements); - return WebformYaml::tidy(Yaml::encode($elements)); + return WebformYaml::encode($elements); } /** diff --git a/web/modules/webform/src/WebformEntityElementsValidator.php b/web/modules/webform/src/WebformEntityElementsValidator.php index 94deef795d..294bf5861f 100644 --- a/web/modules/webform/src/WebformEntityElementsValidator.php +++ b/web/modules/webform/src/WebformEntityElementsValidator.php @@ -6,7 +6,6 @@ use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Serialization\Yaml; -use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Form\FormState; use Drupal\Core\Url; @@ -56,6 +55,13 @@ class WebformEntityElementsValidator implements WebformEntityElementsValidatorIn */ protected $originalElements; + /** + * An array of element keys. + * + * @var array + */ + protected $elementKeys; + /** * The 'renderer' service. * @@ -84,6 +90,19 @@ class WebformEntityElementsValidator implements WebformEntityElementsValidatorIn */ protected $formBuilder; + /** + * Element keys/names that are reserved. + * + * @var array + */ + public static $reservedNames = [ + 'add', + 'form_build_id', + 'form_id', + 'form_token', + 'op', + ]; + /** * Constructs a WebformEntityElementsValidator object. * @@ -106,51 +125,73 @@ public function __construct(RendererInterface $renderer, WebformElementManagerIn /** * {@inheritdoc} */ - public function validate(WebformInterface $webform) { + public function validate(WebformInterface $webform, array $options = []) { + $options += [ + 'required' => TRUE, + 'yaml' => TRUE, + 'array' => TRUE, + 'names' => TRUE, + 'properties' => TRUE, + 'submissions' => TRUE, + 'hierarchy' => TRUE, + 'rendering' => TRUE, + ]; + $this->webform = $webform; $this->elementsRaw = $webform->getElementsRaw(); $this->originalElementsRaw = $webform->getElementsOriginalRaw(); // Validate required. - if ($message = $this->validateRequired()) { + if ($options['required'] && ($message = $this->validateRequired())) { return [$message]; } + // Validate contain valid YAML. - if ($message = $this->validateYaml()) { + if ($options['yaml'] && ($message = $this->validateYaml())) { return [$message]; } $this->elements = Yaml::decode($this->elementsRaw); $this->originalElements = Yaml::decode($this->originalElementsRaw); + $this->elementKeys = []; + if (is_array($this->elements)) { + $this->getElementKeysRecursive($this->elements, $this->elementKeys); + } + // Validate elements are an array. - if ($message = $this->validateArray()) { + if ($options['array'] && ($message = $this->validateArray())) { return [$message]; } // Validate duplicate element name. - if ($messages = $this->validateDuplicateNames()) { - return $messages; + if ($options['names']) { + if ($messages = $this->validateNames()) { + return $messages; + } + elseif ($messages = $this->validateDuplicateNames()) { + return $messages; + } } // Validate ignored properties. - if ($messages = $this->validateProperties()) { + if ($options['properties'] && ($messages = $this->validateProperties())) { return $messages; } // Validate submission data. - if ($messages = $this->validateSubmissions()) { + if ($options['submissions'] && ($messages = $this->validateSubmissions())) { return $messages; } // Validate hierarchy. - if ($messages = $this->validateHierarchy()) { + if ($options['hierarchy'] && ($messages = $this->validateHierarchy())) { return $messages; } // Validate rendering. - if ($message = $this->validateRendering()) { + if ($options['rendering'] && ($message = $this->validateRendering())) { return [$message]; } @@ -196,6 +237,35 @@ protected function validateArray() { return NULL; } + /** + * Validate elements names. + * + * @return array|null + * If not valid, an array of error messages. + */ + protected function validateNames() { + $messages = []; + foreach ($this->elementKeys as $name) { + if (!preg_match('/^[_a-z0-9]+$/', $name)) { + $line_numbers = $this->getLineNumbers('/^\s*(["\']?)' . preg_quote($name, '/') . '\1\s*:/'); + $t_args = [ + '%name' => $name, + '@line_number' => WebformArrayHelper::toString($line_numbers), + ]; + $messages[] = $this->t('The element key %name on line @line_number must contain only lowercase letters, numbers, and underscores.', $t_args); + } + elseif (in_array($name, static::$reservedNames)) { + $line_numbers = $this->getLineNumbers('/^\s*(["\']?)' . preg_quote($name, '/') . '\1\s*:/'); + $t_args = [ + '%name' => $name, + '@line_number' => WebformArrayHelper::toString($line_numbers), + ]; + $messages[] = $this->t('The element key %name on line @line_number is a reserved key.', $t_args); + } + } + return $messages; + } + /** * Validate elements does not contain duplicate names. * @@ -235,7 +305,7 @@ protected function validateDuplicateNames() { */ protected function getDuplicateNamesRecursive(array $elements, array &$names) { foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } if (isset($element['#type'])) { @@ -333,26 +403,6 @@ protected function validateSubmissions() { return NULL; } - /** - * Recurse through elements and collect an associative array of deleted element keys. - * - * @param array $elements - * An array of elements. - * @param array $names - * An array tracking deleted element keys. - */ - protected function getElementKeysRecursive(array $elements, array &$names) { - foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { - continue; - } - if (isset($element['#type'])) { - $names[$key] = $key; - } - $this->getElementKeysRecursive($element, $names); - } - } - /** * Validate element hierarchy. * @@ -419,7 +469,7 @@ protected function validateRendering() { catch (\Exception $exception) { $message = $exception->getMessage(); } - // Restore Drupal's error and exception handler. + // Restore Drupal's error and exception handler. restore_error_handler(); restore_exception_handler(); @@ -439,6 +489,30 @@ protected function validateRendering() { return $message; } + /****************************************************************************/ + // Helper methods. + /****************************************************************************/ + + /** + * Recurse through elements and collect an associative array of deleted element keys. + * + * @param array $elements + * An array of elements. + * @param array $names + * An array tracking deleted element keys. + */ + protected function getElementKeysRecursive(array $elements, array &$names) { + foreach ($elements as $key => &$element) { + if (!WebformElementHelper::isElement($element, $key)) { + continue; + } + if (isset($element['#type'])) { + $names[$key] = $key; + } + $this->getElementKeysRecursive($element, $names); + } + } + /** * Get the line numbers for given pattern in the webform's elements string. * diff --git a/web/modules/webform/src/WebformEntityElementsValidatorInterface.php b/web/modules/webform/src/WebformEntityElementsValidatorInterface.php index 33c2b0633d..4c935c9d0a 100644 --- a/web/modules/webform/src/WebformEntityElementsValidatorInterface.php +++ b/web/modules/webform/src/WebformEntityElementsValidatorInterface.php @@ -12,10 +12,12 @@ interface WebformEntityElementsValidatorInterface { * * @param \Drupal\webform\WebformInterface $webform * A webform. + * @param array $options + * An array of validation rules to check. * * @return array|null * An array of error messages or NULL if the elements are valid. */ - public function validate(WebformInterface $webform); + public function validate(WebformInterface $webform, array $options = []); } diff --git a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntityExportForm.php b/web/modules/webform/src/WebformEntityExportForm.php similarity index 95% rename from web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntityExportForm.php rename to web/modules/webform/src/WebformEntityExportForm.php index 3e5775eab1..577f5f56ab 100644 --- a/web/modules/webform/modules/webform_devel/src/Form/WebformDevelEntityExportForm.php +++ b/web/modules/webform/src/WebformEntityExportForm.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\webform_devel\Form; +namespace Drupal\webform; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Entity\EntityForm; @@ -11,7 +11,7 @@ /** * Export webform configuration. */ -class WebformDevelEntityExportForm extends EntityForm { +class WebformEntityExportForm extends EntityForm { /** * {@inheritdoc} diff --git a/web/modules/webform/src/WebformEntityHandlersForm.php b/web/modules/webform/src/WebformEntityHandlersForm.php index 7d1e74f3c3..4b3e4c4c62 100644 --- a/web/modules/webform/src/WebformEntityHandlersForm.php +++ b/web/modules/webform/src/WebformEntityHandlersForm.php @@ -2,14 +2,18 @@ namespace Drupal\webform; +use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\webform\Ajax\WebformRefreshCommand; use Drupal\webform\Form\WebformEntityAjaxFormTrait; use Drupal\webform\Plugin\WebformHandlerInterface; use Drupal\webform\Plugin\WebformHandlerManagerInterface; use Drupal\webform\Utility\WebformDialogHelper; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; /** * Provides a webform to manage submission handlers. @@ -55,18 +59,18 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - /** @var \Drupal\webform\WebformInterface $webform */ - $webform = $this->getEntity(); - $user_input = $form_state->getUserInput(); + // Hard code the form id. + $form['#id'] = 'webform-handlers-form'; + // Build table header. $header = [ ['data' => $this->t('Title / Description')], ['data' => $this->t('ID'), 'class' => [RESPONSIVE_PRIORITY_LOW]], ['data' => $this->t('Summary'), 'class' => [RESPONSIVE_PRIORITY_LOW]], ['data' => $this->t('Status'), 'class' => [RESPONSIVE_PRIORITY_LOW]], - ['data' => $this->t('Weight'), 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => $this->t('Weight'), 'class' => ['webform-tabledrag-hide']], ['data' => $this->t('Operations')], ]; @@ -121,9 +125,11 @@ public function form(array $form, FormStateInterface $form_state) { '#attributes' => [ 'class' => ['webform-handler-order-weight'], ], + '#wrapper_attributes' => ['class' => ['webform-tabledrag-hide']], ]; $operations = []; + // Edit. $operations['edit'] = [ 'title' => $this->t('Edit'), 'url' => Url::fromRoute('entity.webform.handler.edit_form', [ @@ -132,6 +138,7 @@ public function form(array $form, FormStateInterface $form_state) { ]), 'attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), ]; + // Duplicate. if ($handler->cardinality() === WebformHandlerInterface::CARDINALITY_UNLIMITED) { $operations['duplicate'] = [ 'title' => $this->t('Duplicate'), @@ -142,6 +149,27 @@ public function form(array $form, FormStateInterface $form_state) { 'attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), ]; } + // Test individual handler. + if ($this->entity->access('test')) { + $operations['test'] = [ + 'title' => $this->t('Test'), + 'url' => Url::fromRoute( + 'entity.webform.test_form', + ['webform' => $this->entity->id()], + ['query' => ['_webform_handler' => $handler_id]] + ), + ]; + } + // Add AJAX functionality to enable/disable operations. + $operations['status'] = [ + 'title' => $handler->isEnabled() ? $this->t('Disable') : $this->t('Enable'), + 'url' => Url::fromRoute('entity.webform.handler.' . ($handler->isEnabled() ? 'disable' : 'enable'), [ + 'webform' => $this->entity->id(), + 'webform_handler' => $handler_id, + ]), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['use-ajax']), + ]; + // Delete. $operations['delete'] = [ 'title' => $this->t('Delete'), 'url' => Url::fromRoute('entity.webform.handler.delete_form', [ @@ -150,6 +178,7 @@ public function form(array $form, FormStateInterface $form_state) { ]), 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), ]; + $row['operations'] = [ '#type' => 'operations', '#links' => $operations, @@ -160,41 +189,6 @@ public function form(array $form, FormStateInterface $form_state) { $rows[$handler_id] = $row; } - // Filter add handler by excluded_handlers. - $handler_definitions = $this->handlerManager->getDefinitions(); - $handler_definitions = $this->handlerManager->removeExcludeDefinitions($handler_definitions); - unset($handler_definitions['broken']); - - // Must manually add local actions to the webform because we can't alter local - // actions and add the needed dialog attributes. - // @see https://www.drupal.org/node/2585169 - $local_actions = []; - if (isset($handler_definitions['email'])) { - $local_actions['add_email'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add email'), - 'url' => new Url('entity.webform.handler.add_form', ['webform' => $webform->id(), 'webform_handler' => 'email']), - 'attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(), - ], - ]; - } - unset($handler_definitions['email']); - if ($handler_definitions) { - $local_actions['add_handler'] = [ - '#theme' => 'menu_local_action', - '#link' => [ - 'title' => $this->t('Add handler'), - 'url' => new Url('entity.webform.handler', ['webform' => $webform->id()]), - 'attributes' => WebformDialogHelper::getModalDialogAttributes(), - ], - ]; - } - $form['local_actions'] = [ - '#prefix' => '<ul class="action-links">', - '#suffix' => '</ul>', - ] + $local_actions; - // Build the list of existing webform handlers for this webform. $form['handlers'] = [ '#type' => 'table', @@ -208,12 +202,14 @@ public function form(array $form, FormStateInterface $form_state) { ], '#attributes' => [ 'id' => 'webform-handlers', + 'class' => ['webform-handlers-table'], ], '#empty' => $this->t('There are currently no handlers setup for this webform.'), ] + $rows; // Must preload libraries required by (modal) dialogs. WebformDialogHelper::attachLibraries($form); + $form['#attached']['library'][] = 'webform/webform.admin.tabledrag'; return parent::form($form, $form_state); } @@ -254,7 +250,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $this->logger('webform')->notice('Webform @label handler saved.', $context); - drupal_set_message($this->t('Webform %label handler saved.', ['%label' => $webform->label()])); + $this->messenger()->addStatus($this->t('Webform %label handler saved.', ['%label' => $webform->label()])); } /** @@ -272,4 +268,48 @@ protected function updateHandlerWeights(array $handlers) { } } + /** + * Calls a method on a webform handler and reloads the webform handlers form. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform being acted upon. + * @param string $webform_handler + * THe webform handler id. + * @param string $op + * The operation to perform, e.g., 'enable' or 'disable'. + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse + * Either returns an AJAX response that refreshes the webform's handlers + * page, or redirects back to the webform's handlers page. + */ + public static function ajaxOperation(WebformInterface $webform, $webform_handler, $op, Request $request) { + // Perform the handler disable/enable operation. + $handler = $webform->getHandler($webform_handler); + $handler->$op(); + // Save the webform. + $webform->save(); + + // Display message. + $t_args = [ + '@label' => $handler->label(), + '@op' => ($op === 'enable') ? t('enabled') : t('disabled'), + ]; + \Drupal::messenger()->addStatus(t('This @label handler was @op.', $t_args)); + + // Get the webform's handlers form URL. + $url = $webform->toUrl('handlers', ['query' => ['update' => $webform_handler]])->toString(); + + // If the request is via AJAX, return the webform handlers form. + if ($request->request->get('js')) { + $response = new AjaxResponse(); + $response->addCommand(new WebformRefreshCommand($url)); + return $response; + } + + // Otherwise, redirect back to the webform handlers form. + return new RedirectResponse($url); + } + } diff --git a/web/modules/webform/src/WebformEntityListBuilder.php b/web/modules/webform/src/WebformEntityListBuilder.php index 4de89d676b..2499c09472 100644 --- a/web/modules/webform/src/WebformEntityListBuilder.php +++ b/web/modules/webform/src/WebformEntityListBuilder.php @@ -6,10 +6,14 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\webform\Element\WebformHtmlEditor; use Drupal\webform\Utility\WebformDialogHelper; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; /** * Defines a class to build a listing of webform entities. @@ -18,6 +22,20 @@ */ class WebformEntityListBuilder extends ConfigEntityListBuilder { + /** + * The current request. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + /** * Search keys. * @@ -61,24 +79,43 @@ class WebformEntityListBuilder extends ConfigEntityListBuilder { protected $roleStorage; /** - * Associative array container total results for all webforms. + * Constructs a new WebformListBuilder object. * - * @var array + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. */ - protected $resultsTotals; + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, RequestStack $request_stack, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) { + parent::__construct($entity_type, $storage); + $this->request = $request_stack->getCurrentRequest(); + $this->currentUser = $current_user; + + $this->keys = $this->request->query->get('search'); + $this->category = $this->request->query->get('category'); + $this->state = $this->request->query->get('state'); + $this->submissionStorage = $entity_type_manager->getStorage('webform_submission'); + $this->userStorage = $entity_type_manager->getStorage('user'); + $this->roleStorage = $entity_type_manager->getStorage('user_role'); + } /** * {@inheritdoc} */ - public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage) { - parent::__construct($entity_type, $storage); - - $this->keys = \Drupal::request()->query->get('search'); - $this->category = \Drupal::request()->query->get('category'); - $this->state = \Drupal::request()->query->get('state'); - $this->submissionStorage = \Drupal::entityTypeManager()->getStorage('webform_submission'); - $this->userStorage = \Drupal::entityTypeManager()->getStorage('user'); - $this->roleStorage = \Drupal::entityTypeManager()->getStorage('user_role'); + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('request_stack'), + $container->get('current_user'), + $container->get('entity_type.manager') + ); } /** @@ -94,56 +131,79 @@ public function render() { $build = []; - // Must manually add local actions to the webform because we can't alter local - // actions and add the needed dialog attributes. - // @see https://www.drupal.org/node/2585169 - if (\Drupal::currentUser()->hasPermission('create webform')) { - $build['local_actions'] = [ - 'add_form' => [ - '#type' => 'link', - '#title' => $this->t('Add webform'), - '#url' => new Url('entity.webform.add_form'), - '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, ['button', 'button-action', 'button--primary', 'button--small']), - ], - ]; - } + // Filter form. + $build['filter_form'] = $this->buildFilterForm(); + // Display info. + $build['info'] = $this->buildInfo(); + + // Table. + $build += parent::render(); + $build['table']['#sticky'] = TRUE; + $build['table']['#attributes']['class'][] = 'webform-forms'; + + // Attachments. + // Must preload libraries required by (modal) dialogs. + WebformDialogHelper::attachLibraries($build); + + return $build; + } + + /** + * Build the filter form. + * + * @return array + * A render array representing the filter form. + */ + protected function buildFilterForm() { // Add the filter by key(word) and/or state. - if (\Drupal::currentUser()->hasPermission('administer webform')) { + if ($this->currentUser->hasPermission('administer webform')) { $state_options = [ - '' => $this->t('All [@total]', ['@total' => $this->getTotal(NULL, NULL)]), - WebformInterface::STATUS_OPEN => $this->t('Open [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_OPEN)]), - WebformInterface::STATUS_CLOSED => $this->t('Closed [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_CLOSED)]), - WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_SCHEDULED)]), + (string) $this->t('Active') => [ + '' => $this->t('All [@total]', ['@total' => $this->getTotal(NULL, NULL)]), + WebformInterface::STATUS_OPEN => $this->t('Open [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_OPEN)]), + WebformInterface::STATUS_CLOSED => $this->t('Closed [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_CLOSED)]), + WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_SCHEDULED)]), + ], + (string) $this->t('Inactive') => [ + WebformInterface::STATUS_ARCHIVED => $this->t('Archived [@total]', ['@total' => $this->getTotal(NULL, NULL, WebformInterface::STATUS_ARCHIVED)]), + ], ]; } else { $state_options = [ - '' => $this->t('All'), - WebformInterface::STATUS_OPEN => $this->t('Open'), - WebformInterface::STATUS_CLOSED => $this->t('Closed'), - WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled'), + (string) $this->t('Active') => [ + '' => $this->t('All'), + WebformInterface::STATUS_OPEN => $this->t('Open'), + WebformInterface::STATUS_CLOSED => $this->t('Closed'), + WebformInterface::STATUS_SCHEDULED => $this->t('Scheduled'), + ], + (string) $this->t('Inactive') => [ + WebformInterface::STATUS_ARCHIVED => $this->t('Archived'), + ], ]; } - $build['filter_form'] = \Drupal::formBuilder()->getForm('\Drupal\webform\Form\WebformEntityFilterForm', $this->keys, $this->category, $this->state, $state_options); + return \Drupal::formBuilder()->getForm('\Drupal\webform\Form\WebformEntityFilterForm', $this->keys, $this->category, $this->state, $state_options); + } + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { // Display info. - if (\Drupal::currentUser()->hasPermission('administer webform') && ($total = $this->getTotal($this->keys, $this->category, $this->state))) { - $build['info'] = [ + if ($this->currentUser->hasPermission('administer webform') && ($total = $this->getTotal($this->keys, $this->category, $this->state))) { + return [ '#markup' => $this->formatPlural($total, '@total webform', '@total webforms', ['@total' => $total]), '#prefix' => '<div>', '#suffix' => '</div>', ]; } - - $build += parent::render(); - - $build['table']['#attributes']['class'][] = 'webform-forms'; - - // Must preload libraries required by (modal) dialogs. - WebformDialogHelper::attachLibraries($build); - - return $build; + else { + return []; + } } /** @@ -180,18 +240,15 @@ public function buildHeader() { 'specifier' => 'uid', 'field' => 'uid', ]; - $header['results_total'] = [ - 'data' => $this->t('Total Results'), + $header['results'] = [ + 'data' => $this->t('Results'), 'class' => [RESPONSIVE_PRIORITY_MEDIUM], - 'specifier' => 'results_total', - 'field' => 'results_total', + 'specifier' => 'results', + 'field' => 'results', ]; - $header['results_operations'] = [ + $header['operations'] = [ 'data' => $this->t('Operations'), - 'class' => [RESPONSIVE_PRIORITY_MEDIUM], ]; - $header['operations'] = ''; - return $header; } @@ -200,8 +257,9 @@ public function buildHeader() { */ public function buildRow(EntityInterface $entity) { /* @var $entity \Drupal\webform\WebformInterface */ - $settings = $entity->getSettings(); + // Title. + // // ISSUE: Webforms that the current user can't access are not being hidden via the EntityQuery. // WORK-AROUND: Don't link to the webform. // See: Access control is not applied to config entity queries @@ -210,29 +268,85 @@ public function buildRow(EntityInterface $entity) { if ($entity->isTemplate()) { $row['title']['data']['template'] = ['#markup' => ' <b>(' . $this->t('Template') . ')</b>']; } + + // Description. $row['description']['data'] = WebformHtmlEditor::checkMarkup($entity->get('description')); + + // Category. $row['category']['data']['#markup'] = $entity->get('category'); - switch ($entity->get('status')) { - case WebformInterface::STATUS_OPEN: - $row['status'] = $this->t('Open'); - break; - case WebformInterface::STATUS_CLOSED: - $row['status'] = $this->t('Closed'); - break; + // Status. + $t_args = ['@label' => $entity->label()]; + if ($entity->isArchived()) { + $row['status']['data'] = [ + '#type' => 'html_tag', + '#tag' => 'span', + '#markup' => $this->t('Archived'), + '#attributes' => ['aria-label' => $this->t('@label is archived', $t_args)], + ]; + $row['status'] = $this->t('Archived'); + } + else { + switch ($entity->get('status')) { + case WebformInterface::STATUS_OPEN: + $status = $this->t('Open'); + $aria_label = $this->t('@label is open', $t_args); + break; + + case WebformInterface::STATUS_CLOSED: + $status = $this->t('Closed'); + $aria_label = $this->t('@label is closed', $t_args); + break; + + case WebformInterface::STATUS_SCHEDULED: + $status = $this->t('Scheduled (@state)', ['@state' => $entity->isOpen() ? $this->t('Open') : $this->t('Closed')]); + $aria_label = $this->t('@label is scheduled and is @state', $t_args + ['@state' => $entity->isOpen() ? $this->t('open') : $this->t('closed')]); + break; + + default: + return []; + } - case WebformInterface::STATUS_SCHEDULED: - $row['status'] = $this->t('Scheduled (@state)', ['@state' => $entity->isOpen() ? $this->t('Open') : $this->t('Closed')]); - break; + if ($entity->access('update')) { + $row['status']['data'] = $entity->toLink($status, 'settings-form', ['query' => $this->getDestinationArray()])->toRenderable() + [ + '#attributes' => ['aria-label' => $aria_label], + ]; + } + else { + $row['status']['data'] = [ + '#type' => 'html_tag', + '#tag' => 'span', + '#markup' => $status, + '#attributes' => ['aria-label' => $aria_label], + ]; + } } + + // Owner. $row['owner'] = ($owner = $entity->getOwner()) ? $owner->toLink() : ''; - $row['results_total'] = $this->getResultsTotal($entity->id()) . ($entity->isResultsDisabled() ? ' ' . $this->t('(Disabled)') : ''); - $row['results_operations']['data'] = [ - '#type' => 'operations', - '#links' => $this->getDefaultOperations($entity, 'results'), - '#prefix' => '<div class="webform-dropbutton">', - '#suffix' => '</div>', - ]; + + // Results. + $result_total = $this->storage->getTotalNumberOfResults($entity->id()); + $results_access = $entity->access('submission_view_any'); + $results_disabled = $entity->isResultsDisabled(); + if ($results_disabled || !$results_access) { + $row['results'] = $result_total . ($entity->isResultsDisabled() ? ' ' . $this->t('(Disabled)') : ''); + } + else { + $row['results'] = [ + 'data' => [ + '#type' => 'link', + '#title' => $result_total, + '#attributes' => [ + 'aria-label' => $this->formatPlural($result_total, '@count result for @label', '@count results for @label', ['@label' => $entity->label()]), + ], + '#url' => $entity->toUrl('results-submissions'), + '#suffix' => ($entity->isResultsDisabled() ? ' ' . $this->t('(Disabled)') : ''), + ], + ]; + } + + // Operations. return $row + parent::buildRow($entity); } @@ -251,63 +365,45 @@ public function buildOperations(EntityInterface $entity) { */ public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { /* @var $entity \Drupal\webform\WebformInterface */ - $route_parameters = ['webform' => $entity->id()]; - if ($type == 'results') { - $operations = []; - if ($entity->access('submission_view_any')) { - $operations['submissions'] = [ - 'title' => $this->t('Submissions'), - 'url' => Url::fromRoute('entity.webform.results_submissions', $route_parameters), - ]; - $operations['export'] = [ - 'title' => $this->t('Download'), - 'url' => Url::fromRoute('entity.webform.results_export', $route_parameters), - ]; - } - if ($entity->access('submission_delete_any')) { - $operations['clear'] = [ - 'title' => $this->t('Clear'), - 'url' => Url::fromRoute('entity.webform.results_clear', $route_parameters), - ]; - } + + $operations = []; + if ($entity->access('update')) { + $operations['edit'] = [ + 'title' => $this->t('Build'), + 'url' => $this->ensureDestination($entity->toUrl('edit-form')), + ]; } - else { - $operations = parent::getDefaultOperations($entity); - if (isset($operations['edit'])) { - $operations['edit']['title'] = $this->t('Build'); - } - if ($entity->access('update')) { - $operations['settings'] = [ - 'title' => $this->t('Settings'), - 'weight' => 22, - 'url' => Url::fromRoute('entity.webform.settings', $route_parameters), - ]; - } - if ($entity->access('submission_page')) { - $operations['view'] = [ - 'title' => $this->t('View'), - 'weight' => 24, - 'url' => Url::fromRoute('entity.webform.canonical', $route_parameters), - ]; - } - if ($entity->access('submission_update_any')) { - $operations['test'] = [ - 'title' => $this->t('Test'), - 'weight' => 25, - 'url' => Url::fromRoute('entity.webform.test_form', $route_parameters), - ]; - } - if ($entity->access('duplicate')) { - $operations['duplicate'] = [ - 'title' => $this->t('Duplicate'), - 'weight' => 26, - 'url' => Url::fromRoute('entity.webform.duplicate_form', $route_parameters), - 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), - ]; - } - if (isset($operations['delete'])) { - $operations['delete']['attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW); - } + if ($entity->access('submission_page')) { + $operations['view'] = [ + 'title' => $this->t('View'), + 'url' => $entity->toUrl('canonical'), + ]; + } + if ($entity->access('submission_view_any') && !$entity->isResultsDisabled()) { + $operations['results'] = [ + 'title' => $this->t('Results'), + 'url' => $entity->toUrl('results-submissions'), + ]; + } + if ($entity->access('update')) { + $operations['settings'] = [ + 'title' => $this->t('Settings'), + 'url' => $entity->toUrl('settings'), + ]; + } + if ($entity->access('duplicate')) { + $operations['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'url' => $entity->toUrl('duplicate-form'), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; + } + if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) { + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => $this->ensureDestination($entity->toUrl('delete-form')), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; } return $operations; } @@ -317,17 +413,17 @@ public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { */ protected function getEntityIds() { $header = $this->buildHeader(); - if (\Drupal::request()->query->get('order') === (string) $header['results_total']['data']) { + if ($this->request->query->get('order') === (string) $header['results']['data']) { // Get results totals for all returned entity ids. $results_totals = $this->getQuery($this->keys, $this->category, $this->state) ->execute(); foreach ($results_totals as $entity_id) { - $results_totals[$entity_id] = $this->getResultsTotal($entity_id); + $results_totals[$entity_id] = $this->storage->getTotalNumberOfResults($entity_id); } // Sort results totals. asort($results_totals, SORT_NUMERIC); - if (\Drupal::request()->query->get('sort') === 'desc') { + if ($this->request->query->get('sort') === 'desc') { $results_totals = array_reverse($results_totals, TRUE); } @@ -336,7 +432,7 @@ protected function getEntityIds() { $entity_ids = array_combine($entity_ids, $entity_ids); // Manually initialize and apply paging to the entity ids. - $page = \Drupal::request()->query->get('page') ?: 0; + $page = $this->request->query->get('page') ?: 0; $total = count($entity_ids); $limit = $this->getLimit(); $start = ($page * $limit); @@ -351,41 +447,6 @@ protected function getEntityIds() { } } - /** - * Get total number of results for a webform. - * - * @param string $webform_id - * A webform id. - * - * @return int - * Total number of results for a webform. - */ - protected function getResultsTotal($webform_id) { - $results_totals = $this->getResultsTotals(); - return (isset($results_totals[$webform_id])) ? $results_totals[$webform_id] : 0; - } - - /** - * Get total results for all webforms. - * - * @return array - * An associative array keyed by webform id contains total results for - * all webforms. - */ - protected function getResultsTotals() { - if (isset($this->resultsTotals)) { - return $this->resultsTotals; - } - - $query = \Drupal::database()->select('webform_submission', 'ws'); - $query->fields('ws', ['webform_id']); - $query->addExpression('COUNT(sid)', 'results_total'); - $query->groupBy('webform_id'); - - $this->resultsTotals = array_map('intval', $query->execute()->fetchAllKeyed()); - return $this->resultsTotals; - } - /** * Get the total number of submissions. * @@ -472,12 +533,19 @@ protected function getQuery($keys = '', $category = '', $state = '') { } // Filter by (form) state. - if ($state) { - $query->condition('status', $state); + switch ($state) { + case WebformInterface::STATUS_OPEN; + case WebformInterface::STATUS_CLOSED; + case WebformInterface::STATUS_SCHEDULED; + $query->condition('status', $state); + break; } + // Always filter by archived state. + $query->condition('archive', $state === WebformInterface::STATUS_ARCHIVED ? 1 : 0); + // Filter out templates if the webform_template.module is enabled. - if ($this->moduleHandler()->moduleExists('webform_templates')) { + if ($this->moduleHandler()->moduleExists('webform_templates') && $state !== WebformInterface::STATUS_ARCHIVED) { $query->condition('template', FALSE); } return $query; @@ -521,7 +589,7 @@ protected function getLimit() { * permission. */ protected function isAdmin() { - $account = \Drupal::currentUser(); + $account = $this->currentUser; return ($account->hasPermission('administer webform') || $account->hasPermission('edit any webform') || $account->hasPermission('view any webform submission')); } @@ -529,6 +597,7 @@ protected function isAdmin() { * {@inheritdoc} */ protected function ensureDestination(Url $url) { + // Never add add a destination to operation URLs. return $url; } diff --git a/web/modules/webform/src/WebformEntityReferenceManager.php b/web/modules/webform/src/WebformEntityReferenceManager.php index bda4fc01e0..8c81048e63 100644 --- a/web/modules/webform/src/WebformEntityReferenceManager.php +++ b/web/modules/webform/src/WebformEntityReferenceManager.php @@ -14,7 +14,7 @@ * Webform entity reference (field) manager. * * The webform entity reference (field) manager is used to track webforms that - * are attached to entities, specifically webform nodes. Generally, only one + * are attached to entities, specifically webform nodes. Generally, only one * webform is attached to a single node. Field API does allow multiple * webforms to be attached to any entity and this services helps handle this * edge case. @@ -42,6 +42,20 @@ class WebformEntityReferenceManager implements WebformEntityReferenceManagerInte */ protected $userData; + /** + * Cache of source entity webforms. + * + * @var array + */ + protected $webforms = []; + + /** + * Cache of source entity field names. + * + * @var array + */ + protected $fieldNames = []; + /** * Constructs a WebformEntityReferenceManager object. * @@ -127,6 +141,21 @@ public function deleteUserWebformId(EntityInterface $entity) { // Field methods. /****************************************************************************/ + /** + * {@inheritdoc} + */ + public function hasField(EntityInterface $entity = NULL) { + return $this->getFieldName($entity) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function getFieldName(EntityInterface $entity = NULL) { + $field_names = $this->getFieldNames($entity); + return $field_names ? reset($field_names) : ''; + } + /** * {@inheritdoc} */ @@ -135,6 +164,12 @@ public function getFieldNames(EntityInterface $entity = NULL) { return []; } + // Cache the source entity's field names. + $entity_id = $entity->getEntityTypeId() . '-' . $entity->id(); + if (isset($this->fieldNames[$entity_id])) { + return $this->fieldNames[$entity_id]; + } + $field_names = []; if ($entity instanceof ContentEntityInterface) { $fields = $entity->getFieldDefinitions(); @@ -148,24 +183,10 @@ public function getFieldNames(EntityInterface $entity = NULL) { // Sort fields alphabetically. ksort($field_names); + $this->fieldNames[$entity_id] = $field_names; return $field_names; } - /** - * {@inheritdoc} - */ - public function hasField(EntityInterface $entity = NULL) { - return $this->getFieldName($entity) ? TRUE : FALSE; - } - - /** - * {@inheritdoc} - */ - public function getFieldName(EntityInterface $entity = NULL) { - $field_names = $this->getFieldNames($entity); - return $field_names ? reset($field_names) : ''; - } - /** * {@inheritdoc} */ @@ -173,8 +194,8 @@ public function getWebform(EntityInterface $entity = NULL) { if ($webform_id = $this->getUserWebformId($entity)) { return Webform::load($webform_id); } - elseif ($field_name = $this->getFieldName($entity)) { - return $entity->$field_name->entity; + elseif ($webforms = $this->getWebforms($entity)) { + return reset($webforms); } else { return NULL; @@ -185,14 +206,33 @@ public function getWebform(EntityInterface $entity = NULL) { * {@inheritdoc} */ public function getWebforms(EntityInterface $entity = NULL) { + // Cache the source entity's webforms. + $entity_id = $entity->getEntityTypeId() . '-' . $entity->id(); + if (isset($this->webforms[$entity_id])) { + return $this->webforms[$entity_id]; + } + $field_names = $this->getFieldNames($entity); $target_entities = []; + $sorted_entities = []; foreach ($field_names as $field_name) { foreach ($entity->$field_name as $item) { + $sorted_entities[$item->target_id] = (method_exists($item->entity, 'getWeight')) ? $item->entity->getWeight() : 0; $target_entities[$item->target_id] = $item->entity; } } - return $target_entities; + // Sort the webforms by key and then weight. + ksort($sorted_entities); + asort($sorted_entities, SORT_NUMERIC); + + // Return the sort webforms. + $webforms = []; + foreach (array_keys($sorted_entities) as $target_id) { + $webforms[$target_id] = $target_entities[$target_id]; + } + + $this->webforms[$entity_id] = $webforms; + return $webforms; } /****************************************************************************/ @@ -203,7 +243,6 @@ public function getWebforms(EntityInterface $entity = NULL) { * {@inheritdoc} */ public function getTableNames() { - // @todo Figure out a better way to determine webform field table names. /** @var \Drupal\field\FieldStorageConfigInterface[] $field_storage_configs */ $field_storage_configs = FieldStorageConfig::loadMultiple(); $tables = []; diff --git a/web/modules/webform/src/WebformEntityReferenceManagerInterface.php b/web/modules/webform/src/WebformEntityReferenceManagerInterface.php index 92550880ca..3e7490238c 100644 --- a/web/modules/webform/src/WebformEntityReferenceManagerInterface.php +++ b/web/modules/webform/src/WebformEntityReferenceManagerInterface.php @@ -70,26 +70,26 @@ public function deleteUserWebformId(EntityInterface $entity); public function hasField(EntityInterface $entity = NULL); /** - * Get an entity's webform field names. + * Get an entity's webform field name. * * @param \Drupal\Core\Entity\EntityInterface $entity * A fieldable content entity. * - * @return array - * An array of webform fields associate with an entity. + * @return string + * The name of the webform field or an empty string. */ - public function getFieldNames(EntityInterface $entity = NULL); + public function getFieldName(EntityInterface $entity = NULL); /** - * Get an entity's webform field name. + * Get an entity's webform field names. * * @param \Drupal\Core\Entity\EntityInterface $entity * A fieldable content entity. * - * @return string - * The name of the webform field or an empty string. + * @return array + * An array of webform fields associate with an entity. */ - public function getFieldName(EntityInterface $entity = NULL); + public function getFieldNames(EntityInterface $entity = NULL); /** * Get an entity's target webform. diff --git a/web/modules/webform/src/WebformEntityStorage.php b/web/modules/webform/src/WebformEntityStorage.php index ffe5704c77..6859fba15a 100644 --- a/web/modules/webform/src/WebformEntityStorage.php +++ b/web/modules/webform/src/WebformEntityStorage.php @@ -3,11 +3,13 @@ namespace Drupal\webform; use Drupal\Component\Uuid\UuidInterface; +use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -24,6 +26,20 @@ class WebformEntityStorage extends ConfigEntityStorage implements WebformEntityS */ protected $database; + /** + * The entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Associative array container total results for all webforms. + * + * @var array + */ + protected $totals; + /** * Constructs a WebformEntityStorage object. * @@ -37,10 +53,13 @@ class WebformEntityStorage extends ConfigEntityStorage implements WebformEntityS * The language manager. * @param \Drupal\Core\Database\Connection $database * The database connection to be used. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager service. */ - public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, Connection $database) { + public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, Connection $database, EntityTypeManagerInterface $entity_type_manager) { parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager); $this->database = $database; + $this->entityTypeManager = $entity_type_manager; } /** @@ -52,7 +71,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('config.factory'), $container->get('uuid'), $container->get('language_manager'), - $container->get('database') + $container->get('database'), + $container->get('entity_type.manager') ); } @@ -67,11 +87,26 @@ protected function doCreate(array $values) { // @see \Drupal\webform_ui\Form\WebformUiElementTestForm // @see \Drupal\webform_ui\Form\WebformUiElementTypeFormBase if ($id = $entity->id()) { - $this->entities[$id] = $entity; + $this->setStaticCache([$id => $entity]); } return $entity; } + /** + * {@inheritdoc} + */ + protected function doPostSave(EntityInterface $entity, $update) { + if ($update && $entity->getAccessRules() != $entity->original->getAccessRules()) { + // Invalidate webform_submission listing cache tags because due to the + // change in access rules of this webform, some listings might have + // changed for users. + $cache_tags = $this->entityTypeManager->getDefinition('webform_submission')->getListCacheTags(); + Cache::invalidateTags($cache_tags); + } + + parent::doPostSave($entity, $update); + } + /** * {@inheritdoc} */ @@ -89,34 +124,6 @@ public function save(EntityInterface $entity) { return $return; } - /** - * {@inheritdoc} - * - * Config entities are not cached and there is no easy way to enable static - * caching. See: Issue #1885830: Enable static caching for config entities. - * - * Overriding just EntityStorageBase::load is much simpler - * than completely re-writting EntityStorageBase::loadMultiple. It is also - * worth noting that EntityStorageBase::resetCache() does purge all cached - * webform config entities. - * - * Webforms need to be cached when they are being loading via - * a webform submission, which requires a webform's elements and meta data to be - * initialized via Webform::initElements(). - * - * @see https://www.drupal.org/node/1885830 - * @see \Drupal\Core\Entity\EntityStorageBase::resetCache() - * @see \Drupal\webform\Entity\Webform::initElements() - */ - public function load($id) { - if (isset($this->entities[$id])) { - return $this->entities[$id]; - } - - $this->entities[$id] = parent::load($id); - return $this->entities[$id]; - } - /** * {@inheritdoc} */ @@ -132,9 +139,6 @@ public function delete(array $entities) { foreach ($entities as $entity) { $webform_ids[] = $entity->id(); } - $this->database->delete('webform_submission_log') - ->condition('webform_id', $webform_ids, 'IN') - ->execute(); // Delete all webform records used to track next serial. $this->database->delete('webform') @@ -158,7 +162,7 @@ public function delete(array $entities) { } // Clear empty webform directory. - if (empty(file_scan_directory($file_directory, '/.*/'))) { + if (file_exists($file_directory) && empty(file_scan_directory($file_directory, '/.*/'))) { file_unmanaged_delete_recursive($file_directory); } } @@ -187,15 +191,22 @@ public function getCategories($template = NULL) { * {@inheritdoc} */ public function getOptions($template = NULL) { + /** @var \Drupal\webform\WebformInterface[] $webforms */ $webforms = $this->loadMultiple(); @uasort($webforms, [$this->entityType->getClass(), 'sort']); $uncategorized_options = []; $categorized_options = []; foreach ($webforms as $id => $webform) { + // Skip templates. if ($template !== NULL && $webform->get('template') != $template) { continue; } + // Skip archived. + if ($webform->isArchived()) { + continue; + } + if ($category = $webform->get('category')) { $categorized_options[$category][$id] = $webform->label(); } @@ -246,7 +257,7 @@ public function setNextSerial(WebformInterface $webform, $next_serial = 1) { * {@inheritdoc} */ public function getSerial(WebformInterface $webform) { - // Use a transaction with SELECT ... FOR UPDATE to lock the row between + // Use a transaction with SELECT … FOR UPDATE to lock the row between // the SELECT and the UPDATE, ensuring that multiple Webform submissions // at the same time do not have duplicate numbers. FOR UPDATE must be inside // a transaction. The return value of db_transaction() must be assigned or @@ -288,4 +299,32 @@ public function getMaxSerial(WebformInterface $webform) { return $query->execute()->fetchField() + 1; } + /** + * Get total number of results for specified webform or all webforms. + * + * @param string|null $webform_id + * (optional) A webform id. + * + * @return array|int + * If no webform id is passed, an associative array keyed by webform id + * contains total results for all webforms, otherwise the total number of + * results for specified webform + */ + public function getTotalNumberOfResults($webform_id = NULL) { + if (!isset($this->totals)) { + $query = $this->database->select('webform_submission', 'ws'); + $query->fields('ws', ['webform_id']); + $query->addExpression('COUNT(sid)', 'results'); + $query->groupBy('webform_id'); + $this->totals = array_map('intval', $query->execute()->fetchAllKeyed()); + } + + if ($webform_id) { + return (isset($this->totals[$webform_id])) ? $this->totals[$webform_id] : 0; + } + else { + return $this->totals; + } + } + } diff --git a/web/modules/webform/src/WebformEntityStorageInterface.php b/web/modules/webform/src/WebformEntityStorageInterface.php index 07f5ee6b90..a52ca1e10a 100644 --- a/web/modules/webform/src/WebformEntityStorageInterface.php +++ b/web/modules/webform/src/WebformEntityStorageInterface.php @@ -73,4 +73,13 @@ public function getSerial(WebformInterface $webform); */ public function getMaxSerial(WebformInterface $webform); + /** + * Get total results for all webforms. + * + * @return array + * An associative array keyed by webform id contains total results for + * all webforms. + */ + public function getTotalNumberOfResults(); + } diff --git a/web/modules/webform/src/WebformEntityViewBuilder.php b/web/modules/webform/src/WebformEntityViewBuilder.php index 87300bdcaa..ebb7556bf7 100644 --- a/web/modules/webform/src/WebformEntityViewBuilder.php +++ b/web/modules/webform/src/WebformEntityViewBuilder.php @@ -14,13 +14,8 @@ class WebformEntityViewBuilder extends EntityViewBuilder { * {@inheritdoc} */ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { - // TODO: Determine if webform can be cached. /* @var $entity \Drupal\webform\WebformInterface */ - return [ - '#cache' => [ - 'max-age' => 0, - ], - ] + $entity->getSubmissionForm(); + return $entity->getSubmissionForm(); } } diff --git a/web/modules/webform/src/WebformHelpManager.php b/web/modules/webform/src/WebformHelpManager.php index 759f1bd50b..fa706646e6 100644 --- a/web/modules/webform/src/WebformHelpManager.php +++ b/web/modules/webform/src/WebformHelpManager.php @@ -241,6 +241,8 @@ public function deleteNotification($id) { * {@inheritdoc} */ public function buildHelp($route_name, RouteMatchInterface $route_match) { + $is_help_disabled = $this->configFactory->get('webform.settings')->get('ui.help_disabled'); + // Get path from route match. $path = preg_replace('/^' . preg_quote(base_path(), '/') . '/', '/', Url::fromRouteMatch($route_match)->setAbsolute(FALSE)->toString()); @@ -270,6 +272,11 @@ public function buildHelp($route_name, RouteMatchInterface $route_match) { continue; } + // Check is help is disabled. Messages are always displayed. + if ($is_help_disabled && empty($help['message_type'])) { + continue; + } + if ($help['message_type']) { $build[$id] = [ '#type' => 'webform_message', @@ -293,6 +300,10 @@ public function buildHelp($route_name, RouteMatchInterface $route_match) { '#info' => $help, ]; } + // Add custom help weight. + if (isset($help['weight'])) { + $build[$id]['#weight'] = $help['weight']; + } } // Disable caching when Webform editorial module is enabled. @@ -374,7 +385,7 @@ public function buildVideos($docs = FALSE) { // Content. $row['content'] = ['data' => []]; $row['content']['data']['title'] = [ - '#markup' => $video['title'], + '#markup' => $video['title'] . ' | ' . (isset($video['owner']) ? $video['owner'] : $this->t('Jacob Rockowitz')), '#prefix' => '<h3>', '#suffix' => '</h3>', ]; @@ -393,41 +404,96 @@ public function buildVideos($docs = FALSE) { '#prefix' => '<p>', '#suffix' => '</p>', ]; - $row['content']['data']['resources'] = [ - 'title' => [ - '#markup' => $this->t('Additional resources'), - '#prefix' => '<div><strong>', - '#suffix' => '</strong></div>', - ], - 'links' => [ - '#theme' => 'links', - '#links' => $this->getVideoLinks($id), - '#attributes' => ['class' => ['webform-help-links']], - ], - ]; + if ($video_links = $this->getVideoLinks($id)) { + $row['content']['data']['resources'] = [ + 'title' => [ + '#markup' => $this->t('Additional resources'), + '#prefix' => '<div><strong>', + '#suffix' => '</strong></div>', + ], + 'links' => [ + '#theme' => 'links', + '#links' => $video_links, + '#attributes' => ['class' => ['webform-help-links']], + ], + ]; + } $rows[$id] = ['data' => $row, 'no_striping' => TRUE]; } $build = [ - '#theme' => 'table', - '#rows' => $rows, - '#attributes' => [ - 'border' => 0, - 'cellpadding' => 2, - 'cellspacing' => 0, + 'content' => [ + '#markup' => '<p>' . $this->t('The below are video tutorials are produced by <a href="https://jrockowitz.com">Jacob Rockowitz</a> and <a href="https://www.webwash.net/">WebWash.net</a>.') . '</p>' . + (!$docs ? '<hr/>' : ''), ], ]; if (!$docs) { - $build['#header'] = [ - ['data' => '', 'style' => 'padding:0; border-top-color: transparent', 'class' => [RESPONSIVE_PRIORITY_LOW]], - ['data' => '', 'style' => 'padding:0; border-top-color: transparent'], + // Filter. + $build['filter'] = [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by videos'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-element' => 'table', + 'data-source' => 'tbody tr', + 'data-parent' => 'tr', + 'data-summary' => '.webform-help-videos-summary', + 'data-item-singlular' => $this->t('video'), + 'data-item-plural' => $this->t('videos'), + 'data-no-results' => '.webform-help-videos-no-results', + 'title' => $this->t('Enter a keyword to filter by.'), + 'autofocus' => 'autofocus', + ], ]; + + // Display info. + $build['info'] = [ + '#markup' => $this->t('@total videos', ['@total' => count($rows)]), + '#prefix' => '<p class="webform-help-videos-summary">', + '#suffix' => '</p>', + ]; + + // No results. + $build['no_results'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('No videos found. Try a different search.'), + '#message_type' => 'info', + '#attributes' => ['class' => ['webform-help-videos-no-results']], + ]; + + $build['table'] = [ + '#theme' => 'table', + '#header' => [ + ['data' => '', 'style' => 'padding:0; border-top-color: transparent', 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => '', 'style' => 'padding:0; border-top-color: transparent'], + ], + '#rows' => $rows, + '#attributes' => [ + 'border' => 0, + 'cellpadding' => 2, + 'cellspacing' => 0, + ], + ]; + + $build['#attached']['library'][] = 'webform/webform.admin'; $build['#attached']['library'][] = 'webform/webform.help'; $build['#attached']['library'][] = 'webform/webform.ajax'; } else { - $build['#no_striping'] = TRUE; + $build['videos'] = [ + '#theme' => 'table', + '#rows' => $rows, + '#no_striping' => TRUE, + '#attributes' => [ + 'border' => 0, + 'cellpadding' => 2, + 'cellspacing' => 0, + ], + ]; } return $build; @@ -440,7 +506,8 @@ public function buildAddOns($docs = FALSE) { $build = [ 'content' => [ '#markup' => '<p>' . $this->t("Below is a list of modules and projects that extend and/or provide additional functionality to the Webform module and Drupal's Form API.") . '</p>' . - '<p>★ = ' . $this->t('Recommended') . '</p>', + '<hr/>' . + '<p>★ = ' . $this->t('Recommended') . '</p>', ], ]; @@ -495,6 +562,10 @@ public function buildLibraries($docs = FALSE) { ]; $libraries = $this->librariesManager->getLibraries(); foreach ($libraries as $library_name => $library) { + if ($docs && !empty($library['deprecated'])) { + continue; + } + // Get required elements. $elements = []; if (!empty($library['elements'])) { @@ -532,10 +603,32 @@ public function buildLibraries($docs = FALSE) { '#suffix' => '</dd>', ], ]; + if ($docs) { $build['content']['libraries'][$library_name]['title']['#suffix'] = '</dt>'; unset($build['content']['libraries'][$library_name]['description']['download']); + + if (isset($library['issues_url'])) { + $issues_url = $library['issues_url']; + } + elseif (preg_match('#https://github.com/[^/]+/[^/]+#', $library['download_url']->toString(), $match)) { + $issues_url = Url::fromUri($match[0] . '/issues'); + } + else { + $issues_url = NULL; + } + + if ($issues_url) { + $build['content']['libraries'][$library_name]['description']['accessibility'] = [ + '#type' => 'link', + '#title' => $this->t('known accessibility issues'), + '#url' => $issues_url->setOption('query', ['q' => 'is:issue is:open accessibility ']), + '#prefix' => '<em>@see ', + '#suffix' => '</em>', + ]; + } } + } return $build; } @@ -716,7 +809,10 @@ protected function initGroups() { * An associative array containing videos. */ protected function initVideos() { - $videos = [ + $videos = []; + + // Jacob Rockowitz (jrockowitz.com). + $videos += [ 'introduction' => [ 'title' => $this->t('Introduction to Webform for Drupal 8'), 'content' => $this->t('This screencast provides a general introduction to the Webform module.'), @@ -877,6 +973,18 @@ protected function initVideos() { ], ], ], + 'import' => [ + 'title' => $this->t('Importing webform submissions'), + 'content' => $this->t("This screencast shows how to import submissions using CSV (comma separated values) file."), + 'youtube_id' => 'AYGr4O-jZBo', + 'presentation_id' => '189XhD6m0879EMo44ym8uaZaIAFiEl8tkH31WUtge_u8', + 'links' => [ + [ + 'title' => $this->t('Webform module now supports importing submissions | Drupal.org'), + 'url' => 'https://www.drupal.org/node/3040513', + ], + ], + ], 'configuration' => [ 'title' => $this->t("Configuring the Webform module"), 'content' => $this->t('This screencast walks through all the configuration settings available to manage forms, submissions, options, handlers, exporters, libraries and assets.'), @@ -897,10 +1005,31 @@ protected function initVideos() { ], ], ], + 'access' => [ + 'title' => $this->t("Webform access controls"), + 'content' => $this->t('This screencast walks through how to use permissions, roles, and custom access rules to control access to webforms and submissions.'), + 'youtube_id' => 'EPg9Ltwak2M', + 'presentation_id' => '19Xkb2MR5N075Va403slTVRYjanJ14HmuEYBwwbrQFX4', + 'links' => [ + [ + 'title' => $this->t('Users, Roles, and Permissions | Drupal.org'), + 'url' => 'https://drupal.org/docs/user_guide/en/user-concept.html ', + ], + [ + 'title' => $this->t('Users, Roles, and Permissions | Drupalize.me'), + 'url' => 'https://drupalize.me/topic/users-roles-and-permissions', + ], + [ + 'title' => $this->t('Access Control | Tag1 Consulting'), + 'url' => 'https://tag1consulting.com/blog/access-control', + ], + ], + ], 'webform_nodes' => [ 'title' => $this->t('Attaching webforms to nodes'), 'content' => $this->t('This screencast walks through how to attach a webform to node.'), 'youtube_id' => 'B_ZyCOVKPqA', + 'presentation_id' => '1XoIUSgQ0bb_xCfWx8VZe1WHTr0QoCfnE8DzSAsc2WQM', 'links' => [ [ 'title' => $this->t('Working with content types and fields | Drupal.org'), @@ -915,7 +1044,6 @@ protected function initVideos() { 'url' => 'https://drupalize.me/tutorial/user-guide/planning-data-types', ], ], - 'presentation_id' => '1XoIUSgQ0bb_xCfWx8VZe1WHTr0QoCfnE8DzSAsc2WQM', ], 'webform_blocks' => [ 'title' => $this->t('Placing webforms as blocks'), @@ -969,6 +1097,62 @@ protected function initVideos() { ], ], ], + 'dialogs' => [ + 'title' => $this->t('Opening webforms in modal dialogs'), + 'content' => $this->t('This screencast shows how to open webforms in modal dialogs.'), + 'youtube_id' => 'zmRxyUHWczw', + 'presentation_id' => '1XlAv-u1lZr13nZvCEuJXtDp4Dmn8X7Fwq_4yac-SajE', + 'links' => [ + [ + 'title' => $this->t('Creating a modal in Drupal 8 | Befused'), + 'url' => 'https://www.drupal.org/project/devel', + ], + [ + 'title' => $this->t('Display forms in a modal dialog with Drupal 8 | Agaric'), + 'url' => 'http://agaric.com/blogs/display-forms-modal-dialog-drupal-8', + ], + [ + 'title' => $this->t('jQueryUI Dialog Documentation'), + 'url' => 'https://jqueryui.com/dialog/ ', + ], + ], + ], + 'views' => [ + 'title' => $this->t('Webform views integration'), + 'content' => $this->t('This presentation shows how to use views to display webform submissions.'), + 'youtube_id' => 'Qs_m5ybxeXk', + 'presentation_id' => '1pUUmwjsyxtU9YB4y0qQbSING1W4YBTcqYQjabmSL5N8', + 'links' => [ + [ + 'title' => $this->t('Views module | Drupal.org'), + 'url' => 'https://www.drupal.org/docs/8/core/modules/views', + ], + [ + 'title' => $this->t('Webform Views Integration | Drupal.org'), + 'url' => 'https://www.drupal.org/project/webform_views', + ], + [ + 'title' => $this->t('D8 Webform and Webform Views Integration @ Drupalcamp Colorado'), + 'url' => 'https://www.youtube.com/watch?v=Riw9g_y1A_s', + ], + ], + ], + 'attachments' => [ + 'title' => $this->t('Sending webform email attachments'), + 'content' => $this->t('This presentation shows how to set up and add email attachments via an email handler.'), + 'youtube_id' => 'w7exQFDIHhQ', + 'presentation_id' => '1DTE9nSg_CKhWkhBCmfks_o2RoeApTHc4orhNxrj2imk', + 'links' => [ + [ + 'title' => $this->t('How to send email attachments? | Drupal.org'), + 'url' => 'https://www.drupal.org/node/3021480 ', + ], + [ + 'title' => $this->t('Webform Attachment sub-module | Drupal.org'), + 'url' => 'https://www.drupal.org/node/3021481', + ], + ], + ], 'translations' => [ 'title' => $this->t('Translating webforms'), 'content' => $this->t("This screencast shows how to translate a webform's title, descriptions, label and messages."), @@ -1011,6 +1195,148 @@ protected function initVideos() { 'youtube_id' => 'zl_ErUKymYo', 'presentation_id' => '14vpNvDhYKGhHspu9BurIneTL4C1spyfwsqI82MvTYUA', ], + 'accessibility' => [ + 'title' => $this->t('Webform Accessibility'), + 'content' => $this->t('This presentation is about approaching accessibility using the Webform module for Drupal 8.'), + 'youtube_id' => 'JR0wnd6Orfk', + 'presentation_id' => '1ni2a9id7VT67uO3f0i1UMt9_dswfcSHW1gZcXGCSEcM', + 'links' => [ + [ + 'title' => $this->t('Accessibility | Drupal.org'), + 'url' => 'https://www.drupal.org/about/features/accessibility', + ], + [ + 'title' => $this->t('Drupal 8 Accessibility'), + 'url' => 'https://www.drupal.org/docs/8/accessibility', + ], + [ + 'title' => $this->t('Webform Accessibility'), + 'url' => 'https://www.drupal.org/docs/8/modules/webform/webform-accessibility', + ], + ], + ], + 'advanced' => [ + 'title' => $this->t('Advanced Webforms'), + 'content' => $this->t('This presentation gives you the extra knowledge you need to get the most out the Webform module.'), + 'youtube_id' => 'Yg2lAzE1heM', + 'presentation_id' => '1TMo0vBjkdtfcIsYWhxQnjO_rG9ebK64oHhdPvTvwNus', + ], + 'healthcare' => [ + 'title' => $this->t('Webforms for Healthcare'), + 'content' => $this->t('This presentation discusses how healthcare organizations can leverage the Webform module for Drupal 8.'), + 'youtube_id' => 'YiK__YobDJw', + 'presentation_id' => '1jxbJkovaubHrhvjIZ-_OoK0zsANqC1vG4HFvAxfszOE/edit?usp=sharing', + ], + ]; + + // WebWash (www.webwash.net/). + $videos += [ + 'webwash_webform' => [ + 'title' => $this->t('How to Create Forms using Webform and Contact in Drupal 8'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create forms using Webform and Contact module in Drupal 8.'), + 'youtube_id' => 'u8PBW0K9I9I', + 'links' => [ + [ + 'title' => $this->t('Getting Started with Webform in Drupal 8: Part I | WebWash'), + 'url' => 'https://www.webwash.net/getting-started-webform-drupal-8/', + ], + [ + 'title' => $this->t('Moving Forward with Webform in Drupal 8: Part II | WebWash'), + 'url' => 'https://www.webwash.net/moving-forward-webform-drupal-8/ ', + ], + ] + ], + 'webwash_install' => [ + 'title' => $this->t('Using Webform in Drupal 8, 1.1: Install Webform'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to download and install the Webform module.'), + 'youtube_id' => 'T4CiLF8fwFQ', + ], + 'webwash_create' => [ + 'title' => $this->t('Using Webform in Drupal 8, 1.2: Create a Form'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create a form from scratch and add three elements to it: Name, Email and Telephone.'), + 'youtube_id' => 'fr3kTiYKNls', + ], + 'webwash_conditional' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.1: Create Conditional Elements'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create conditional elements.'), + 'youtube_id' => 'ic4wu-iZd4Y', + ], + 'webwash_wizard' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.2: Create Multi-step Wizard'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create a multi-step page form.'), + 'youtube_id' => 'k17W2yH71ak', + ], + 'webwash_float' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.3: Float Elements Next to Each Other'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to float elements next to each other on a form.'), + 'youtube_id' => 'EgFNqfVboHQ', + ], + 'webwash_options' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.4: Create List Options'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create reusable list options for elements.'), + 'youtube_id' => 'magHXd9DNpg', + 'links' => [ + [ + 'title' => $this->t('How to Use Webform Predefined Options in Drupal 8 | WebWash'), + 'url' => 'https://www.webwash.net/use-webform-predefined-options-drupal-8/', + ], + ], + ], + 'webwash_email' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.5: Sending Emails'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to send emails when a submission is submitted.'), + 'youtube_id' => 'kSzi1J1MyBc', + ], + 'webwash_confirmation' => [ + 'title' => $this->t('Using Webform in Drupal 8, 2.6: Create Confirmation Page'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to create a custom confirmation page.'), + 'youtube_id' => 'edYCWGoLzZk', + ], + 'webwash_submissions' => [ + 'title' => $this->t('Using Webform in Drupal 8, 3.1: View, Download and Clear Submissions'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to view and manage submission data.'), + 'youtube_id' => 'dftBF8P4Lh4', + ], + 'webwash_drafts' => [ + 'title' => $this->t('Using Webform in Drupal 8, 3.2: Allow Draft Submissions'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to allow users to save draft submissions.'), + 'youtube_id' => 'xA3RtJFZc_4', + ], + 'webwash_zapier' => [ + 'title' => $this->t('Using Webform in Drupal 8, 4.1: Send Submissions to Zapier'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to integrate Webform with other system using Zapier.'), + 'youtube_id' => 'GY0F-rya2iY', + 'links' => [ + [ + 'title' => $this->t('Integrate Webform and Google Sheets using Zapier in Drupal 8 | WebWash'), + 'url' => 'https://www.webwash.net/integrate-webform-and-google-sheets-using-zapier-in-drupal-8/', + ], + ], + ], + 'webwash_block' => [ + 'title' => $this->t('Using Webform in Drupal 8, 5.1: Display Form as a Block'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to display a form as a block.'), + 'youtube_id' => 'men4peeDS_4', + ], + 'webwash_node' => [ + 'title' => $this->t('Using Webform in Drupal 8, 5.2: Display Form using Webform Node'), + 'owner' => $this->t('WebWash'), + 'content' => $this->t('Learn how to display forms using Webform Node sub-module.'), + 'youtube_id' => '29pntXdy81k', + ], ]; foreach ($videos as $id => &$video_info) { $video_info['id'] = $id; @@ -1176,13 +1502,28 @@ protected function initHelp() { ], ]; + + /**************************************************************************/ + // Help. + /**************************************************************************/ + + $help['help'] = [ + 'group' => 'help', + 'title' => $this->t('Help'), + 'content' => $this->t('Visit the Webform 8.x-5.x <a href="https://www.drupal.org/node/2856146">documentation pages</a> for an <a href="https://www.drupal.org/node/2834423">introduction</a>, <a href="https://www.drupal.org/node/2837024">features overview</a>, <a href="https://www.drupal.org/node/2932764">articles</a>, <a href="https://www.drupal.org/node/2860989">recipes</a>, <a href="https://www.drupal.org/node/2932760">known issues</a>, and a <a href="https://www.drupal.org/node/2843422">roadmap</a>.'), + 'routes' => [ + // @see /admin/structure/webform/help + 'webform.help', + ], + ]; + /**************************************************************************/ // Contribute. /**************************************************************************/ // Contribute. $help['contribute'] = [ - 'group' => 'contribute', + 'group' => 'help', 'title' => $this->t('Contribute'), 'content' => $this->t('The <strong>Contribute</strong> page encourages individuals and organizations to join the Drupal community, become members of the Drupal Association, and contribute to Drupal projects, events, and more.'), 'video_id' => 'about', @@ -1191,6 +1532,7 @@ protected function initHelp() { 'webform.contribute', ], ]; + /**************************************************************************/ // Configuration. /**************************************************************************/ @@ -1282,20 +1624,37 @@ protected function initHelp() { ]; // Configuration: Libraries. + $t_args = [ + '@webform-libraries-make' => 'webform-libraries-make', + '@webform-libraries-composer' => 'webform-libraries-composer', + '@webform-libraries-download' => 'webform-libraries-download', + '@webform-composer-update' => 'webform-composer-update', + ]; + $drush_version = (class_exists('\Drush\Drush')) ? \Drush\Drush::getMajorVersion() : 8; + if ($drush_version >= 9) { + foreach ($t_args as $command_name => $command) { + $t_args[$command_name] = str_replace('-', ':', $command); + } + } $help['config_libraries_help'] = [ 'group' => 'configuration', 'title' => $this->t('Configuration: Libraries: Help'), 'content' => '<p>' . $this->t('The Webform module utilizes third-party Open Source libraries to enhance webform elements and to provide additional functionality.') . ' ' . - $this->t('It is recommended that these libraries are installed in your Drupal installations /libraries directory.') . ' ' . + $this->t('It is recommended that these libraries are installed in your Drupal installations /libraries or /web/libraries directory.') . ' ' . $this->t('If these libraries are not installed, they will be automatically loaded from a CDN.') . ' ' . $this->t('All libraries are optional and can be excluded via the admin settings form.') . '</p>' . '<p>' . $this->t('There are several ways to download the needed third-party libraries.') . '</p>' . + '<p><strong>' . $this->t('Recommended') . '</strong></p>' . '<ul>' . - '<li>' . $this->t('Generate a *.make.yml or composer.json file using <code>drush webform-libraries-make</code> or <code>drush webform-libraries-composer</code>.') . '</li>' . - '<li>' . $this->t('Execute <code>drush webform-libraries-download</code>, which will download third-party libraries required by the Webform module.') . '</li>' . - '<li>' . $this->t("Execute <code>drush webform-composer-update</code>, which will update your Drupal installation's composer.json to include the Webform module's selected libraries as repositories.") . '</li>' . - '<li>' . $this->t('Download and extract a <a href=":href">zipped archive containing all webform libraries</a> and extract the directories and files to /libraries', [':href' => 'https://cgit.drupalcode.org/sandbox-jrockowitz-2941983/plain/libraries.zip']) . '</li>' . + '<li>' . $this->t('Use the <a href="https://github.com/wikimedia/composer-merge-plugin">Composer Merge plugin</a> to include the Webform module\'s <a href="https://cgit.drupalcode.org/webform/tree/composer.libraries.json">composer.libraries.json</a> or generate a custom file using <code>drush @webform-libraries-composer > DRUPAL_ROOT/composer.libraries.json</code>.', $t_args) . '<br/><strong>' . t('<a href="https://www.drupal.org/node/3003140">Learn more »</a>') . '</strong>'. '</li>' . + '</ul>' . + '<p><strong>' . $this->t('Alternatives') . '</strong></p>' . + '<ul>' . + '<li>' . $this->t('Generate a *.make.yml or composer.json file using <code>drush @webform-libraries-make</code> or <code>drush @webform-libraries-composer</code>.', $t_args) . '</li>' . + '<li>' . $this->t('Execute <code>drush @webform-libraries-download</code>, to download third-party libraries required by the Webform module. (OSX/Linux)', $t_args) . '</li>' . + '<li>' . $this->t("Execute <code>drush @webform-composer-update</code>, to update your Drupal installation's composer.json to include the Webform module's selected libraries as repositories.", $t_args) . '</li>' . + '<li>' . $this->t('Download and extract a <a href=":href">zipped archive containing all webform libraries</a> and extract the directories and files to /libraries or /web/libraries', [':href' => 'https://cgit.drupalcode.org/sandbox-jrockowitz-2941983/plain/libraries.zip']) . '</li>' . '</ul>', 'message_type' => 'info', 'message_close' => TRUE, @@ -1342,8 +1701,8 @@ protected function initHelp() { $this->t('<strong>Webform Element</strong> plugins are used to enhance existing render/form elements. Webform element plugins provide default properties, data normalization, custom validation, element configuration form and customizable display formats.'), 'video_id' => 'plugins', 'routes' => [ - // @see /admin/structure/webform/plugins/elements - 'webform.element_plugins', + // @see /admin/reports/webform-plugins/elements + 'webform.reports_plugins.elements', ], ]; @@ -1355,8 +1714,8 @@ protected function initHelp() { $this->t('<strong>Handlers</strong> are used to route submitted data to external applications and send notifications & confirmations.'), 'video_id' => 'plugins', 'routes' => [ - // @see /admin/structure/webform/plugins/handlers - 'webform.handler_plugins', + // @see /admin/reports/webform-plugins/handlers + 'webform.reports_plugins.handlers', ], ]; @@ -1368,8 +1727,8 @@ protected function initHelp() { $this->t('<strong>Exporters</strong> are used to export results into a downloadable format that can be used by MS Excel, Google Sheets and other spreadsheet applications.'), 'video_id' => 'plugins', 'routes' => [ - // @see /admin/structure/webform/plugins/exporters - 'webform.exporter_plugins', + // @see /admin/reports/webform-plugins/exporters + 'webform.reports_plugins.exporters', ], ]; @@ -1395,7 +1754,7 @@ protected function initHelp() { 'group' => 'forms', 'title' => $this->t('Webform: Test'), 'content' => $this->t("The <strong>Test</strong> form allows a webform to be tested using a customizable test dataset.") . ' ' . - $this->t('Multiple test submissions can be created using the devel_generate module.'), + $this->t('Multiple test submissions can be created using the Devel generate module.'), 'video_id' => 'forms', 'routes' => [ // @see /admin/structure/webform/manage/{webform}/test @@ -1554,30 +1913,6 @@ protected function initHelp() { ], ]; - // Submissions: Purge. - $help['submissions_purge'] = [ - 'group' => 'submissions', - 'title' => $this->t('Submissions: Purge'), - 'content' => $this->t('The <strong>Submissions purge</strong> page allows all submissions across all webforms to be deleted. <strong>PLEASE NOTE: THIS ACTION CANNOT BE UNDONE.</strong>'), - 'message_type' => 'warning', - 'routes' => [ - // @see /admin/structure/webform/results/purge - 'entity.webform_submission.collection_purge', - ], - ]; - - // Submissions: Log. - $help['submissions_log'] = [ - 'group' => 'submissions', - 'title' => $this->t('Submissions: Log'), - 'content' => $this->t('The <strong>Submissions log</strong> page tracks all submission events for all webforms that have submission logging enabled. Submission logging can be enabled globally or on a per webform basis.'), - 'routes' => [ - // @see /admin/structure/webform/results/log - 'entity.webform_submission.collection_log', - ], - ]; - - // Results. $help['results'] = [ 'group' => 'submissions', @@ -1591,17 +1926,6 @@ protected function initHelp() { ], ]; - // Results: Log. - $help['results_log'] = [ - 'group' => 'submissions', - 'title' => $this->t('Results: Log'), - 'content' => $this->t('The <strong>Results Log</strong> lists all webform submission events for the current webform.'), - 'routes' => [ - // @see /admin/structure/webform/manage/{webform}/results/log - 'entity.webform.results_log', - ], - ]; - // Results: Download. $help['results_download'] = [ 'group' => 'submissions', @@ -1613,17 +1937,6 @@ protected function initHelp() { ], ]; - // Results: Clear. - $help['results_clear'] = [ - 'group' => 'submissions', - 'title' => $this->t('Results: Clear'), - 'content' => $this->t("The <strong>Clear</strong> page allows all submissions to a webform to be deleted."), - 'routes' => [ - // @see /admin/structure/webform/manage/{webform}/results/clear - 'entity.webform.results_clear', - ], - ]; - /**************************************************************************/ // Submission. /**************************************************************************/ @@ -1680,19 +1993,6 @@ protected function initHelp() { ], ]; - $help['submission_log'] = [ - 'group' => 'submission', - 'title' => $this->t('Submission: Log'), - 'content' => $this->t("The <strong>Log</strong> page shows all events and transactions for a submission."), - 'video_id' => 'submission', - 'routes' => [ - // @see /admin/structure/webform/manage/{webform}/submission/{webform_submisssion}/log - 'entity.webform_submission.log', - // @see /node/{node}/webform/submission/{webform_submission}/log - 'entity.node.webform_submission.log', - ], - ]; - $help['submission_edit'] = [ 'group' => 'submission', 'title' => $this->t('Submission: Edit'), @@ -1808,15 +2108,6 @@ protected function initHelp() { 'entity.node.webform.results_submissions', ], ]; - $help['webform_node_results_log'] = [ - 'group' => 'webform_nodes', - 'title' => $this->t('Webform Node: Results: Log'), - 'content' => $this->t('The <strong>Results Log</strong> lists all webform submission events for the current webform.'), - 'routes' => [ - // @see /node/{node}/webform/results/log - 'entity.node.webform.results_log', - ], - ]; $help['webform_node_results_download'] = [ 'group' => 'webform_nodes', 'title' => $this->t('Webform Node: Results: Download'), @@ -1826,15 +2117,6 @@ protected function initHelp() { 'entity.node.webform.results_export', ], ]; - $help['webform_node_results_clear'] = [ - 'group' => 'webform_nodes', - 'title' => $this->t('Webform Node: Results: Clear'), - 'content' => $this->t("The <strong>Clear</strong> page allows all submissions to a webform node to be deleted."), - 'routes' => [ - // @see /node/{node}/webform/results/clear - 'entity.node.webform.results_clear', - ], - ]; // Webform Block. $help['webform_block'] = [ @@ -1847,6 +2129,22 @@ protected function initHelp() { ], ]; + // Webform Accessibility. + $help['webform_accessibility'] = [ + 'group' => 'webform_accessibility', + 'title' => $this->t('Webform Node'), + 'content' => $this->t("The Webform module aims to be accessible to all users."), + 'video_id' => 'accessibility', + 'paths' => [ + '/admin/structure/webform/manage/example_accessibility_*', + ], + 'message_type' => 'info', + 'message_close' => TRUE, + 'message_storage' => WebformMessage::STORAGE_USER, + 'access' => $this->currentUser->hasPermission('administer webform'), + 'weight' => -10, + ]; + /**************************************************************************/ // Messages. /**************************************************************************/ diff --git a/web/modules/webform/src/WebformHelpManagerInterface.php b/web/modules/webform/src/WebformHelpManagerInterface.php index dc25477983..3d3e416cd6 100644 --- a/web/modules/webform/src/WebformHelpManagerInterface.php +++ b/web/modules/webform/src/WebformHelpManagerInterface.php @@ -43,7 +43,7 @@ public function getHelp($id = NULL); public function getVideo($id = NULL); /** - * Get video. links + * Get video links. * * @param string $id * Video id. @@ -57,7 +57,7 @@ public function getVideoLinks($id); * Sets a notification to be displayed to webform administrators. * * @param string $id - * The notification id + * The notification id. * @param string|\Drupal\Component\Render\MarkupInterface|array $message * The notification to be displayed to webform administrators. * @param string $type @@ -97,7 +97,7 @@ public function getNotifications($type = NULL); * Delete a notification by id. * * @param string $id - * The notification id + * The notification id. * * @internal * Currently being used to display notifications related to updates. diff --git a/web/modules/webform/src/WebformInterface.php b/web/modules/webform/src/WebformInterface.php index 755e0e9f2d..b32eb2d667 100644 --- a/web/modules/webform/src/WebformInterface.php +++ b/web/modules/webform/src/WebformInterface.php @@ -4,7 +4,6 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityWithPluginCollectionInterface; -use Drupal\Core\Session\AccountInterface; use Drupal\user\EntityOwnerInterface; use Drupal\webform\Plugin\WebformHandlerInterface; @@ -13,6 +12,26 @@ */ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface, EntityOwnerInterface { + /** + * Webform title. + */ + const TITLE_WEBFORM = 'webform'; + + /** + * Source entity title. + */ + const TITLE_SOURCE_ENTITY = 'source_entity'; + + /** + * Both source entity and webform title. + */ + const TITLE_SOURCE_ENTITY_WEBFORM = 'source_entity_webform'; + + /** + * Both webform and source entity title. + */ + const TITLE_WEBFORM_SOURCE_ENTITY = 'webform_source_entity'; + /** * Denote drafts are not allowed. * @@ -49,6 +68,11 @@ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollec */ const STATUS_SCHEDULED = 'scheduled'; + /** + * Webform status archived. + */ + const STATUS_ARCHIVED = 'archived'; + /** * Webform confirmation page. */ @@ -84,6 +108,31 @@ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollec */ const CONFIRMATION_DEFAULT = 'default'; + /** + * Webform confirmation none. + */ + const CONFIRMATION_NONE = 'none'; + + /** + * Display standard 403 access denied page. + */ + const ACCESS_DENIED_DEFAULT = 'default'; + + /** + * Display customized access denied message. + */ + const ACCESS_DENIED_MESSAGE = 'message'; + + /** + * Display customized 403 access denied page. + */ + const ACCESS_DENIED_PAGE = 'page'; + + /** + * Redirect to user login with custom message. + */ + const ACCESS_DENIED_LOGIN = 'login'; + /** * Returns the webform's (original) langcode. * @@ -92,6 +141,16 @@ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollec */ public function getLangcode(); + /** + * Returns the webform's weight. + * + * Only applies to when multiple webforms are attached to a single node. + * + * @return int + * The webform's weight. + */ + public function getWeight(); + /** * Determine if the webform has page or is attached to other entities. * @@ -108,6 +167,14 @@ public function hasPage(); */ public function hasManagedFile(); + /** + * Determine if the webform's elements include attachments. + * + * @return bool + * TRUE if the webform's elements include attachments. + */ + public function hasAttachments(); + /** * Determine if the webform is using a Flexbox layout. * @@ -165,10 +232,10 @@ public function getNumberOfActions(); public function hasPreview(); /** - * Determine if the webform has multistep form wizard pages. + * Determine if the webform has multi-step form wizard pages. * * @return bool - * TRUE if the webform has multistep form wizard pages. + * TRUE if the webform has multi-step form wizard pages. */ public function hasWizardPages(); @@ -180,6 +247,34 @@ public function hasWizardPages(); */ public function getNumberOfWizardPages(); + /** + * Returns the webform's current operation. + * + * @return string + * The webform's operation. + */ + public function getOperation(); + + /** + * Sets the webform's current operation . + * + * @param string $operation + * The webform's operation. + * + * @return $this + * + * @see \Drupal\webform\WebformSubmissionForm + */ + public function setOperation($operation); + + /** + * Determine if the webform is being tested. + * + * @return bool + * TRUE if the webform is being tested. + */ + public function isTest(); + /** * Sets the webform settings and properties override state. * @@ -258,14 +353,30 @@ public function isOpening(); */ public function isTemplate(); + /** + * Returns the webform archive indicator. + * + * @return bool + * TRUE if the webform is archived. + */ + public function isArchived(); + /** * Returns the webform confidential indicator. * * @return bool - * TRUE if the webform is confidential . + * TRUE if the webform is confidential. */ public function isConfidential(); + /** + * Determine if remote IP address is being stored. + * + * @return bool + * TRUE if remote IP address is being stored. + */ + public function hasRemoteAddr(); + /** * Determine if the saving of submissions is disabled. * @@ -453,18 +564,18 @@ public function setSettingOverride($key, $value); public function setPropertyOverride($property_name, $value); /** - * Returns the webform access controls. + * Returns the webform access rules. * * @return array - * A structured array containing all the webform access controls. + * A structured array containing all the webform access rules. */ public function getAccessRules(); /** - * Sets the webform access. + * Sets the webform access rules. * * @param array $access - * The structured array containing all the webform access controls. + * The structured array containing all the webform access rules. * * @return $this */ @@ -478,30 +589,6 @@ public function setAccessRules(array $access); */ public static function getDefaultSettings(); - /** - * Returns the webform default access controls. - * - * @return array - * A structured array containing all the webform default access controls. - */ - public static function getDefaultAccessRules(); - - /** - * Checks webform access to an operation on a webform's submission. - * - * @param string $operation - * The operation access should be checked for. - * Usually "create", "view", "update", "delete", "purge", or "admin". - * @param \Drupal\Core\Session\AccountInterface $account - * The user session for which to check access. - * @param \Drupal\webform\WebformSubmissionInterface|null $webform_submission - * (optional) A webform submission. - * - * @return \Drupal\Core\Access\AccessResultInterface - * The access result. - */ - public function checkAccessRules($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission = NULL); - /** * Get webform submission webform. * @@ -616,13 +703,37 @@ public function getElementsInitializedAndFlattened($operation = NULL); public function getElementsInitializedFlattenedAndHasValue($operation = NULL); /** - * Get webform elements selectors as options. + * Get webform managed file elements. + * + * @return array + * Webform managed file elements. + */ + public function getElementsManagedFiles(); + + /** + * Get webform attachemnt elements. + * + * @return array + * Webform attachment elements. + */ + public function getElementsAttachments(); + + /** + * Get webform element's selectors as options. * * @return array * Webform elements selectors as options. */ public function getElementsSelectorOptions(); + /** + * Get webform element options as autocomplete source values. + * + * @return array + * Webform element options as autocomplete source values. + */ + public function getElementsSelectorSourceValues(); + /** * Get webform elements that can be prepopulated. * @@ -777,7 +888,7 @@ public function deleteWebformHandler(WebformHandlerInterface $handler); * Invoke a handlers method. * * @param string $method - * The handle method to be invoked. + * The handler method to be invoked. * @param mixed $data * The argument to passed by reference to the handler method. * @param mixed $context1 @@ -791,7 +902,7 @@ public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = * Invoke elements method. * * @param string $method - * The handle method to be invoked. + * The handler method to be invoked. * @param mixed $data * The argument to passed by reference to the handler method. * @param mixed $context1 diff --git a/web/modules/webform/src/WebformLibrariesManager.php b/web/modules/webform/src/WebformLibrariesManager.php index b808744f11..5f285ba91e 100644 --- a/web/modules/webform/src/WebformLibrariesManager.php +++ b/web/modules/webform/src/WebformLibrariesManager.php @@ -102,6 +102,12 @@ public function requirements($cli = FALSE) { ]; foreach ($libraries as $library_name => $library) { + // Excluded. + if ($this->isExcluded($library_name)) { + $stats['@excluded']++; + continue; + } + $library_path = '/libraries/' . $library_name; $library_exists = (file_exists(DRUPAL_ROOT . $library_path)) ? TRUE : FALSE; @@ -117,17 +123,13 @@ public function requirements($cli = FALSE) { ':settings_elements_href' => Url::fromRoute('webform.config.elements')->toString(), ]; - if ($this->isExcluded($library_name)) { - // Excluded. - $stats['@excluded']++; - $title = $this->t('<strong>@title</strong> (Excluded)', $t_args); - if (!empty($library['elements']) && $this->areElementsExcluded($library['elements'])) { - $t_args['@element_type'] = implode('; ', $library['elements']); - $description = $this->t('The <a href=":homepage_href">@title</a> library is excluded because required element types (@element_type) are <a href=":settings_elements_href">excluded</a>.', $t_args); - } - else { - $description = $this->t('The <a href=":homepage_href">@title</a> library is <a href=":settings_libraries_href">excluded</a>.', $t_args); - } + if (!empty($library['module'])) { + // Installed by module. + $t_args['@module'] = $library['module']; + $t_args[':module_href'] = 'https://www.drupal.org/project/' . $library['module']; + $stats['@installed']++; + $title = $this->t('<strong>@title</strong> (Installed)', $t_args); + $description = $this->t('The <a href=":homepage_href">@title</a> library is installed by the <b><a href=":module_href">@module</a></b> module.', $t_args); } elseif ($library_exists) { // Installed. @@ -192,7 +194,7 @@ public function requirements($cli = FALSE) { */ public function getLibrary($name) { $libraries = $this->getLibraries(); - return $libraries [$name]; + return $libraries[$name]; } /** @@ -281,10 +283,9 @@ protected function initLibraries() { 'plugin_path' => 'libraries/ckeditor.autogrow/', 'plugin_url' => "https://cdn.rawgit.com/ckeditor/ckeditor-dev/$ckeditor_version/plugins/autogrow/", 'version' => $ckeditor_version, - 'optional' => TRUE, ]; $libraries['ckeditor.fakeobjects'] = [ - 'title' => $this->t('CKEditor: Fakeobjects'), + 'title' => $this->t('CKEditor: Fake Objects'), 'description' => $this->t('Utility required by CKEditor link plugin.'), 'notes' => $this->t('Allows CKEditor to use basic image and link dialog.'), 'homepage_url' => Url::fromUri('https://ckeditor.com/addon/fakeobjects'), @@ -292,7 +293,6 @@ protected function initLibraries() { 'plugin_path' => 'libraries/ckeditor.fakeobjects/', 'plugin_url' => "https://cdn.rawgit.com/ckeditor/ckeditor-dev/$ckeditor_version/plugins/fakeobjects/", 'version' => $ckeditor_version, - 'optional' => TRUE, ]; $libraries['ckeditor.image'] = [ 'title' => $this->t('CKEditor: Image'), @@ -303,7 +303,6 @@ protected function initLibraries() { 'plugin_path' => 'libraries/ckeditor.image/', 'plugin_url' => "https://cdn.rawgit.com/ckeditor/ckeditor-dev/$ckeditor_version/plugins/image/", 'version' => $ckeditor_version, - 'optional' => TRUE, ]; $libraries['ckeditor.link'] = [ 'title' => $this->t('CKEditor: Link'), @@ -314,27 +313,38 @@ protected function initLibraries() { 'plugin_path' => 'libraries/ckeditor.link/', 'plugin_url' => "https://cdn.rawgit.com/ckeditor/ckeditor-dev/$ckeditor_version/plugins/link/", 'version' => $ckeditor_version, - 'optional' => TRUE, ]; $libraries['ckeditor.codemirror'] = [ 'title' => $this->t('CKEditor: CodeMirror'), 'description' => $this->t('Provides syntax highlighting for the CKEditor with the CodeMirror Plugin.'), 'notes' => $this->t('Makes it easier to edit the HTML source.'), 'homepage_url' => Url::fromUri('https://github.com/w8tcha/CKEditor-CodeMirror-Plugin'), - 'download_url' => Url::fromUri('https://github.com/w8tcha/CKEditor-CodeMirror-Plugin/releases/download/v1.17.5/CKEditor-CodeMirror-Plugin.zip'), + 'download_url' => Url::fromUri('https://github.com/w8tcha/CKEditor-CodeMirror-Plugin/releases/download/v1.17.7/CKEditor-CodeMirror-Plugin.zip'), 'plugin_path' => 'libraries/ckeditor.codemirror/codemirror/', - 'plugin_url' => "https://cdn.rawgit.com/w8tcha/CKEditor-CodeMirror-Plugin/v1.17.5/codemirror/", - 'version' => 'v1.17.5', - 'optional' => TRUE, + 'plugin_url' => "https://cdn.rawgit.com/w8tcha/CKEditor-CodeMirror-Plugin/v1.17.7/codemirror/", + 'version' => 'v1.17.7', ]; $libraries['codemirror'] = [ 'title' => $this->t('Code Mirror'), 'description' => $this->t('Code Mirror is a versatile text editor implemented in JavaScript for the browser.'), 'notes' => $this->t('Code Mirror is used to provide a text editor for YAML, HTML, CSS, and JavaScript configuration settings and messages.'), 'homepage_url' => Url::fromUri('http://codemirror.net/'), - 'download_url' => Url::fromUri('https://github.com/components/codemirror/archive/5.36.0.zip'), - 'version' => '5.36.0', - 'optional' => TRUE, + 'download_url' => Url::fromUri('https://github.com/components/codemirror/archive/5.44.0.zip'), + 'issues_url' => Url::fromUri('https://github.com/codemirror/codemirror/issues'), + 'version' => '5.44.0', + ]; + $libraries['algolia.places'] = [ + 'title' => $this->t('Algolia Places'), + 'description' => $this->t('Algolia Places provides a fast, distributed and easy way to use an address search autocomplete JavaScript library on your website.'), + 'notes' => $this->t('Algolia Places is by the location places elements.'), + 'homepage_url' => Url::fromUri('https://github.com/algolia/places'), + 'issues_url' => Url::fromUri('https://github.com/algolia/places/issues'), + // NOTE: Using NPM/JsDelivr because it contains the '/dist/cdn/' directory. + // @see https://asset-packagist.org/package/detail?fullname=npm-asset/places.js + // @see https://www.jsdelivr.com/package/npm/places.js + 'download_url' => Url::fromUri('https://registry.npmjs.org/places.js/-/places.js-1.16.1.tgz'), + 'version' => '1.16.1', + 'elements' => ['webform_location_places'], ]; $libraries['jquery.geocomplete'] = [ 'title' => $this->t('jQuery: Geocoding and Places Autocomplete Plugin'), @@ -343,7 +353,8 @@ protected function initLibraries() { 'homepage_url' => Url::fromUri('http://ubilabs.github.io/geocomplete/'), 'download_url' => Url::fromUri('https://github.com/ubilabs/geocomplete/archive/1.7.0.zip'), 'version' => '1.7.0', - 'elements' => ['webform_location'], + 'elements' => ['webform_location_geocomplete'], + 'deprecated' => $this->t('The jQuery: Geocoding and Places Autocomplete Plugin library is not being maintained. It has been <a href=":href">deprecated</a> and will be removed before Webform 8.x-6.0.', [':href' => 'https://www.drupal.org/node/2991275']), ]; $libraries['jquery.icheck'] = [ 'title' => $this->t('jQuery: iCheck'), @@ -352,31 +363,28 @@ protected function initLibraries() { 'homepage_url' => Url::fromUri('http://icheck.fronteed.com/'), 'download_url' => Url::fromUri('https://github.com/fronteed/icheck/archive/1.0.2.zip'), 'version' => '1.0.2 ', - 'optional' => TRUE, - 'deprecated' => $this->t('The iCheck library is not being maintained and has been <a href=":href">deprecated</a>. It wil be removed before Webform 8.x-5.0.', [':href' => 'https://www.drupal.org/project/webform/issues/2931154']), + 'deprecated' => $this->t('The iCheck library is not being maintained. It has been <a href=":href">deprecated</a> and will be removed before Webform 8.x-6.0.', [':href' => 'https://www.drupal.org/project/webform/issues/2931154']), ]; $libraries['jquery.inputmask'] = [ 'title' => $this->t('jQuery: Input Mask'), - 'description' => $this->t('Input masks ensures a predefined format is entered. This can be useful for dates, numerics, phone numbers, etc...'), + 'description' => $this->t('Input masks ensures a predefined format is entered. This can be useful for dates, numerics, phone numbers, etc…'), 'notes' => $this->t('Input masks are used to ensure predefined and custom formats for text fields.'), 'homepage_url' => Url::fromUri('https://robinherbots.github.io/Inputmask/'), - 'download_url' => Url::fromUri('https://github.com/RobinHerbots/jquery.inputmask/archive/3.3.11.zip'), - 'version' => '3.3.11', - 'optional' => TRUE, + 'download_url' => Url::fromUri('https://github.com/RobinHerbots/jquery.inputmask/archive/4.0.6.zip'), + 'version' => '4.0.6', ]; $libraries['jquery.intl-tel-input'] = [ 'title' => $this->t('jQuery: International Telephone Input'), 'description' => $this->t("A jQuery plugin for entering and validating international telephone numbers. It adds a flag dropdown to any input, detects the user's country, displays a relevant placeholder and provides formatting/validation methods."), 'notes' => $this->t('International Telephone Input is used by the Telephone element.'), 'homepage_url' => Url::fromUri('https://github.com/jackocnr/intl-tel-input'), - 'download_url' => Url::fromUri('https://github.com/jackocnr/intl-tel-input/archive/v12.1.12.zip'), - 'version' => '12.1.12', - 'optional' => TRUE, + 'download_url' => Url::fromUri('https://github.com/jackocnr/intl-tel-input/archive/v15.0.1.zip'), + 'version' => '15.0.1', ]; $libraries['jquery.rateit'] = [ 'title' => $this->t('jQuery: RateIt'), 'description' => $this->t("Rating plugin for jQuery. Fast, progressive enhancement, touch support, customizable (just swap out the images, or change some CSS), unobtrusive JavaScript (using HTML5 data-* attributes), RTL support. The Rating plugin supports as many stars as you'd like, and also any step size."), - 'notes' => $this->t('RateIt is used to provide a customizable rating webform element.'), + 'notes' => $this->t('RateIt is used to provide a customizable rating element.'), 'homepage_url' => Url::fromUri('https://github.com/gjunge/rateit.js'), 'download_url' => Url::fromUri('https://github.com/gjunge/rateit.js/archive/1.1.1.zip'), 'version' => '1.1.1', @@ -389,25 +397,32 @@ protected function initLibraries() { 'homepage_url' => Url::fromUri('https://select2.github.io/'), 'download_url' => Url::fromUri('https://github.com/select2/select2/archive/4.0.5.zip'), 'version' => '4.0.5', - 'optional' => TRUE, + 'module' => $this->moduleHandler->moduleExists('select2') ? 'select2' : '', ]; $libraries['jquery.chosen'] = [ 'title' => $this->t('jQuery: Chosen'), 'description' => $this->t('A jQuery plugin that makes long, unwieldy select boxes much more user-friendly.'), 'notes' => $this->t('Chosen is used to improve the user experience for select menus. Chosen is an alternative to Select2.'), 'homepage_url' => Url::fromUri('https://harvesthq.github.io/chosen/'), - 'download_url' => Url::fromUri('https://github.com/harvesthq/chosen/releases/download/v1.8.3/chosen_v1.8.3.zip'), - 'version' => '1.8.3', - 'optional' => TRUE, + 'download_url' => Url::fromUri('https://github.com/harvesthq/chosen/releases/download/v1.8.7/chosen_v1.8.7.zip'), + 'version' => '1.8.7', + 'module' => $this->moduleHandler->moduleExists('chosen') ? 'chosen' : '', + ]; + $libraries['jquery.textcounter'] = [ + 'title' => $this->t('jQuery: Text Counter'), + 'description' => $this->t('A jQuery plugin for counting and limiting characters/words on text input, or textarea, elements.'), + 'notes' => $this->t('Word or character counting, with server-side validation, is available for text fields and text areas.'), + 'homepage_url' => Url::fromUri('https://github.com/ractoon/jQuery-Text-Counter'), + 'download_url' => Url::fromUri('https://github.com/ractoon/jQuery-Text-Counter/archive/0.8.0.zip'), + 'version' => '0.8.0', ]; $libraries['jquery.timepicker'] = [ 'title' => $this->t('jQuery: Timepicker'), 'description' => $this->t('A lightweight, customizable javascript timepicker plugin for jQuery, inspired by Google Calendar.'), 'notes' => $this->t('Timepicker is used to provide a polyfill for HTML 5 time elements.'), 'homepage_url' => Url::fromUri('https://github.com/jonthornton/jquery-timepicker'), - 'download_url' => Url::fromUri('https://github.com/jonthornton/jquery-timepicker/archive/1.11.13.zip'), - 'version' => '1.11.13', - 'optional' => TRUE, + 'download_url' => Url::fromUri('https://github.com/jonthornton/jquery-timepicker/archive/1.11.14.zip'), + 'version' => '1.11.14', ]; $libraries['jquery.toggles'] = [ 'title' => $this->t('jQuery: Toggles'), @@ -417,24 +432,15 @@ protected function initLibraries() { 'download_url' => Url::fromUri('https://github.com/simontabor/jquery-toggles/archive/v4.0.0.zip'), 'version' => '4.0.0', 'elements' => ['webform_toggle', 'webform_toggles'], - ]; - $libraries['jquery.word-and-character-counter'] = [ - 'title' => $this->t('jQuery: Word and character counter plug-in!'), - 'description' => $this->t('The jQuery word and character counter plug-in allows you to count characters or words'), - 'notes' => $this->t('Word or character counting, with server-side validation, is available for text fields and text areas.'), - 'homepage_url' => Url::fromUri('https://github.com/qwertypants/jQuery-Word-and-Character-Counter-Plugin'), - 'download_url' => Url::fromUri('https://github.com/qwertypants/jQuery-Word-and-Character-Counter-Plugin/archive/2.5.1.zip'), - 'version' => '2.5.1', - 'optional' => TRUE, + 'deprecated' => $this->t('The Toogles library is not being maintained and has major accessibility issues. It has been <a href=":href">deprecated</a> and will be removed before Webform 8.x-6.0.', [':href' => 'https://www.drupal.org/project/webform/issues/2890861']), ]; $libraries['progress-tracker'] = [ 'title' => $this->t('Progress Tracker'), - 'description' => $this->t("A flexible SASS component to illustrate the steps in a multi step process e.g. a multi step form, a timeline or a quiz."), + 'description' => $this->t("A flexible SASS component to illustrate the steps in a multi-step process e.g. a multi-step form, a timeline or a quiz."), 'notes' => $this->t('Progress Tracker is used by multi-step wizard forms.'), 'homepage_url' => Url::fromUri('http://nigelotoole.github.io/progress-tracker/'), 'download_url' => Url::fromUri('https://github.com/NigelOToole/progress-tracker/archive/v1.4.0.zip'), 'version' => '1.4.0', - 'optional' => TRUE, ]; $libraries['signature_pad'] = [ 'title' => $this->t('Signature Pad'), @@ -446,10 +452,20 @@ protected function initLibraries() { 'elements' => ['webform_signature'], ]; + // Add webform as the provider to all libraries. + foreach ($libraries as $library_name => $library) { + $libraries[$library_name] += [ + 'optional' => TRUE, + 'provider' => 'webform', + ]; + } + // Allow other modules to define webform libraries. foreach ($this->moduleHandler->getImplementations('webform_libraries_info') as $module) { foreach ($this->moduleHandler->invoke($module, 'webform_libraries_info') as $library_name => $library) { - $libraries[$library_name] = $library; + $libraries[$library_name] = $library + [ + 'provider' => $module, + ]; } } @@ -459,6 +475,14 @@ protected function initLibraries() { // Sort libraries by key. ksort($libraries); + // Move deprecated libraries last. + foreach ($libraries as $library_name => $library) { + if (!empty($library['deprecated'])) { + unset($libraries[$library_name]); + $libraries[$library_name] = $library; + } + } + return $libraries; } diff --git a/web/modules/webform/src/WebformMessageManager.php b/web/modules/webform/src/WebformMessageManager.php index 29df9b49af..7e6b1f7874 100644 --- a/web/modules/webform/src/WebformMessageManager.php +++ b/web/modules/webform/src/WebformMessageManager.php @@ -2,6 +2,8 @@ namespace Drupal\webform; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; @@ -96,6 +98,13 @@ class WebformMessageManager implements WebformMessageManagerInterface { */ protected $webformSubmission; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a WebformMessageManager object. * @@ -104,22 +113,28 @@ class WebformMessageManager implements WebformMessageManagerInterface { * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration object factory. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity manager. + * The entity type manager. * @param \Psr\Log\LoggerInterface $logger * A logger instance. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. * @param \Drupal\webform\WebformRequestInterface $request_handler * The webform request handler. * @param \Drupal\webform\WebformTokenManagerInterface $token_manager * The webform token manager. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - public function __construct(AccountInterface $current_user, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, RendererInterface $renderer, WebformRequestInterface $request_handler, WebformTokenManagerInterface $token_manager) { + public function __construct(AccountInterface $current_user, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, RendererInterface $renderer, MessengerInterface $messenger, WebformRequestInterface $request_handler, WebformTokenManagerInterface $token_manager) { $this->currentUser = $current_user; $this->configFactory = $config_factory; $this->entityStorage = $entity_type_manager->getStorage('webform_submission'); $this->logger = $logger; $this->renderer = $renderer; + $this->messenger = $messenger; $this->requestHandler = $request_handler; $this->tokenManager = $token_manager; } @@ -171,7 +186,7 @@ public function append(array $build, $key, $type = 'status') { */ public function display($key, $type = 'status') { if ($build = $this->build($key)) { - drupal_set_message($this->renderer->renderPlain($build), $type); + $this->messenger->addMessage($this->renderer->renderPlain($build), $type); } } @@ -197,7 +212,7 @@ public function build($key) { } // Set max-age to 0 if settings message contains any [token] values. - $setting_message = $this->setting($key); + $setting_message = $this->getSetting($key); if ($setting_message && strpos($setting_message, '[') !== FALSE) { $message['#cache']['max-age'] = 0; } @@ -209,97 +224,93 @@ public function build($key) { } } - /** - * {@inheritdoc} - */ - public function setting($key) { - $webform_settings = ($this->webform) ? $this->webform->getSettings() : []; - if (!empty($webform_settings[$key])) { - return $webform_settings[$key]; - } - - $default_settings = $this->configFactory->get('webform.settings')->get('settings'); - if (!empty($default_settings['default_' . $key])) { - return $default_settings['default_' . $key]; - } - return FALSE; - } - /** * {@inheritdoc} */ public function get($key) { - // Get message from settings. - if ($setting = $this->setting($key)) { - $entity = $this->webformSubmission ?: $this->webform; - return WebformHtmlEditor::checkMarkup($this->tokenManager->replace($setting, $entity)); + // Get custom message from settings. + if ($custom_message = $this->getCustomMessage($key)) { + return $custom_message; } $webform = $this->webform; $source_entity = $this->sourceEntity; - $t_args = [ - '%form' => ($source_entity) ? $source_entity->label() : $webform->label(), - ':handlers_href' => $webform->toUrl('handlers')->toString(), - ':settings_href' => $webform->toUrl('settings')->toString(), - ':duplicate_href' => $webform->toUrl('duplicate-form')->toString(), - ]; + // Get custom message from settings with arguments. + switch ($key) { + case WebformMessageManagerInterface::PREVIOUS_SUBMISSION: + $webform_submission = $this->entityStorage->getLastSubmission($webform, $source_entity, $this->currentUser); + $args = [':href' => $this->requestHandler->getUrl($webform_submission, $source_entity, 'webform.user.submission')->toString()]; + return $this->getCustomMessage('previous_submission_message', $args); + case WebformMessageManagerInterface::PREVIOUS_SUBMISSIONS: + $args = [':href' => $this->requestHandler->getUrl($webform, $source_entity, 'webform.user.submissions')->toString()]; + return $this->getCustomMessage('previous_submissions_message', $args); + } + + // Get hard-coded messages. switch ($key) { case WebformMessageManagerInterface::ADMIN_PAGE: - return $this->t('Only webform administrators are allowed to access this page and create new submissions.', $t_args); + return $this->t('Only webform administrators are allowed to access this page and create new submissions.'); case WebformMessageManagerInterface::ADMIN_CLOSED: - return $this->t('This webform is <a href=":settings_href">closed</a>. Only submission administrators are allowed to access this webform and create new submissions.', $t_args); + $t_args = [':href' => $webform->toUrl('settings-form')->toString()]; + return $this->t('This webform is <a href=":href">closed</a>. Only submission administrators are allowed to access this webform and create new submissions.', $t_args); + + case WebformMessageManagerInterface::ADMIN_ARCHIVED: + $t_args = [':href' => $webform->toUrl('settings')->toString()]; + return $this->t('This webform is <a href=":href">archived</a>. Only submission administrators are allowed to access this webform and create new submissions.', $t_args); case WebformMessageManagerInterface::SUBMISSION_DEFAULT_CONFIRMATION: + $t_args = ['%form' => ($source_entity) ? $source_entity->label() : $webform->label()]; return $this->t('New submission added to %form.', $t_args); case WebformMessageManagerInterface::FORM_SAVE_EXCEPTION: + $t_args = [ + ':handlers_href' => $webform->toUrl('handlers')->toString(), + ':settings_href' => $webform->toUrl('settings')->toString(), + ]; return $this->t('This webform is currently not saving any submitted data. Please enable the <a href=":settings_href">saving of results</a> or add a <a href=":handlers_href">submission handler</a> to the webform.', $t_args); case WebformMessageManagerInterface::HANDLER_SUBMISSION_REQUIRED: - return $this->t('This webform\'s <a href=":handlers_href">submission handlers</a> requires submissions to be saved to the database.', $t_args); - - case WebformMessageManagerInterface::SUBMISSION_PREVIOUS: - $webform_submission = $this->entityStorage->getLastSubmission($webform, $source_entity, $this->currentUser); - $t_args[':submission_href'] = $this->requestHandler->getUrl($webform_submission, $source_entity, 'webform.user.submission')->toString(); - return $this->t('You have already submitted this webform.') . ' ' . $this->t('<a href=":submission_href">View your previous submission</a>.', $t_args); - - case WebformMessageManagerInterface::SUBMISSIONS_PREVIOUS: - $t_args[':submissions_href'] = $this->requestHandler->getUrl($webform, $source_entity, 'webform.user.submissions')->toString(); - return $this->t('You have already submitted this webform.') . ' ' . $this->t('<a href=":submissions_href">View your previous submissions</a>.', $t_args); + $t_args = [':href' => $webform->toUrl('handlers')->toString()]; + return $this->t('This webform\'s <a href=":href">submission handlers</a> requires submissions to be saved to the database.', $t_args); case WebformMessageManagerInterface::DRAFT_PREVIOUS: $webform_draft = $this->entityStorage->loadDraft($webform, $source_entity, $this->currentUser); - if ($source_entity && $source_entity->hasLinkTemplate('canonical')) { - $t_args[':draft_href'] = $source_entity->toUrl('canonical', ['query' => ['token' => $webform_draft->getToken()]])->toString(); - } - else { - $t_args[':draft_href'] = $webform->toUrl('canonical', ['query' => ['token' => $webform_draft->getToken()]])->toString(); - } - return $this->t('You have a pending draft for this webform.') . ' ' . $this->t('<a href=":draft_href">Load your pending draft</a>.', $t_args); + $t_args = [':href' => $webform_draft->getTokenUrl()->toString()]; + return $this->t('You have a pending draft for this webform.') . ' ' . $this->t('<a href=":href">Load your pending draft</a>.', $t_args); case WebformMessageManagerInterface::DRAFTS_PREVIOUS: - $t_args[':drafts_href'] = $this->requestHandler->getUrl($webform, $source_entity, 'webform.user.drafts')->toString(); - return $this->t('You have pending drafts for this webform.') . ' ' . $this->t('<a href=":drafts_href">View your pending drafts</a>.', $t_args); + $t_args = [':href' => $this->requestHandler->getUrl($webform, $source_entity, 'webform.user.drafts')->toString()]; + return $this->t('You have pending drafts for this webform.') . ' ' . $this->t('<a href=":href">View your pending drafts</a>.', $t_args); case WebformMessageManagerInterface::SUBMISSION_UPDATED: + $t_args = ['%form' => ($source_entity) ? $source_entity->label() : $webform->label()]; return $this->t('Submission updated in %form.', $t_args); case WebformMessageManagerInterface::SUBMISSION_TEST: - return $this->t("The below webform has been prepopulated with custom/random test data. When submitted, this information <strong>will still be saved</strong> and/or <strong>sent to designated recipients</strong>.", $t_args); + return $this->t("The below webform has been prepopulated with custom/random test data. When submitted, this information <strong>will still be saved</strong> and/or <strong>sent to designated recipients</strong>."); case WebformMessageManagerInterface::TEMPLATE_PREVIEW: - return $this->t('You are previewing the below template, which can be used to <a href=":duplicate_href">create a new webform</a>. <strong>Submitted data will be ignored</strong>.', $t_args); + $t_args = [':href' => $webform->toUrl('duplicate-form')->toString()]; + return $this->t('You are previewing the below template, which can be used to <a href=":href">create a new webform</a>. <strong>Submitted data will be ignored</strong>.', $t_args); case WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_TYPE: case WebformMessageManagerInterface::PREPOPULATE_SOURCE_ENTITY_REQUIRED: - return $this->t('This webform is not available. Please contact the site administrator.', $t_args); + return $this->t('This webform is not available. Please contact the site administrator.'); - default: - return FALSE; + case WebformMessageManagerInterface::PREVIOUS_SUBMISSION: + $webform_submission = $this->entityStorage->getLastSubmission($webform, $source_entity, $this->currentUser); + $args = [':href' => $this->requestHandler->getUrl($webform_submission, $source_entity, 'webform.user.submission')->toString()]; + return $this->getCustomMessage('previous_submission_message', $args); + + case WebformMessageManagerInterface::PREVIOUS_SUBMISSIONS: + $args = [':href' => $this->requestHandler->getUrl($webform, $source_entity, 'webform.user.submissions')->toString()]; + return $this->getCustomMessage('previous_submissions_message', $args); } + + return FALSE; } /** @@ -336,4 +347,56 @@ public function log($key, $type = 'warning') { $this->logger->$type($message, $context); } + /** + * Get message from webform specific setting or global setting. + * + * @param string $key + * The name of webform settings message to be displayed. + * + * @return string|bool + * A message or FALSE if no message is found. + */ + protected function getSetting($key) { + $webform_settings = ($this->webform) ? $this->webform->getSettings() : []; + if (!empty($webform_settings[$key])) { + return $webform_settings[$key]; + } + + $default_settings = $this->configFactory->get('webform.settings')->get('settings'); + if (!empty($default_settings['default_' . $key])) { + return $default_settings['default_' . $key]; + } + return FALSE; + } + + /** + * Get custom message. + * + * @param string $key + * Message key. + * @param array $arguments + * An array with placeholder replacements, keyed by placeholder. + * + * @return array|bool + * Renderable array or FALSE if custom message does not exist. + */ + protected function getCustomMessage($key, array $arguments = []) { + $setting = $this->getSetting($key); + if (!$setting) { + return FALSE; + } + + // Replace tokens. + $entity = $this->webformSubmission ?: $this->webform; + $message = $this->tokenManager->replace($setting, $entity); + + // Replace arguments. + if ($arguments) { + $message = str_replace('href="#"', 'href=":href"', $message); + $message = new FormattableMarkup($message, $arguments); + } + + return WebformHtmlEditor::checkMarkup($message); + } + } diff --git a/web/modules/webform/src/WebformMessageManagerInterface.php b/web/modules/webform/src/WebformMessageManagerInterface.php index c8657c2589..5712adc003 100644 --- a/web/modules/webform/src/WebformMessageManagerInterface.php +++ b/web/modules/webform/src/WebformMessageManagerInterface.php @@ -10,78 +10,83 @@ interface WebformMessageManagerInterface { /****************************************************************************/ - // Hardcode message constants. + // Hardcode message or custom messages with arguments constants. /****************************************************************************/ /** * Admin closed. */ - const ADMIN_CLOSED = 1; + const ADMIN_CLOSED = 'admin_closed'; /** * Admin page. */ - const ADMIN_PAGE = 2; + const ADMIN_PAGE = 'admin_page'; + + /** + * Admin archived. + */ + const ADMIN_ARCHIVED = 'admin_archived'; /** * Default submission confirmation. */ - const SUBMISSION_DEFAULT_CONFIRMATION = 3; + const SUBMISSION_DEFAULT_CONFIRMATION = 'submission_default_confirmation'; /** * Submission previous. */ - const SUBMISSION_PREVIOUS = 4; + const PREVIOUS_SUBMISSION = 'previous_submission'; /** * Submissions previous. */ - const SUBMISSIONS_PREVIOUS = 5; + const PREVIOUS_SUBMISSIONS = 'previous_submissions'; /** * Submission updates. */ - const SUBMISSION_UPDATED = 6; + const SUBMISSION_UPDATED = 'submission_updated'; /** * Submission test. */ - const SUBMISSION_TEST = 7; + const SUBMISSION_TEST = 'submission_test'; /** * Webform not saving or sending any data. */ - const FORM_SAVE_EXCEPTION = 8; + const FORM_SAVE_EXCEPTION = 'form_save_exception'; /** * Webform not able to handle file uploads. */ - const FORM_FILE_UPLOAD_EXCEPTION = 9; + const FORM_FILE_UPLOAD_EXCEPTION = 'form_file_upload_exception'; /** * Handler submission test. */ - const HANDLER_SUBMISSION_REQUIRED = 10; + const HANDLER_SUBMISSION_REQUIRED = 'handler_submission_required'; /** * Draft previous. */ - const DRAFT_PREVIOUS = 11; + const DRAFT_PREVIOUS = 'draft_previous'; /** * Drafts previous. */ - const DRAFTS_PREVIOUS = 12; + const DRAFTS_PREVIOUS = 'drafts_previous'; /****************************************************************************/ - // Configurable message constants. + // Configurable custom message constants. // Values corresponds to admin config and webform settings. /****************************************************************************/ /** * Webform exception. */ - const FORM_EXCEPTION = 'form_exception_message'; + const FORM_EXCEPTION_MESSAGE = 'form_exception_message'; /** * Webform preview. @@ -116,33 +121,38 @@ interface WebformMessageManagerInterface { /** * Submission draft saved. */ - const SUBMISSION_DRAFT_SAVED = 'draft_saved_message'; + const SUBMISSION_DRAFT_SAVED_MESSAGE = 'draft_saved_message'; /** * Submission draft loaded. */ - const SUBMISSION_DRAFT_LOADED = 'draft_loaded_message'; + const SUBMISSION_DRAFT_LOADED_MESSAGE = 'draft_loaded_message'; /** * Submission confirmation. */ - const SUBMISSION_CONFIRMATION = 'confirmation_message'; + const SUBMISSION_CONFIRMATION_MESSAGE = 'confirmation_message'; /** * Submission exception. */ - const SUBMISSION_EXCEPTION = 'submission_exception_message'; + const SUBMISSION_EXCEPTION_MESSAGE = 'submission_exception_message'; /** * Submission exception. */ - const SUBMISSION_LOCKED = 'submission_locked_message'; + const SUBMISSION_LOCKED_MESSAGE = 'submission_locked_message'; /** * Template preview. */ const TEMPLATE_PREVIEW = 'template_preview'; + /** + * Autofill. + */ + const AUTOFILL_MESSAGE = 'autofill_message'; + /** * Prepopulate source entity required. */ @@ -153,11 +163,6 @@ interface WebformMessageManagerInterface { */ const PREPOPULATE_SOURCE_ENTITY_TYPE = 'prepopulate_source_entity_type'; - /** - * Autofill. - */ - const AUTOFILL = 'autofill_message'; - /** * Set the webform submission used for token replacement. * @@ -184,17 +189,6 @@ public function setWebform(WebformInterface $webform = NULL); */ public function setSourceEntity(EntityInterface $entity = NULL); - /** - * Get message from webform specific setting or global setting. - * - * @param string $key - * The name of webform settings message to be displayed. - * - * @return string|bool - * A message or FALSE if no message is found. - */ - public function setting($key); - /** * Get message. * diff --git a/web/modules/webform/src/WebformOptionsAccessControlHandler.php b/web/modules/webform/src/WebformOptionsAccessControlHandler.php index 670399c13a..7952efe635 100644 --- a/web/modules/webform/src/WebformOptionsAccessControlHandler.php +++ b/web/modules/webform/src/WebformOptionsAccessControlHandler.php @@ -18,7 +18,7 @@ class WebformOptionsAccessControlHandler extends EntityAccessControlHandler { * {@inheritdoc} */ public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { - return AccessResult::allowedIf($account->hasPermission('administer webform'))->cachePerPermissions(); + return AccessResult::allowedIfHasPermission($account, 'administer webform'); } } diff --git a/web/modules/webform/src/WebformOptionsDeleteForm.php b/web/modules/webform/src/WebformOptionsDeleteForm.php index 5215faf5fc..e9ce67db7f 100644 --- a/web/modules/webform/src/WebformOptionsDeleteForm.php +++ b/web/modules/webform/src/WebformOptionsDeleteForm.php @@ -2,72 +2,79 @@ namespace Drupal\webform; -use Drupal\Core\Entity\EntityDeleteForm; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; -use Drupal\webform\Form\WebformDialogFormTrait; +use Drupal\webform\Form\WebformConfigEntityDeleteFormBase; /** * Provides a delete webform options form. */ -class WebformOptionsDeleteForm extends EntityDeleteForm { - - use WebformDialogFormTrait; +class WebformOptionsDeleteForm extends WebformConfigEntityDeleteFormBase { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); + public function getDescription() { + return [ + 'title' => [ + '#markup' => $this->t('This action will…'), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => [ + $this->t('Remove configuration'), + $this->t('Affect any elements which use these options'), + ], + ], + ]; + } + /** + * {@inheritdoc} + */ + public function getDetails() { /** @var \Drupal\webform\WebformOptionsInterface $webform_options */ $webform_options = $this->entity; /** @var \Drupal\webform\WebformOptionsStorageInterface $webform_options_storage */ $webform_options_storage = $this->entityTypeManager->getStorage('webform_options'); - // Display warning that options is used by composite elements - // and/or webforms. - $t_args = ['%title' => $webform_options->label()]; - $message = []; + $t_args = [ + '%label' => $this->getEntity()->label(), + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + ]; + + $details = []; if ($used_by_elements = $webform_options_storage->getUsedByCompositeElements($webform_options)) { - $message['elements'] = [ - '#theme' => 'item_list', - '#title' => $this->t('%title is used by the below composite element(s).', $t_args), - '#items' => $used_by_elements, + $details['elements'] = [ + 'title' => [ + '#markup' => $this->t('%label is used by the below composite element(s).', $t_args), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => $used_by_elements, + ], ]; } if ($used_by_webforms = $webform_options_storage->getUsedByWebforms($webform_options)) { - $message['webform'] = [ - '#theme' => 'item_list', - '#title' => $this->t('%title is used by the below webform(s).', $t_args), - '#items' => $used_by_webforms, + $details['webform'] = [ + 'title' => [ + '#markup' => $this->t('%label is used by the below webform(s).', $t_args), + ], + 'list' => [ + '#theme' => 'item_list', + '#items' => $used_by_webforms, + ], ]; } - if ($message) { - $form['used_by_composite_elements'] = [ - '#type' => 'webform_message', - '#message_message' => $message, - '#message_type' => 'warning', - '#weight' => -100, - ]; - } - - $form['confirm'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Yes, I want to delete these webform options.'), - '#required' => TRUE, - '#weight' => 10, - ]; - - return $this->buildDialogConfirmForm($form, $form_state); - } - /** - * {@inheritdoc} - */ - public function getRedirectUrl() { - return Url::fromRoute('entity.webform_options.collection'); + if ($details) { + return [ + '#type' => 'details', + '#title' => $this->t('Webforms affected'), + ] + $details; + } + else { + return []; + } } } diff --git a/web/modules/webform/src/WebformOptionsForm.php b/web/modules/webform/src/WebformOptionsForm.php index f93a6a2aae..66db47b221 100644 --- a/web/modules/webform/src/WebformOptionsForm.php +++ b/web/modules/webform/src/WebformOptionsForm.php @@ -8,6 +8,7 @@ use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\webform\Entity\WebformOptions; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\Utility\WebformOptionsHelper; /** @@ -30,7 +31,7 @@ protected function prepareEntity() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - /** @var \Drupal\webform\WebformOptionsInterface $webform */ + /** @var \Drupal\webform\WebformOptionsInterface $webform_options */ $webform_options = $this->getEntity(); // Customize title for duplicate and edit operation. @@ -40,6 +41,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { break; case 'edit': + case 'source': $form['#title'] = $webform_options->label(); break; } @@ -102,10 +104,10 @@ public function form(array $form, FormStateInterface $form_state) { '@module' => new PluralTranslatableMarkup(count($module_names), $this->t('module'), $this->t('modules')), ]; if (empty($webform_options->get('options'))) { - drupal_set_message($this->t('The %title options are being set by the %module_names @module. Altering any of the below options will override these dynamically populated options.', $t_args), 'warning'); + $this->messenger()->addWarning($this->t('The %title options are being set by the %module_names @module. Altering any of the below options will override these dynamically populated options.', $t_args)); } else { - drupal_set_message($this->t('The %title options have been customized. Resetting the below options will allow the %module_names @module to dynamically populate these options.', $t_args), 'warning'); + $this->messenger()->addWarning($this->t('The %title options have been customized. Resetting the below options will allow the %module_names @module to dynamically populate these options.', $t_args)); } } @@ -134,6 +136,12 @@ protected function actions(array $form, FormStateInterface $form_state) { ]; } + // Open delete button in a modal dialog. + if (isset($actions['delete'])) { + $actions['delete']['#attributes'] = WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW, $actions['delete']['#attributes']['class']); + WebformDialogHelper::attachLibraries($actions['delete']); + } + return $actions; } @@ -225,7 +233,7 @@ public function reset(array &$form, FormStateInterface $form_state) { ]; $this->logger('webform')->notice('Options @label have been reset.', $context); - drupal_set_message($this->t('Options %label have been reset.', ['%label' => $webform_options->label()])); + $this->messenger()->addStatus($this->t('Options %label have been reset.', ['%label' => $webform_options->label()])); $form_state->setRedirect('entity.webform_options.collection'); } @@ -244,7 +252,7 @@ public function save(array $form, FormStateInterface $form_state) { ]; $this->logger('webform')->notice('Options @label saved.', $context); - drupal_set_message($this->t('Options %label saved.', ['%label' => $webform_options->label()])); + $this->messenger()->addStatus($this->t('Options %label saved.', ['%label' => $webform_options->label()])); $form_state->setRedirect('entity.webform_options.collection'); } diff --git a/web/modules/webform/src/WebformOptionsListBuilder.php b/web/modules/webform/src/WebformOptionsListBuilder.php index c10a9c90dc..6f32a5cb01 100644 --- a/web/modules/webform/src/WebformOptionsListBuilder.php +++ b/web/modules/webform/src/WebformOptionsListBuilder.php @@ -4,10 +4,15 @@ use Drupal\Core\Config\Entity\ConfigEntityListBuilder; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\OptGroup; use Drupal\Core\Url; use Drupal\webform\Entity\WebformOptions; use Drupal\webform\Utility\WebformDialogHelper; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\RequestStack; /** * Defines a class to build a listing of webform options entities. @@ -16,28 +21,108 @@ */ class WebformOptionsListBuilder extends ConfigEntityListBuilder { + /** + * Search keys. + * + * @var string + */ + protected $keys; + + /** + * Search category. + * + * @var string + */ + protected $category; + + /** + * Constructs a new WebformOptionsListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, RequestStack $request_stack) { + parent::__construct($entity_type, $storage); + $this->request = $request_stack->getCurrentRequest(); + + $this->keys = $this->request->query->get('search'); + $this->category = $this->request->query->get('category'); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('request_stack') + ); + } + /** * {@inheritdoc} */ public function render() { + // Handler autocomplete redirect. + if ($this->keys && preg_match('#\(([^)]+)\)$#', $this->keys, $match)) { + if ($webform_options = $this->getStorage()->load($match[1])) { + return new RedirectResponse($webform_options->toUrl()->setAbsolute(TRUE)->toString()); + } + } + $build = []; + // Filter form. + $build['filter_form'] = $this->buildFilterForm(); + // Display info. - if ($total = $this->getStorage()->getQuery()->count()->execute()) { - $build['info'] = [ - '#markup' => $this->formatPlural($total, '@total option', '@total options', ['@total' => $total]), - '#prefix' => '<div>', - '#suffix' => '</div>', - ]; - } + $build['info'] = $this->buildInfo(); + // Table. $build += parent::render(); + $build['table']['#sticky'] = TRUE; + // Attachments. $build['#attached']['library'][] = 'webform/webform.admin.dialog'; return $build; } + /** + * Build the filter form. + * + * @return array + * A render array representing the filter form. + */ + protected function buildFilterForm() { + $categories = $this->getStorage()->getCategories(); + return \Drupal::formBuilder()->getForm('\Drupal\webform\Form\WebformOptionsFilterForm', $this->keys, $this->category, $categories); + } + + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { + $total = $this->getQuery($this->keys, $this->category)->count()->execute(); + if (!$total) { + return []; + } + + return [ + '#markup' => $this->formatPlural($total, '@total option', '@total options', ['@total' => $total]), + '#prefix' => '<div>', + '#suffix' => '</div>', + ]; + } + /** * {@inheritdoc} */ @@ -96,7 +181,7 @@ public function getDefaultOperations(EntityInterface $entity, $type = 'edit') { * Build list of webforms and composite elements that the webform options is used by. * * @param \Drupal\webform\WebformOptionsInterface $webform_options - * A webform options entity + * A webform options entity. * * @return array * Table data containing list of webforms and composite elements that the @@ -110,14 +195,14 @@ protected function buildUsedBy(WebformOptionsInterface $webform_options) { '#type' => 'link', '#title' => $title, '#url' => Url::fromRoute('entity.webform.canonical', ['webform' => $id]), - '#suffix' => '</br>' + '#suffix' => '</br>', ]; } $elements = $this->getStorage()->getUsedByCompositeElements($webform_options); foreach ($elements as $id => $title) { $links[] = [ '#markup' => $title, - '#suffix' => '</br>' + '#suffix' => '</br>', ]; } return [ @@ -130,7 +215,7 @@ protected function buildUsedBy(WebformOptionsInterface $webform_options) { * Build list of webform options. * * @param \Drupal\webform\WebformOptionsInterface $webform_options - * A webform options entity + * A webform options entity. * * @return string * Semi-colon delimited list of webform options. @@ -144,7 +229,59 @@ protected function buildOptions(WebformOptionsInterface $webform_options) { $value .= ' (' . $key . ')'; } } - return implode('; ', array_slice($options, 0, 12)) . (count($options) > 12 ? '; ...' : ''); + return implode('; ', array_slice($options, 0, 12)) . (count($options) > 12 ? '; …' : ''); + } + + /** + * {@inheritdoc} + */ + public function buildOperations(EntityInterface $entity) { + return parent::buildOperations($entity) + [ + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; + } + + /** + * Get the base entity query filtered by search and category. + * + * @param string $keys + * (optional) Search key. + * @param string $category + * (optional) Category. + * + * @return \Drupal\Core\Entity\Query\QueryInterface + * An entity query. + */ + protected function getQuery($keys = '', $category = '') { + $query = $this->getStorage()->getQuery(); + + // Filter by key(word). + if ($keys) { + $or = $query->orConditionGroup() + ->condition('id', $keys, 'CONTAINS') + ->condition('title', $keys, 'CONTAINS') + ->condition('options', $keys, 'CONTAINS'); + $query->condition($or); + } + + // Filter by category. + if ($category) { + $query->condition('category', $category); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + protected function getEntityIds() { + $header = $this->buildHeader(); + $query = $this->getQuery($this->keys, $this->category); + $query->tableSort($header); + $query->pager($this->limit); + return $query->execute(); } } diff --git a/web/modules/webform/src/WebformOptionsStorage.php b/web/modules/webform/src/WebformOptionsStorage.php index 3ed0f9ad41..ea5066c3b2 100644 --- a/web/modules/webform/src/WebformOptionsStorage.php +++ b/web/modules/webform/src/WebformOptionsStorage.php @@ -3,13 +3,13 @@ namespace Drupal\webform; use Drupal\Component\Render\FormattableMarkup; -use Drupal\Component\Serialization\Yaml; use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\Entity\ConfigEntityStorage; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Render\ElementInfoManagerInterface; +use Drupal\Core\Serialization\Yaml; use Drupal\webform\Element\WebformCompositeBase; use Drupal\webform\Plugin\WebformElementManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -144,7 +144,9 @@ public function getUsedByCompositeElements(WebformOptionsInterface $webform_opti foreach (array_keys($definitions) as $plugin_id) { /** @var \Drupal\Core\Render\Element\ElementInterface $element */ $element = $this->elementInfo->createInstance($plugin_id); - if (!$element instanceof WebformCompositeBase) { + // Make sure element is composite and not provided by the + // webform_composite.module. + if (!$element instanceof WebformCompositeBase || in_array($plugin_id, ['webform_composite'])) { continue; } @@ -191,8 +193,8 @@ public function getUsedByWebforms(WebformOptionsInterface $webform_options) { $config = $this->configFactory->get($webform_config_name); $element_data = Yaml::encode($config->get('elements')); if (preg_match_all('/(?:options|answers)\'\: ([a-z_]+)/', $element_data, $matches)) { - $webform_id = $config->get('id'); - $webform_title = $config->get('title'); + $webform_id = $config->get('id'); + $webform_title = $config->get('title'); foreach ($matches[1] as $options_id) { $this->usedByWebforms[$options_id][$webform_id] = $webform_title; } diff --git a/web/modules/webform/src/WebformRequest.php b/web/modules/webform/src/WebformRequest.php index 3261a35d25..a848bfa150 100644 --- a/web/modules/webform/src/WebformRequest.php +++ b/web/modules/webform/src/WebformRequest.php @@ -93,7 +93,7 @@ class WebformRequest implements WebformRequestInterface { * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The current route match. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type repository. + * The entity type manager. * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository * The entity type repository. * @param \Drupal\webform\WebformEntityReferenceManagerInterface $webform_entity_reference_manager @@ -172,6 +172,24 @@ public function getCurrentWebformSubmission() { return $webform_submission; } + /** + * {@inheritdoc} + */ + public function getCurrentWebformUrl($route_name, array $route_options = []) { + $webform_entity = $this->getCurrentWebform(); + $source_entity = $this->getCurrentSourceEntity(); + return $this->getUrl($webform_entity, $source_entity, $route_name, $route_options); + } + + /** + * {@inheritdoc} + */ + public function getCurrentWebformSubmissionUrl($route_name, array $route_options = []) { + $webform_entity = $this->getCurrentWebformSubmission(); + $source_entity = $this->getCurrentSourceEntity(); + return $this->getUrl($webform_entity, $source_entity, $route_name, $route_options); + } + /** * {@inheritdoc} */ diff --git a/web/modules/webform/src/WebformRequestInterface.php b/web/modules/webform/src/WebformRequestInterface.php index eea30d5de6..2f2f94c640 100644 --- a/web/modules/webform/src/WebformRequestInterface.php +++ b/web/modules/webform/src/WebformRequestInterface.php @@ -44,6 +44,32 @@ public function getCurrentWebform(); */ public function getCurrentWebformSubmission(); + /** + * Get the URL for the current webform and source entity. + * + * @param string $route_name + * The route name. + * @param array $route_options + * The route options. + * + * @return \Drupal\Core\Url + * The URL for a form/submission and source entity. + */ + public function getCurrentWebformUrl($route_name, array $route_options = []); + + /** + * Get the URL for the current webform submission and source entity. + * + * @param string $route_name + * The route name. + * @param array $route_options + * The route options. + * + * @return \Drupal\Core\Url + * The URL for a form/submission and source entity. + */ + public function getCurrentWebformSubmissionUrl($route_name, array $route_options = []); + /** * Get the webform and source entity for the current request. * diff --git a/web/modules/webform/src/WebformSubmissionAccessControlHandler.php b/web/modules/webform/src/WebformSubmissionAccessControlHandler.php index 9c736fe3b5..defea601a2 100644 --- a/web/modules/webform/src/WebformSubmissionAccessControlHandler.php +++ b/web/modules/webform/src/WebformSubmissionAccessControlHandler.php @@ -2,17 +2,51 @@ namespace Drupal\webform; -use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\webform\Access\WebformAccessResult; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines the access control handler for the webform submission entity type. * * @see \Drupal\webform\Entity\WebformSubmission. */ -class WebformSubmissionAccessControlHandler extends EntityAccessControlHandler { +class WebformSubmissionAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface { + + /** + * Webform access rules manager service. + * + * @var \Drupal\webform\WebformAccessRulesManagerInterface + */ + protected $accessRulesManager; + + /** + * WebformSubmissionAccessControlHandler constructor. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager + * Webform access rules manager service. + */ + public function __construct(EntityTypeInterface $entity_type, WebformAccessRulesManagerInterface $access_rules_manager) { + parent::__construct($entity_type); + + $this->accessRulesManager = $access_rules_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('webform.access_rules_manager') + ); + } /** * {@inheritdoc} @@ -20,46 +54,41 @@ class WebformSubmissionAccessControlHandler extends EntityAccessControlHandler { public function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { /** @var \Drupal\webform\WebformSubmissionInterface $entity */ - // Check webform submission access permissions. - // @todo: Refactor and consolidate below code after there are tests. - switch ($operation) { - case 'view': - // Allow users with 'view any webform submission' to view all submissions. - if ($account->hasPermission('view any webform submission')) { - return AccessResult::allowed(); - } + // Check 'administer webform' permission. + if ($account->hasPermission('administer webform')) { + return WebformAccessResult::allowed(); + } - // Allow users with 'view own webform submission' to view own submission. - if ($account->hasPermission('view own webform submission') && $entity->getOwnerId() == $account->id()) { - return AccessResult::allowed(); - } - break; + // Check 'administer webform submission' permission. + if ($account->hasPermission('administer webform submission')) { + return WebformAccessResult::allowed(); + } - case 'update': - // Allow users with 'edit any webform submission' to edit all submissions. - if ($account->hasPermission('edit any webform submission')) { - return AccessResult::allowed(); - } - // Allow users with 'edit own webform submission' to edit own submission. - if ($account->hasPermission('edit own webform submission') && $entity->getOwnerId() == $account->id()) { - return AccessResult::allowed(); - } - break; + // Check webform 'update' permission. + if ($entity->getWebform()->access('update', $account)) { + return WebformAccessResult::allowed($entity, TRUE); + } - case 'delete': - // Allow users with 'delete any webform submission' to edit all submissions. - if ($account->hasPermission('delete any webform submission')) { - return AccessResult::allowed(); - } - // Allow users with 'delete own webform submission' to edit own submission. - if ($account->hasPermission('delete own webform submission') && $entity->getOwnerId() == $account->id()) { - return AccessResult::allowed(); - } - break; + // Check 'any' or 'own' webform submission permissions. + $operations = [ + 'view' => 'view', + 'update' => 'edit', + 'delete' => 'delete', + ]; + if (isset($operations[$operation])) { + $action = $operations[$operation]; + // Check operation any. + if ($account->hasPermission("$action any webform submission")) { + return WebformAccessResult::allowed(); + } + // Check operation own. + if ($account->hasPermission("$action own webform submission") && $entity->isOwner($account)) { + return WebformAccessResult::allowed($entity, TRUE); + } } - // Check webform update access. - $webform_access = $entity->getWebform()->checkAccessRules($operation, $account, $entity); + // Check webform access rules. + $webform_access = $this->accessRulesManager->checkWebformSubmissionAccess($operation, $account, $entity); if ($webform_access->isAllowed()) { return $webform_access; } diff --git a/web/modules/webform/src/WebformSubmissionConditionsValidator.php b/web/modules/webform/src/WebformSubmissionConditionsValidator.php index 71a2bb70c3..b75a82af1c 100644 --- a/web/modules/webform/src/WebformSubmissionConditionsValidator.php +++ b/web/modules/webform/src/WebformSubmissionConditionsValidator.php @@ -3,8 +3,9 @@ namespace Drupal\webform; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Render\Element; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; +use Drupal\webform\Plugin\WebformElement\WebformElement; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformElementHelper; @@ -38,9 +39,6 @@ class WebformSubmissionConditionsValidator implements WebformSubmissionCondition 'open' => '!collapsed', 'closed' => 'collapsed', 'readwrite' => '!readonly', - // Below states are never used by the #states API. - // 'untouched' => '!touched', - // 'irrelevant' => '!relevant', ]; /** @@ -61,24 +59,24 @@ public function __construct(WebformElementManagerInterface $element_manager) { } /****************************************************************************/ - // Webform submission form methods. + // Build form methods. /****************************************************************************/ /** * {@inheritdoc} - * - * @see \Drupal\webform\WebformSubmissionForm::buildForm */ public function buildForm(array &$form, FormStateInterface $form_state) { /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $form_state->getFormObject()->getEntity(); - // Get build and get visible form elements. - $elements = &$this->getBuildElements($form); + // Get build/visible form elements. + $visible_elements = &$this->getBuildElements($form); // Loop through visible elements with #states. - foreach ($elements as &$element) { - $states = WebformElementHelper::getStates($element); + foreach ($visible_elements as &$element) { + $states =& WebformElementHelper::getStates($element); + // Store original #states in #_webform_states. + $element['#_webform_states'] = $states; foreach ($states as $original_state => $conditions) { if (!is_array($conditions)) { continue; @@ -87,18 +85,36 @@ public function buildForm(array &$form, FormStateInterface $form_state) { // Process state/negate. list($state, $negate) = $this->processState($original_state); - // @todo Track an element's states. // If hide/show we need to make sure that validation is not triggered. if (strpos($state, 'visible') === 0) { $element['#after_build'][] = [get_class($this), 'elementAfterBuild']; } - // Skip if conditions targets are visible. - if ($this->isConditionsTargetsVisible($conditions, $elements)) { + $targets = $this->getConditionTargetsVisiblity($conditions, $visible_elements); + + // Determine if targets are visible or cross page. + $all_targets_visible = (array_sum($targets) === count($targets)); + $has_cross_page_targets = (!$all_targets_visible && array_sum($targets)); + + // Skip if evaluating conditions when all targets are visible. + if ($all_targets_visible) { + continue; + } + + // Replace hidden cross page targets with hidden inputs. + if ($has_cross_page_targets) { + $cross_page_targets = array_filter( + $targets, + function ($visible) { + return $visible === FALSE; + } + ); + $states[$original_state] = $this->replaceCrossPageTargets($conditions, $webform_submission, $cross_page_targets, $form); continue; } $result = $this->validateConditions($conditions, $webform_submission); + // Skip invalid conditions. if ($result === NULL) { continue; @@ -142,77 +158,87 @@ public function buildForm(array &$form, FormStateInterface $form_state) { } } - /****************************************************************************/ - // Element hide/show validation methods. - /****************************************************************************/ - /** - * Webform element #after_build callback: Wrap #element_validate so that we suppress element validation errors. + * Replace hidden cross page targets with hidden inputs. + * + * @param array $conditions + * An element's conditions. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $targets + * An array of hidden target selectors. + * @param array $form + * A form. + * + * @return array + * The conditions with cross page targets replaced with hidden inputs. */ - public static function elementAfterBuild(array $element, FormStateInterface $form_state) { - return WebformElementHelper::setElementValidate($element, [get_called_class(), 'elementValidate']); - } + public function replaceCrossPageTargets(array $conditions, WebformSubmissionInterface $webform_submission, array $targets, array &$form) { + // Cache random cross page values. + static $cross_page_values = []; - /** - * Webform conditional #element_validate callback: Execute #element_validate and suppress errors. - */ - public static function elementValidate(array &$element, FormStateInterface $form_state) { - // Element validation is trigger sequentially. - // Triggers must be validated before dependants. - // - // Build webform submission with validated and processed data. - // Webform submission must be rebuilt every time since the - // $element and $form_state values can be changed by validation callbacks. - /** @var \Drupal\webform\WebformSubmissionForm $form_object */ - $form_object = $form_state->getFormObject(); - $complete_form = &$form_state->getCompleteForm(); - $webform_submission = $form_object->buildEntity($complete_form, $form_state); + $cross_page_conditions = []; + foreach ($conditions as $index => $value) { + if (is_int($index) && is_array($value) && WebformArrayHelper::isSequential($value)) { + $cross_page_conditions[$index] = $this->replaceCrossPageTargets($conditions, $webform_submission, $targets, $form); + } + else { + $cross_page_conditions[$index] = $value; - /** @var \Drupal\webform\WebformSubmissionConditionsValidatorInterface $conditions_validator */ - $conditions_validator = \Drupal::service('webform_submission.conditions_validator'); - if ($conditions_validator->isElementVisible($element, $webform_submission)) { - WebformElementHelper::triggerElementValidate($element, $form_state); - } - else { - WebformElementHelper::suppressElementValidate($element, $form_state); - } - } + if (is_string($value) && in_array($value, ['and', 'or', 'xor'])) { + continue; + } + elseif (is_int($index)) { + $selector = key($value); + $condition = $value[$selector]; + } + else { + $selector = $index; + $condition = $value; + } - /** - * {@inheritdoc} - */ - public function isElementVisible(array $element, WebformSubmissionInterface $webform_submission) { - $states = WebformElementHelper::getStates($element); + if (!isset($targets[$selector])) { + continue; + } - $visible = TRUE; - foreach ($states as $state => $conditions) { - if (!is_array($conditions)) { - continue; - } + $condition_result = $this->validateCondition($selector, $condition, $webform_submission); + if ($condition_result === NULL) { + continue; + } - // Process state/negate. - list($state, $negate) = $this->processState($state); + $target_trigger = $condition_result ? 'value' : '!value'; + $target_name = 'webform_states_' . md5($selector); + $target_selector = ':input[name="' . $target_name . '"]'; - $result = $this->validateConditions($conditions, $webform_submission); - // Skip invalid conditions. - if ($result === NULL) { - continue; - } + // IMPORTANT: + // Using a random value to make sure users can't determine a hidden + // or computed element's value/result. + if (!isset($cross_page_values[$target_name])) { + $cross_page_values[$target_name] = rand(); + } + $target_value = $cross_page_values[$target_name]; - // Negate the result. - $result = ($negate) ? !$result : $result; + if (is_int($index)) { + unset($cross_page_conditions[$index][$selector]); + $cross_page_conditions[$index][$target_selector] = [$target_trigger => $target_value]; + } + else { + unset($cross_page_conditions[$selector]); + $cross_page_conditions[$target_selector] = [$target_trigger => $target_value]; + } - // Apply result to element state. - if (strpos($state, 'visible') === 0 && $result === FALSE) { - $visible = FALSE; + // Append cross page element's result as a hidden input. + $form[$target_name] = [ + '#type' => 'hidden', + '#value' => $target_value, + ]; } } - - return $visible; + return $cross_page_conditions; } /****************************************************************************/ - // Validation methods. + // Validate form methods. /****************************************************************************/ /** @@ -234,7 +260,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ protected function validateFormRecursive(array $form, FormStateInterface $form_state) { foreach ($form as $key => $element) { - if (!Element::child($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -296,6 +322,140 @@ protected function validateFormElement(array $element, FormStateInterface $form_ } } + /****************************************************************************/ + // Submit form methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $form_state->getFormObject()->getEntity(); + + // Get submission data. + $data = $webform_submission->getData(); + + // Recursive through the form and unset unset submission data for + // form elements that are hidden. + $this->submitFormRecursive($form, $webform_submission, $data); + + // Set submission data. + $webform_submission->setData($data); + } + + /** + * Recursively unset submission data for form elements that are hidden. + * + * @param array $elements + * An array of form elements. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $data + * A webform submission's data. + * @param bool $visible + * Flag that determine if the currrent form elements are visible. + */ + protected function submitFormRecursive(array $elements, WebformSubmissionInterface $webform_submission, array &$data, $visible = TRUE) { + foreach ($elements as $key => &$element) { + if (!WebformElementHelper::isElement($element, $key)) { + continue; + } + + // Skip if element's #states_clear is FALSE. + if (isset($element['#states_clear']) && $element['#states_clear'] === FALSE) { + continue; + } + + if (isset($element['#_webform_access']) && $element['#_webform_access'] === FALSE) { + continue; + } + + // Determine if the element is visible. + $element_visible = ($visible && $this->isElementVisible($element, $webform_submission)) ? TRUE : FALSE; + + // Set data to empty array or string for any webform element that is hidden. + if (!$element_visible && !empty($element['#webform_key']) && isset($data[$key])) { + $data[$key] = (is_array($data[$key])) ? [] : ''; + } + + $this->submitFormRecursive($element, $webform_submission, $data, $element_visible); + } + } + + /****************************************************************************/ + // Element hide/show validation methods. + /****************************************************************************/ + + /** + * Webform element #after_build callback: Wrap #element_validate so that we suppress element validation errors. + */ + public static function elementAfterBuild(array $element, FormStateInterface $form_state) { + return WebformElementHelper::setElementValidate($element, [get_called_class(), 'elementValidate']); + } + + /** + * Webform conditional #element_validate callback: Execute #element_validate and suppress errors. + */ + public static function elementValidate(array &$element, FormStateInterface $form_state) { + // Element validation is trigger sequentially. + // Triggers must be validated before dependants. + // + // Build webform submission with validated and processed data. + // Webform submission must be rebuilt every time since the + // $element and $form_state values can be changed by validation callbacks. + /** @var \Drupal\webform\WebformSubmissionForm $form_object */ + $form_object = $form_state->getFormObject(); + $complete_form = &$form_state->getCompleteForm(); + $webform_submission = $form_object->buildEntity($complete_form, $form_state); + + /** @var \Drupal\webform\WebformSubmissionConditionsValidatorInterface $conditions_validator */ + $conditions_validator = \Drupal::service('webform_submission.conditions_validator'); + if ($conditions_validator->isElementVisible($element, $webform_submission)) { + WebformElementHelper::triggerElementValidate($element, $form_state); + } + else { + WebformElementHelper::suppressElementValidate($element, $form_state); + } + } + + /****************************************************************************/ + // Element state methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function isElementVisible(array $element, WebformSubmissionInterface $webform_submission) { + $states = WebformElementHelper::getStates($element); + + $visible = TRUE; + foreach ($states as $state => $conditions) { + if (!is_array($conditions)) { + continue; + } + + // Process state/negate. + list($state, $negate) = $this->processState($state); + + $result = $this->validateConditions($conditions, $webform_submission); + // Skip invalid conditions. + if ($result === NULL) { + continue; + } + + // Negate the result. + $result = ($negate) ? !$result : $result; + + // Apply result to element state. + if (strpos($state, 'visible') === 0 && $result === FALSE) { + $visible = FALSE; + } + } + + return $visible; + } + /****************************************************************************/ // Validate state methods. /****************************************************************************/ @@ -327,54 +487,48 @@ public function validateState($state, array $conditions, WebformSubmissionInterf * {@inheritdoc} */ public function validateConditions(array $conditions, WebformSubmissionInterface $webform_submission) { - $condition_logic = 'and'; + // Determine condition logic. + // @see Drupal.states.Dependent.verifyConstraints + if (WebformArrayHelper::isSequential($conditions)) { + $condition_logic = (in_array('xor', $conditions)) ? 'xor' : 'or'; + } + else { + $condition_logic = 'and'; + } + $condition_results = []; foreach ($conditions as $index => $value) { + // Skip and, or, and xor. if (is_string($value) && in_array($value, ['and', 'or', 'xor'])) { - $condition_logic = $value; - // If OR conditional logic operatator, check current condition - // results. - if ($condition_logic === 'or' && array_sum($condition_results)) { - return TRUE; - } continue; } - elseif (is_int($index)) { - $selector = key($value); - $condition = $value[$selector]; - } - else { - $selector = $index; - $condition = $value; - } - - // Ignore invalid selector and return NULL. - $input_name = static::getSelectorInputName($selector); - if (!$input_name) { - return NULL; - } - - $element_key = static::getInputNameAsArray($input_name, 0); - - // Ignore missing dependee element and return NULL. - $element = $webform_submission->getWebform()->getElement($element_key); - if (!$element) { - return NULL; - } - // Issue #1149078: States API doesn't work with multiple select fields. - // @see https://www.drupal.org/project/drupal/issues/1149078 - if (WebformArrayHelper::isSequential($condition)) { - $sub_condition_results = []; - foreach ($condition as $sub_condition) { - $sub_condition_results[] = $this->checkCondition($element, $selector, $sub_condition, $webform_submission); + if (is_int($index) && is_array($value)) { + // Validate nested conditions. + // NOTE: Nested conditions is not supported via the UI. + $nested_result = $this->validateConditions($value, $webform_submission); + if ($nested_result === NULL) { + return NULL; } - // Evalute sub-conditions using the 'OR' operator. - $condition_results[$selector] = (boolean) array_sum($sub_condition_results); + $condition_results[] = $nested_result; } else { - $condition_results[$selector] = $this->checkCondition($element, $selector, $condition, $webform_submission); + if (is_int($index)) { + $selector = key($value); + $condition = $value[$selector]; + } + else { + $selector = $index; + $condition = $value; + } + + $condition_result = $this->validateCondition($selector, $condition, $webform_submission); + if ($condition_result === NULL) { + return NULL; + } + + $condition_results[] = $this->validateCondition($selector, $condition, $webform_submission); } } @@ -397,6 +551,59 @@ public function validateConditions(array $conditions, WebformSubmissionInterface } } + /** + * Validate #state condition. + * + * @param string $selector + * The #state condition selector. + * @param array $condition + * A condition. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return bool|null + * TRUE if the condition validates. NULL if condition can't be processed. + * NULL is returned when there is invalid selector and missing element + * in the conditions. + */ + protected function validateCondition($selector, array $condition, WebformSubmissionInterface $webform_submission) { + // Ignore invalid selector and return NULL. + $input_name = static::getSelectorInputName($selector); + if (!$input_name) { + return NULL; + } + + $element_key = static::getInputNameAsArray($input_name, 0); + $element = $webform_submission->getWebform()->getElement($element_key); + + // If no element is found try checking file uploads which use + // :input[name="files[ELEMENT_KEY]. + // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::getElementSelectorOptions + if (!$element && strpos($selector, ':input[name="files[') === 0) { + $element_key = static::getInputNameAsArray($input_name, 1); + $element = $webform_submission->getWebform()->getElement($element_key); + } + + // Ignore missing dependee element and return NULL. + if (!$element) { + return NULL; + } + + // Issue #1149078: States API doesn't work with multiple select fields. + // @see https://www.drupal.org/project/drupal/issues/1149078 + if (WebformArrayHelper::isSequential($condition)) { + $sub_condition_results = []; + foreach ($condition as $sub_condition) { + $sub_condition_results[] = $this->checkCondition($element, $selector, $sub_condition, $webform_submission); + } + // Evaluate sub-conditions using the 'OR' operator. + return (boolean) array_sum($sub_condition_results); + } + else { + return $this->checkCondition($element, $selector, $condition, $webform_submission); + } + } + /** * Check a condition. * @@ -417,6 +624,12 @@ protected function checkCondition(array $element, $selector, array $condition, W $trigger_value = $condition[$trigger_state]; $element_plugin = $this->elementManager->getElementInstance($element); + + // Ignored conditions for generic webform elements. + if ($element_plugin instanceof WebformElement) { + return TRUE; + } + $element_value = $element_plugin->getElementSelectorInputValue($selector, $trigger_state, $element, $webform_submission); // Process trigger sub state used for custom #states API validation. @@ -437,7 +650,8 @@ protected function checkCondition(array $element, $selector, array $condition, W // @see \Drupal\webform\Element\WebformElementStates::processWebformStates switch ($trigger_state) { case 'empty': - $result = (empty($element_value) === (boolean) $trigger_value); + $empty = (empty($element_value) && $element_value !== '0'); + $result = ($empty === (boolean) $trigger_value); break; case 'checked': @@ -456,16 +670,19 @@ protected function checkCondition(array $element, $selector, array $condition, W break; case 'pattern': - // @see \Drupal\Core\Render\Element\FormElement::validatePattern - $result = preg_match('{' . $trigger_value . '}', $element_value); + // PHP: Convert JavaScript-escaped Unicode characters to PCRE + // escape sequence format. + // @see \Drupal\webform\Plugin\WebformElement\TextBase::validatePattern + $pcre_pattern = preg_replace('/\\\\u([a-fA-F0-9]{4})/', '\\x{\\1}', $trigger_value); + $result = preg_match('{' . $pcre_pattern . '}u', $element_value); break; case 'less': - $result = ($element_value !== '' && $trigger_value > $element_value); + $result = ($element_value !== '' && floatval($trigger_value) > floatval($element_value)); break; case 'greater': - $result = ($element_value !== '' && $trigger_value < $element_value); + $result = ($element_value !== '' && floatval($trigger_value) < floatval($element_value)); break; default: @@ -536,7 +753,7 @@ protected function &getBuildElements(array &$form) { */ protected function getBuildElementsRecusive(array &$elements, array &$form, array $parent_states = []) { foreach ($form as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -596,6 +813,13 @@ protected function getBuildElementsRecusive(array &$elements, array &$form, arra } } + // Store original #access in #_webform_access for all elements. + // @see \Drupal\webform\WebformSubmissionConditionsValidator::submitFormRecursive + if (isset($element['#access'])) { + $element['#_webform_access'] = $element['#access']; + } + + // Skip if element is not visible. if (isset($element['#access']) && $element['#access'] === FALSE) { continue; } @@ -603,23 +827,94 @@ protected function getBuildElementsRecusive(array &$elements, array &$form, arra $elements[$key] = &$element; $this->getBuildElementsRecusive($elements, $element, $subelement_states); + + $element_plugin = $this->elementManager->getElementInstance($element); + if ($element_plugin instanceof WebformCompositeBase && !$element_plugin->hasMultipleValues($element)) { + // Handle composite with single item. + if ($subelement_states) { + $composite_elements = $element_plugin->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + // Skip if #access is set to FALSE. + if (isset($element['#' . $composite_key . '__access']) && $element['#' . $composite_key . '__access'] === FALSE) { + continue; + } + // Move #composite__required to #composite___required which triggers + // conditional #_required handling. + if (!empty($element['#' . $composite_key . '__required'])) { + unset($element['#' . $composite_key . '__required']); + $element['#' . $composite_key . '___required'] = TRUE; + $element['#' . $composite_key . '__states'] = $subelement_states; + } + } + } + } + elseif (isset($element['#element']) && isset($element['#webform_composite_elements'])) { + // Handle composite with multiple items and custom composite elements. + // + // For $element['#elements'] ... + // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::prepareMultipleWrapper + // + // For $element['#webform_composite_elements'] ... + // @see \Drupal\webform\Plugin\WebformElement\WebformCompositeBase::initializeCompositeElements + // @see \Drupal\webform_composite\Plugin\WebformElement\WebformComposite::initializeCompositeElements + // + // Recurse through a composite's sub elements. + $this->getBuildElementsRecusive($elements, $element['#element'], $subelement_states); + } } } /** - * Determine if #states conditions targets are visible. + * Get the visibility state for all of conditions targets. * * @param array $conditions * An associative array containing conditions. * @param array $elements * An associative array of visible elements. * - * @return bool - * TRUE if ALL #states conditions targets are visible. + * @return array + * An associative array keyed by target selectors with a boolean state. */ - protected function isConditionsTargetsVisible(array $conditions, array $elements) { + protected function getConditionTargetsVisiblity(array $conditions, array $elements) { + $targets = []; + $this->getConditionTargetsVisiblityRecursive($conditions, $targets); + foreach ($targets as $selector) { + // Ignore invalid selector and return FALSE. + $input_name = static::getSelectorInputName($selector); + if (!$input_name) { + $targets[$selector] = FALSE; + continue; + } + + // Check if the input's element is visible. + $element_key = static::getInputNameAsArray($input_name, 0); + if (!isset($elements[$element_key])) { + $targets[$selector] = FALSE; + continue; + } + + $targets[$selector] = TRUE; + } + return $targets; + } + + /** + * Recursively collect a conditions targets. + * + * @param array $conditions + * An associative array containing conditions. + * @param array $targets + * An associative array keyed by target selectors with a boolean state. + */ + protected function getConditionTargetsVisiblityRecursive(array $conditions, array &$targets = []) { foreach ($conditions as $index => $value) { - if (is_string($value) && in_array($value, ['and', 'or', 'xor'])) { + if (is_int($index) && is_array($value) && WebformArrayHelper::isSequential($value)) { + // Recurse downward and get nested target element. + // NOTE: Nested conditions is not supported via the UI. + $this->getConditionTargetsVisiblityRecursive($value, $targets); + } + elseif (is_string($value) && in_array($value, ['and', 'or', 'xor'])) { + // Skip AND, OR, or XOR operators. continue; } elseif (is_int($index)) { @@ -629,14 +924,7 @@ protected function isConditionsTargetsVisible(array $conditions, array $elements $selector = $index; } - // Ignore invalid selector and return FALSE. - $input_name = static::getSelectorInputName($selector); - if (!$input_name) { - return FALSE; - } - - $element_key = static::getInputNameAsArray($input_name, 0); - return isset($elements[$element_key]) ? TRUE : FALSE; + $targets[$selector] = $selector; } } diff --git a/web/modules/webform/src/WebformSubmissionConditionsValidatorInterface.php b/web/modules/webform/src/WebformSubmissionConditionsValidatorInterface.php index c1e59bc7d8..6c61df1d7b 100644 --- a/web/modules/webform/src/WebformSubmissionConditionsValidatorInterface.php +++ b/web/modules/webform/src/WebformSubmissionConditionsValidatorInterface.php @@ -16,6 +16,8 @@ interface WebformSubmissionConditionsValidatorInterface { * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * + * @see \Drupal\webform\WebformSubmissionForm::buildForm */ public function buildForm(array &$form, FormStateInterface $form_state); @@ -26,9 +28,23 @@ public function buildForm(array &$form, FormStateInterface $form_state); * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * + * @see \Drupal\webform\WebformSubmissionForm::validateForm */ public function validateForm(array &$form, FormStateInterface $form_state); + /** + * Submit form #states for visible elements. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @see \Drupal\webform\WebformSubmissionForm::submitForm + */ + public function submitForm(array &$form, FormStateInterface $form_state); + /** * Validate state with conditions. * @@ -53,7 +69,9 @@ public function validateState($state, array $conditions, WebformSubmissionInterf * A webform submission. * * @return bool|null - * TRUE if conditions validate. NULL if conditions can't be processed. + * TRUE if the conditions validate. NULL if the conditions can't be + * processed. NULL is returned when there is an invalid selector or a + * missing element in the conditions. * * @see drupal_process_states() */ diff --git a/web/modules/webform/src/WebformSubmissionExporter.php b/web/modules/webform/src/WebformSubmissionExporter.php index 1954820249..ee6e41f4ae 100644 --- a/web/modules/webform/src/WebformSubmissionExporter.php +++ b/web/modules/webform/src/WebformSubmissionExporter.php @@ -2,6 +2,7 @@ namespace Drupal\webform; +use Drupal\Core\Archiver\ArchiverManager; use Drupal\Core\Archiver\ArchiveTar; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; @@ -51,6 +52,13 @@ class WebformSubmissionExporter implements WebformSubmissionExporterInterface { */ protected $streamWrapperManager; + /** + * The archiver manager. + * + * @var \Drupal\Core\Archiver\ArchiverManager + */ + protected $archiverManager; + /** * Webform element manager. * @@ -111,16 +119,19 @@ class WebformSubmissionExporter implements WebformSubmissionExporterInterface { * The entity type manager. * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager * The stream wrapper manager. + * @param \Drupal\Core\Archiver\ArchiverManager $archiver_manager + * The archiver manager. * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager * The webform element manager. * @param \Drupal\webform\Plugin\WebformExporterManagerInterface $exporter_manager * The results exporter manager. */ - public function __construct(ConfigFactoryInterface $config_factory, FileSystemInterface $file_system, EntityTypeManagerInterface $entity_type_manager, StreamWrapperManagerInterface $stream_wrapper_manager, WebformElementManagerInterface $element_manager, WebformExporterManagerInterface $exporter_manager) { + public function __construct(ConfigFactoryInterface $config_factory, FileSystemInterface $file_system, EntityTypeManagerInterface $entity_type_manager, StreamWrapperManagerInterface $stream_wrapper_manager, ArchiverManager $archiver_manager, WebformElementManagerInterface $element_manager, WebformExporterManagerInterface $exporter_manager) { $this->configFactory = $config_factory; $this->fileSystem = $file_system; $this->entityStorage = $entity_type_manager->getStorage('webform_submission'); $this->streamWrapperManager = $stream_wrapper_manager; + $this->archiverManager = $archiver_manager; $this->elementManager = $element_manager; $this->exporterManager = $exporter_manager; } @@ -264,7 +275,13 @@ public function getDefaultExportOptions() { 'files' => FALSE, ]; - // Append element handler default options. + // Append webform exporter default options. + $exporter_plugins = $this->exporterManager->getInstances(); + foreach ($exporter_plugins as $element_type => $element_plugin) { + $this->defaultOptions += $element_plugin->defaultConfiguration(); + } + + // Append webform element default options. $element_types = $this->getWebformElementTypes(); $element_plugins = $this->elementManager->getInstances(); foreach ($element_plugins as $element_type => $element_plugin) { @@ -289,6 +306,11 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st $exporter_plugins = $this->exporterManager->getInstances($export_options); $states_archive = ['invisible' => []]; $states_options = ['invisible' => []]; + $states_files = [ + 'invisible' => [ + [':input[name="download"]' => ['checked' => FALSE]], + ], + ]; foreach ($exporter_plugins as $plugin_id => $exporter_plugin) { if ($exporter_plugin->isArchive()) { if ($states_archive['invisible']) { @@ -296,6 +318,12 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st } $states_archive['invisible'][] = [':input[name="exporter"]' => ['value' => $plugin_id]]; } + if (!$exporter_plugin->hasFiles()) { + if ($states_archive['invisible']) { + $states_files['invisible'][] = 'or'; + } + $states_files['invisible'][] = [':input[name="exporter"]' => ['value' => $plugin_id]]; + } if (!$exporter_plugin->hasOptions()) { if ($states_options['invisible']) { $states_options['invisible'][] = 'or'; @@ -304,6 +332,17 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st } } + $form['#attributes']['data-webform-states-no-clear'] = TRUE; + + // Build the list of exporter descriptions. + $exporters = $this->exporterManager->getInstances(); + $exporter_description = ''; + foreach ($exporters as $exporter) { + $exporter_description .= '<hr/>'; + $exporter_description .= '<div><strong>' . $exporter->label() . '</strong></div>'; + $exporter_description .= '<div>' . $exporter->description() . '</div>'; + } + $form['export']['format'] = [ '#type' => 'details', '#title' => $this->t('Format options'), @@ -313,6 +352,7 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st '#type' => 'select', '#title' => $this->t('Export format'), '#options' => $this->exporterManager->getOptions(), + '#description' => $exporter_description, '#default_value' => $export_options['exporter'], // Below .js-webform-exporter is used for exporter configuration form // #states. @@ -470,11 +510,7 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st '#return_value' => TRUE, '#default_value' => ($webform->hasManagedFile()) ? $export_options['files'] : 0, '#access' => $webform->hasManagedFile(), - '#states' => [ - 'invisible' => [ - ':input[name="download"]' => ['checked' => FALSE], - ], - ], + '#states' => $states_files, ]; $source_entity = $this->getSourceEntity(); @@ -492,14 +528,14 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st $form['export']['download']['submitted']['entity_type'] = [ '#type' => 'select', '#title' => $this->t('Entity type'), - '#title_display' => 'Invisible', + '#title_display' => 'invisible', '#options' => ['' => $this->t('All')] + $entity_types, '#default_value' => $export_options['entity_type'], ]; $form['export']['download']['submitted']['entity_id'] = [ '#type' => 'number', '#title' => $this->t('Entity id'), - '#title_display' => 'Invisible', + '#title_display' => 'invisible', '#min' => 1, '#size' => 10, '#default_value' => $export_options['entity_id'], @@ -556,15 +592,15 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st ], ]; $form['export']['download'][$key]['range_start'] = $range_element + [ - '#title' => $this->t('From'), - '#parents' => [$key, 'range_start'], - '#default_value' => $export_options['range_start'], - ]; + '#title' => $this->t('From'), + '#parents' => [$key, 'range_start'], + '#default_value' => $export_options['range_start'], + ]; $form['export']['download'][$key]['range_end'] = $range_element + [ - '#title' => $this->t('To'), - '#parents' => [$key, 'range_end'], - '#default_value' => $export_options['range_end'], - ]; + '#title' => $this->t('To'), + '#parents' => [$key, 'range_end'], + '#default_value' => $export_options['range_end'], + ]; } $form['export']['download']['order'] = [ '#type' => 'select', @@ -670,7 +706,7 @@ public function writeRecords(array $webform_submissions) { $is_archive = ($this->isArchive() && $export_options['files']); $files_directories = []; if ($is_archive) { - $archiver = new ArchiveTar($this->getArchiveFilePath(), 'gz'); + $archiver = $this->getArchiveTar(); $stream_wrappers = array_keys($this->streamWrapperManager->getNames(StreamWrapperInterface::WRITE_VISIBLE)); foreach ($stream_wrappers as $stream_wrapper) { $files_directory = $this->fileSystem->realpath($stream_wrapper . '://webform/' . $webform->id()); @@ -710,9 +746,7 @@ public function writeFooter() { public function writeExportToArchive() { $export_file_path = $this->getExportFilePath(); if (file_exists($export_file_path)) { - $archive_file_path = $this->getArchiveFilePath(); - - $archiver = new ArchiveTar($archive_file_path, 'gz'); + $archiver = $this->getArchiveTar(); $archiver->addModify($export_file_path, $this->getBaseFileName(), $this->getFileTempDirectory()); @unlink($export_file_path); @@ -793,6 +827,7 @@ public function getQuery() { if ($export_options['range_type'] == 'latest' && $export_options['range_latest']) { // Clone the query and use it to get latest sid starting sid. $latest_query = clone $query; + $latest_query->sort('created', 'DESC'); $latest_query->sort('sid', 'DESC'); $latest_query->range(0, (int) $export_options['range_latest']); if ($latest_query_entity_ids = $latest_query->execute()) { @@ -800,10 +835,16 @@ public function getQuery() { } } else { - // Sort by sid in ASC or DESC order. + // Sort by created and sid in ASC or DESC order. + $query->sort('created', isset($export_options['order']) ? $export_options['order'] : 'ASC'); $query->sort('sid', isset($export_options['order']) ? $export_options['order'] : 'ASC'); } + // Do not check access to submission since the exporter UI and Drush + // already have access checking. + // @see webform_query_webform_submission_access_alter() + $query->accessCheck(FALSE); + return $query; } @@ -866,7 +907,7 @@ public function requiresBatch() { * {@inheritdoc} */ public function getFileTempDirectory() { - return file_directory_temp(); + return $this->configFactory->get('webform.settings')->get('export.temp_directory') ?: file_directory_temp(); } /** @@ -931,4 +972,26 @@ public function isBatch() { return ($this->isArchive() || ($this->getTotal() >= $this->getBatchLimit())); } + /** + * Construct an instance of archive tar implementation. + * + * @return \Drupal\Core\Archiver\ArchiveTar + * Archive tar implementation object. + */ + protected function getArchiveTar() { + $archive_tar = $this->archiverManager->getInstance([ + 'filepath' => $this->getArchiveFilePath(), + ]); + + $archive_tar = $archive_tar->getArchive(); + + if ($archive_tar instanceof ArchiveTar) { + // Make it gzip compress. + $archive_tar->_compress = TRUE; + $archive_tar->_compress_type = 'gz'; + } + + return $archive_tar; + } + } diff --git a/web/modules/webform/src/WebformSubmissionExporterInterface.php b/web/modules/webform/src/WebformSubmissionExporterInterface.php index c45980f436..4e54fd7643 100644 --- a/web/modules/webform/src/WebformSubmissionExporterInterface.php +++ b/web/modules/webform/src/WebformSubmissionExporterInterface.php @@ -113,7 +113,7 @@ public function buildExportOptionsForm(array &$form, FormStateInterface $form_st /** * Get the values from the webform's user input or webform state values. * - * @paran array $input + * @param array $values * An associative array of user input or webform state values. * * @return array diff --git a/web/modules/webform/src/WebformSubmissionForm.php b/web/modules/webform/src/WebformSubmissionForm.php index f4f8ef6118..4f79d946db 100644 --- a/web/modules/webform/src/WebformSubmissionForm.php +++ b/web/modules/webform/src/WebformSubmissionForm.php @@ -3,6 +3,7 @@ namespace Drupal\webform; use Drupal\Component\Render\PlainTextOutput; +use Drupal\Component\Utility\Bytes; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; @@ -11,6 +12,7 @@ use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Path\PathValidatorInterface; use Drupal\Core\Render\Element; @@ -18,14 +20,18 @@ use Drupal\Core\Routing\TrustedRedirectResponse; use Drupal\Core\Template\Attribute; use Drupal\Core\Url; -use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Form\WebformDialogFormTrait; use Drupal\webform\Plugin\WebformElement\Hidden; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Plugin\WebformHandlerInterface; +use Drupal\webform\Plugin\WebformSourceEntityManager; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformDialogHelper; +use Drupal\webform\Utility\WebformElementHelper; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Drupal\Core\Config\ConfigFactoryInterface; /** * Provides a webform to collect and edit submissions. @@ -34,6 +40,13 @@ class WebformSubmissionForm extends ContentEntityForm { use WebformDialogFormTrait; + /** + * The configuration object factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + /** * The renderer service. * @@ -62,13 +75,6 @@ class WebformSubmissionForm extends ContentEntityForm { */ protected $elementManager; - /** - * The webform submission storage. - * - * @var \Drupal\webform\WebformSubmissionStorageInterface - */ - protected $storage; - /** * Webform request handler. * @@ -100,7 +106,7 @@ class WebformSubmissionForm extends ContentEntityForm { /** * The webform submission conditions (#states) validator. * - * @var \Drupal\webform\WebformSubmissionConditionsValidator + * @var \Drupal\webform\WebformSubmissionConditionsValidatorInterface */ protected $conditionsValidator; @@ -118,13 +124,6 @@ class WebformSubmissionForm extends ContentEntityForm { */ protected $generate; - /** - * The webform settings. - * - * @var array - */ - protected $settings; - /** * The webform submission. * @@ -139,11 +138,20 @@ class WebformSubmissionForm extends ContentEntityForm { */ protected $sourceEntity; + /** + * States API prefix. + * + * @var string + */ + protected $statesPrefix; + /** * Constructs a WebformSubmissionForm object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer service. * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager @@ -167,14 +175,28 @@ class WebformSubmissionForm extends ContentEntityForm { * @param \Drupal\webform\WebformSubmissionGenerateInterface $submission_generate * The webform submission generation service. */ - public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, WebformRequestInterface $request_handler, WebformElementManagerInterface $element_manager, WebformThirdPartySettingsManagerInterface $third_party_settings_manager, WebformMessageManagerInterface $message_manager, WebformTokenManagerInterface $token_manager, WebformSubmissionConditionsValidator $conditions_validator, WebformEntityReferenceManagerInterface $webform_entity_reference_manager, WebformSubmissionGenerateInterface $submission_generate) { + public function __construct( + EntityManagerInterface $entity_manager, + ConfigFactoryInterface $config_factory, + RendererInterface $renderer, + AliasManagerInterface $alias_manager, + PathValidatorInterface $path_validator, + WebformRequestInterface $request_handler, + WebformElementManagerInterface $element_manager, + WebformThirdPartySettingsManagerInterface $third_party_settings_manager, + WebformMessageManagerInterface $message_manager, + WebformTokenManagerInterface $token_manager, + WebformSubmissionConditionsValidator $conditions_validator, + WebformEntityReferenceManagerInterface $webform_entity_reference_manager, + WebformSubmissionGenerateInterface $submission_generate + ) { parent::__construct($entity_manager); + $this->configFactory = $config_factory; $this->renderer = $renderer; $this->requestHandler = $request_handler; $this->aliasManager = $alias_manager; $this->pathValidator = $path_validator; $this->elementManager = $element_manager; - $this->storage = $this->entityManager->getStorage('webform_submission'); $this->thirdPartySettingsManager = $third_party_settings_manager; $this->messageManager = $message_manager; $this->tokenManager = $token_manager; @@ -190,6 +212,7 @@ public function __construct(EntityManagerInterface $entity_manager, RendererInte public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), + $container->get('config.factory'), $container->get('renderer'), $container->get('path.alias_manager'), $container->get('path.validator'), @@ -201,7 +224,6 @@ public static function create(ContainerInterface $container) { $container->get('webform_submission.conditions_validator'), $container->get('webform.entity_reference_manager'), $container->get('webform_submission.generate') - ); } @@ -238,7 +260,6 @@ public function getFormId() { * @see \Drupal\Core\Entity\EntityFormBuilder::getForm */ public function setEntity(EntityInterface $entity) { - /** @var \Drupal\webform\WebformSubmissionInterface $entity */ $webform = $entity->getWebform(); @@ -254,33 +275,46 @@ public function setEntity(EntityInterface $entity) { // Get the source entity and allow webform submission to be used as a source // entity. - $this->sourceEntity = $this->requestHandler->getCurrentSourceEntity(['webform']); - if ($this->sourceEntity === $entity) { - $this->sourceEntity = $this->requestHandler->getCurrentSourceEntity(['webform', 'webform_submission']); + $source_entity = $entity->getSourceEntity() ?: $this->requestHandler->getCurrentSourceEntity(['webform']); + if ($source_entity === $entity) { + $source_entity = $this->requestHandler->getCurrentSourceEntity(['webform', 'webform_submission']); + } + // Handle paragraph sourc entity. + if ($source_entity && $source_entity->getEntityTypeId() === 'paragraph') { + // Default data supports paragraph source entity tokens. + // @see \Drupal\webform\Plugin\Field\FieldFormatter\WebformEntityReferenceEntityFormatter::viewElements + $data = $entity->getData(); + // Disable :clear suffix to prevent webform tokens from being removed. + $data = $this->tokenManager->replace($data, $source_entity, [], ['suffixes' => ['clear' => FALSE]]); + $entity->setData($data); + + $source_entity = WebformSourceEntityManager::getMainSourceEntity($source_entity); } + // Set source entity. + $this->sourceEntity = $source_entity; - $source_entity = $this->sourceEntity; + // Get account. + $account = $this->currentUser(); // Load entity from token or saved draft when not editing or testing // submission form. if (!in_array($this->operation, ['edit', 'edit_all', 'test'])) { $token = $this->getRequest()->query->get('token'); - $webform_submission_token = $this->storage->loadFromToken($token, $webform, $source_entity); + $webform_submission_token = $this->getStorage()->loadFromToken($token, $webform, $source_entity); if ($webform_submission_token) { $entity = $webform_submission_token; } elseif ($webform->getSetting('draft') != WebformInterface::DRAFT_NONE) { - $account = $this->currentUser(); if ($webform->getSetting('draft_multiple')) { // Allow multiple drafts to be restored using token. // This allows the webform's public facing URL to be used instead of // the admin URL of the webform. - $webform_submission_token = $this->storage->loadFromToken($token, $webform, $source_entity, $account); + $webform_submission_token = $this->getStorage()->loadFromToken($token, $webform, $source_entity, $account); if ($webform_submission_token && $webform_submission_token->isDraft()) { $entity = $webform_submission_token; } } - elseif ($webform_submission_draft = $this->storage->loadDraft($webform, $source_entity, $account)) { + elseif ($webform_submission_draft = $this->getStorage()->loadDraft($webform, $source_entity, $account)) { // Else load the most recent draft. $entity = $webform_submission_draft; } @@ -290,17 +324,46 @@ public function setEntity(EntityInterface $entity) { // Set entity before calling get last submission. $this->entity = $entity; - // Autofill with previous submission. - if ($this->operation === 'add' && $entity->isNew() && $webform->getSetting('autofill')) { - if ($last_submission = $this->getLastSubmission()) { - $excluded_elements = $webform->getSetting('autofill_excluded_elements') ?: []; - $last_submission_data = array_diff_key($last_submission->getData(), $excluded_elements); - $entity->setData($last_submission_data + $entity->getData()); + if ($entity->isNew()) { + if ($webform->getSetting('limit_total_unique')) { + // Get last webform/source entity submission. + $last_submission = $this->getStorage()->getLastSubmission($webform, $source_entity, NULL, ['in_draft' => FALSE]); + if ($last_submission) { + $entity = $last_submission; + $this->operation = 'edit'; + } + } + elseif ($webform->getSetting('limit_user_unique')) { + // Require user to be authenticated to access a unique submission. + if (!$account->isAuthenticated()) { + throw new AccessDeniedHttpException(); + } + + // Get last user submission. + $last_submission = $this->getStorage()->getLastSubmission($webform, $source_entity, $account, ['in_draft' => FALSE]); + if ($last_submission) { + $entity = $last_submission; + $this->operation = 'edit'; + } + } + } + + if ($this->operation === 'add' && $entity->isNew()) { + if ($webform->getSetting('autofill')) { + // Autofill with previous submission. + if ($last_submission = $this->getLastSubmission()) { + $excluded_elements = $webform->getSetting('autofill_excluded_elements') ?: []; + $last_submission_data = array_diff_key($last_submission->getData(), $excluded_elements); + $entity->setData($last_submission_data + $entity->getData()); + } } } - // Alter webform settings before setting the entity. - $entity->getWebform()->invokeHandlers('overrideSettings', $entity); + // Override settings. + $this->overrideSettings($entity); + + // Set the webform's current operation. + $webform->setOperation($this->operation); return parent::setEntity($entity); } @@ -312,18 +375,42 @@ public function buildEntity(array $form, FormStateInterface $form_state) { /** @var \Drupal\webform\WebformSubmissionInterface $entity */ $entity = parent::buildEntity($form, $form_state); - // Alter webform settings before setting the entity. - $entity->getWebform()->invokeHandlers('overrideSettings', $entity); + // Override settings. + $this->overrideSettings($entity); return $entity; } /** - * {@inheritdoc} + * Override webform settings for the webform submission. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. */ - protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { - parent::copyFormValuesToEntity($entity, $form, $form_state); + protected function overrideSettings(WebformSubmissionInterface $webform_submission) { + $webform = $webform_submission->getWebform(); + + // Invoke override settings which resets the webform settings. + $webform->invokeHandlers('overrideSettings', $webform_submission); + + // Look for ?_webform_dialog=1 which enables Ajax support when this form is + // opened in dialog. + // @see webform.dialog.js + // + // Must be called after WebformHandler::overrideSettings which resets all + // overridden settings. + // @see \Drupal\webform\Entity\Webform::invokeHandlers + if ($this->getRequest()->query->get('_webform_dialog') && !$webform->getSetting('ajax')) { + $webform->setSettingOverride('ajax', TRUE); + } + } + /** + * {@inheritdoc} + */ + public function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + // NOTE: We are not copying form values to the entity because + // webform element keys can override webform submission properties. /* @var $webform_submission \Drupal\webform\WebformSubmissionInterface */ $webform_submission = $entity; $webform = $webform_submission->getWebform(); @@ -341,6 +428,12 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, if ($current_page = $this->getCurrentPage($form, $form_state)) { $entity->setCurrentPage($current_page); } + + // Set in draft. + $in_draft = $form_state->get('in_draft'); + if ($in_draft !== NULL) { + $entity->set('in_draft', $in_draft); + } } /** @@ -375,6 +468,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { // Server side #states API validation. $this->conditionsValidator->buildForm($form, $form_state); + // Add Ajax callbacks. + $form = $this->buildAjaxForm($form, $form_state); + // Alter webform via webform handler. $this->getWebform()->invokeHandlers('alterForm', $form, $form_state, $webform_submission); @@ -382,7 +478,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form_id = $this->getFormId(); $this->thirdPartySettingsManager->alter('webform_submission_form', $form, $form_state, $form_id); - return $this->buildAjaxForm($form, $form_state); + return $form; } /** @@ -418,6 +514,14 @@ public function form(array $form, FormStateInterface $form_state) { } } + // Disable the default $form['#theme'] templates. + // If the webform's id begins with an underscore the #theme + // was automatically being set to 'webform_submission__WEBFORM_ID', this + // causes the form to be rendered using the 'webform_submission' template. + // @see \Drupal\Core\Form\FormBuilder::prepareForm + // @see webform-submission-form.html.twig + $form['#theme'] = ['webform_submission_form']; + // Define very specific webform classes, this override the form's // default classes. // @see \Drupal\Core\Form\FormBuilder::retrieveForm @@ -437,6 +541,10 @@ public function form(array $form, FormStateInterface $form_state) { array_walk($class, ['\Drupal\Component\Utility\Html', 'getClass']); $form['#attributes']['class'] = $class; + // Get last class, which is the most specific, as #states prefix. + // @see \Drupal\webform\WebformSubmissionForm::addStatesPrefix + $this->statesPrefix = '.' . end($class); + // Check for a custom webform, track it, and return it. if ($custom_form = $this->getCustomForm($form, $form_state)) { $custom_form['#custom_form'] = TRUE; @@ -450,12 +558,12 @@ public function form(array $form, FormStateInterface $form_state) { // Prepend webform submission data using the default view without the data. if (!$webform_submission->isNew() && !$webform_submission->isDraft()) { $form['navigation'] = [ - '#theme' => 'webform_submission_navigation', + '#type' => 'webform_submission_navigation', '#webform_submission' => $webform_submission, '#weight' => -20, ]; $form['information'] = [ - '#theme' => 'webform_submission_information', + '#type' => 'webform_submission_information', '#webform_submission' => $webform_submission, '#source_entity' => $this->sourceEntity, '#weight' => -19, @@ -483,8 +591,9 @@ public function form(array $form, FormStateInterface $form_state) { ], '#attributes' => ['class' => ['js-hide', 'webform-confirmation-modal', 'js-webform-confirmation-modal']], '#weight' => -1000, + '#attached' => ['library' => ['webform/webform.confirmation.modal']], + '#element_validate' => ['::removeConfirmationModal'], ]; - $form['#attached']['library'][] = 'webform/webform.confirmation.modal'; } /* Data */ @@ -504,16 +613,25 @@ public function form(array $form, FormStateInterface $form_state) { // Prepare webform elements. $this->prepareElements($elements, $form, $form_state); - // Add wizard progress tracker to the webform. - $current_page = $this->getCurrentPage($form, $form_state); - if ($current_page && $this->getWebformSetting('wizard_progress_bar') || $this->getWebformSetting('wizard_progress_pages') || $this->getWebformSetting('wizard_progress_percentage')) { - $form['progress'] = [ - '#theme' => 'webform_progress', - '#webform' => $this->getWebform(), - '#current_page' => $current_page, - '#operation' => $this->operation, - '#weight' => -20, - ]; + // Add wizard progress tracker and page links to the webform. + $pages = $webform->getPages($this->operation); + if ($pages) { + $current_page = $this->getCurrentPage($form, $form_state); + + // Add hidden pages submit actions. + $form['pages'] = $this->pagesElement($form, $form_state); + + // Add progress tracker. + $display_wizard_progress = ($this->getWebformSetting('wizard_progress_bar') || $this->getWebformSetting('wizard_progress_pages') || $this->getWebformSetting('wizard_progress_percentage')); + if ($current_page && $display_wizard_progress) { + $form['progress'] = [ + '#theme' => 'webform_progress', + '#webform' => $this->getWebform(), + '#current_page' => $current_page, + '#operation' => $this->operation, + '#weight' => -20, + ]; + } } // Required indicator. @@ -531,7 +649,7 @@ public function form(array $form, FormStateInterface $form_state) { // Pages: Set current wizard or preview page. $this->displayCurrentPage($form, $form_state); - /* Webform */ + /* Webform */ // Move all $elements properties to the $form. $this->setFormPropertiesFromElements($form, $elements); @@ -564,6 +682,7 @@ public function form(array $form, FormStateInterface $form_state) { if ($this->getWebformSetting('form_unsaved')) { $form['#attributes']['class'][] = 'js-webform-unsaved'; $form['#attached']['library'][] = 'webform/webform.form.unsaved'; + // Set 'data-webform-unsaved' attribute if unsaved wizard. $pages = $this->getPages($form, $form_state); $current_page = $this->getCurrentPage($form, $form_state); if ($current_page && ($current_page != $this->getFirstPage($pages))) { @@ -588,7 +707,7 @@ public function form(array $form, FormStateInterface $form_state) { $form['#attributes']['novalidate'] = 'novalidate'; } - // Inline form errors: Add #disable_inline_form_errors property to form. + // Inline form errors: Add #disable_inline_form_errors property to form. if ($this->getWebformSetting('form_disable_inline_errors')) { $form['#disable_inline_form_errors'] = TRUE; } @@ -643,12 +762,12 @@ protected function getCustomForm(array &$form, FormStateInterface $form_state) { // Exit if elements are broken, usually occurs when elements YAML is edited // directly in the export config file. if (!$webform_submission->getWebform()->getElementsInitialized()) { - return $this->getMessageManager()->append($form, WebformMessageManagerInterface::FORM_EXCEPTION, 'warning'); + return $this->getMessageManager()->append($form, WebformMessageManagerInterface::FORM_EXCEPTION_MESSAGE, 'warning'); } // Exit if submission is locked. if ($webform_submission->isLocked()) { - return $this->getMessageManager()->append($form, WebformMessageManagerInterface::SUBMISSION_LOCKED, 'warning'); + return $this->getMessageManager()->append($form, WebformMessageManagerInterface::SUBMISSION_LOCKED_MESSAGE, 'warning'); } // Check prepopulate source entity required and type. @@ -675,6 +794,7 @@ protected function getCustomForm(array &$form, FormStateInterface $form_state) { // Add hidden back (aka reset) button used by the Ajaxified back to link. // NOTE: Below code could be used to add a 'Reset' button to any webform. // @see Drupal.behaviors.webformConfirmationBackAjax + $form['actions'] = ['#type' => 'actions']; $form['actions']['reset'] = [ '#type' => 'submit', '#value' => $this->t('Reset'), @@ -690,16 +810,16 @@ protected function getCustomForm(array &$form, FormStateInterface $form_state) { } // Don't display webform if it is closed. - if ($webform_submission->isNew() && $webform->isClosed()) { + if (($webform_submission->isNew() || $webform_submission->isDraft()) && $webform->isClosed()) { // If the current user can update any submission just display the closed // message and still allow them to create new submissions. - if ($webform->isTemplate() && $webform->access('duplicate')) { + if ($webform->isTemplate() && $webform->access('duplicate') && !$webform->isArchived()) { if (!$this->isDialog()) { $this->getMessageManager()->display(WebformMessageManagerInterface::TEMPLATE_PREVIEW, 'warning'); } } elseif ($webform->access('submission_update_any')) { - $form = $this->getMessageManager()->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info'); + $form = $this->getMessageManager()->append($form, $webform->isArchived() ? WebformMessageManagerInterface::ADMIN_ARCHIVED : WebformMessageManagerInterface::ADMIN_CLOSED, 'info'); } else { if ($webform->isOpening()) { @@ -730,12 +850,12 @@ protected function getCustomForm(array &$form, FormStateInterface $form_state) { } else { // Display exception message to users. - return $this->getMessageManager()->append($form, WebformMessageManagerInterface::FORM_EXCEPTION, 'warning'); + return $this->getMessageManager()->append($form, WebformMessageManagerInterface::FORM_EXCEPTION_MESSAGE, 'warning'); } } // Check total limit. - if ($this->checkTotalLimit()) { + if ($this->checkTotalLimit() && empty($this->getWebformSetting('limit_total_unique'))) { $form = $this->getMessageManager()->append($form, WebformMessageManagerInterface::LIMIT_TOTAL_MESSAGE); if ($webform->access('submission_update_any')) { $form = $this->getMessageManager()->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info'); @@ -746,7 +866,7 @@ protected function getCustomForm(array &$form, FormStateInterface $form_state) { } // Check user limit. - if ($this->checkUserLimit()) { + if ($this->checkUserLimit() && empty($this->getWebformSetting('limit_user_unique'))) { $form = $this->getMessageManager()->append($form, WebformMessageManagerInterface::LIMIT_USER_MESSAGE, 'warning'); if ($webform->access('submission_update_any')) { $form = $this->getMessageManager()->append($form, WebformMessageManagerInterface::ADMIN_CLOSED, 'info'); @@ -774,7 +894,7 @@ protected function displayMessages(array $form, FormStateInterface $form_state) $source_entity = $this->getSourceEntity(); // Display test message. - if ($this->isGet() && $this->isRoute('webform.test_form')) { + if ($this->isGet() && $this->operation === 'test') { $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_TEST, 'warning'); // Display devel generate link for webform or source entity. @@ -787,12 +907,17 @@ protected function displayMessages(array $form, FormStateInterface $form_state) ]; } $query['destination'] = $this->requestHandler->getUrl($webform, $source_entity, 'webform.results_submissions')->toString(); + $offcanvas = WebformDialogHelper::useOffCanvas(); $build = [ '#type' => 'link', '#title' => $this->t('Generate %title submissions', ['%title' => $webform->label()]), '#url' => Url::fromRoute('devel_generate.webform_submission', [], ['query' => $query]), + '#attributes' => ($offcanvas) ? WebformDialogHelper::getOffCanvasDialogAttributes(400) : [], ]; - drupal_set_message($this->renderer->renderPlain($build), 'warning'); + if ($offcanvas) { + WebformDialogHelper::attachLibraries($form); + } + $this->messenger()->addWarning($this->renderer->renderPlain($build)); } } @@ -807,11 +932,11 @@ protected function displayMessages(array $form, FormStateInterface $form_state) // Display loaded or saved draft message. if ($webform_submission->isDraft()) { if ($form_state->get('draft_saved')) { - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_SAVED); + $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_SAVED_MESSAGE); $form_state->set('draft_saved', FALSE); } - elseif ($this->isGet() && !$webform->getSetting('draft_multiple')) { - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_LOADED); + elseif ($this->isGet() && !$webform->getSetting('draft_multiple') && !$webform->isClosed()) { + $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_DRAFT_LOADED_MESSAGE); } } @@ -821,13 +946,13 @@ protected function displayMessages(array $form, FormStateInterface $form_state) && $this->getWebformSetting('draft') !== WebformInterface::DRAFT_NONE && $this->getWebformSetting('draft_multiple', FALSE) && ($this->isRoute('webform.canonical') || $this->isWebformEntityReferenceFromSourceEntity()) - && ($previous_draft_total = $this->storage->getTotal($webform, $this->sourceEntity, $this->currentUser(), ['in_draft' => TRUE])) + && ($previous_draft_total = $this->getStorage()->getTotal($webform, $this->sourceEntity, $this->currentUser(), ['in_draft' => TRUE])) ) { if ($previous_draft_total > 1) { $this->getMessageManager()->display(WebformMessageManagerInterface::DRAFTS_PREVIOUS); } else { - $draft_submission = $this->storage->loadDraft($webform, $this->sourceEntity, $this->currentUser()); + $draft_submission = $this->getStorage()->loadDraft($webform, $this->sourceEntity, $this->currentUser()); if (!$draft_submission || $webform_submission->id() != $draft_submission->id()) { $this->getMessageManager()->display(WebformMessageManagerInterface::DRAFT_PREVIOUS); } @@ -840,13 +965,16 @@ protected function displayMessages(array $form, FormStateInterface $form_state) && $this->getWebformSetting('form_previous_submissions', FALSE) && ($this->isRoute('webform.canonical') || $this->isWebformEntityReferenceFromSourceEntity()) && ($webform->access('submission_view_own') || $this->currentUser()->hasPermission('view own webform submission')) - && ($previous_submission_total = $this->storage->getTotal($webform, $this->sourceEntity, $this->currentUser())) + && ($previous_submission_total = $this->getStorage()->getTotal($webform, $this->sourceEntity, $this->currentUser())) ) { if ($previous_submission_total > 1) { - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSIONS_PREVIOUS); + $this->getMessageManager()->display(WebformMessageManagerInterface::PREVIOUS_SUBMISSIONS); } - elseif ($webform_submission->id() != $this->getLastSubmission(FALSE)->id()) { - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_PREVIOUS); + else { + $last_submission = $this->getLastSubmission(FALSE); + if ($last_submission && $webform_submission->id() !== $last_submission->id()) { + $this->getMessageManager()->display(WebformMessageManagerInterface::PREVIOUS_SUBMISSION); + } } } @@ -856,7 +984,7 @@ protected function displayMessages(array $form, FormStateInterface $form_state) && $webform_submission->isNew() && $webform->getSetting('autofill') && $this->getLastSubmission()) { - $this->getMessageManager()->display(WebformMessageManagerInterface::AUTOFILL); + $this->getMessageManager()->display(WebformMessageManagerInterface::AUTOFILL_MESSAGE); } } @@ -884,6 +1012,82 @@ public function afterBuild(array $form, FormStateInterface $form_state) { return $form; } + /** + * Returns the wizard page submit buttons for the current entity form. + */ + protected function pagesElement(array $form, FormStateInterface $form_state) { + $pages = $this->getPages($form, $form_state); + if (!$pages) { + return NULL; + } + + $current_page_name = $this->getCurrentPage($form, $form_state); + if (!$this->getWebformSetting('wizard_progress_link') && !($this->getWebformSetting('wizard_preview_link') && $current_page_name === 'webform_preview')) { + return NULL; + } + + $page_indexes = array_flip(array_keys($pages)); + $current_index = $page_indexes[$current_page_name] - 1; + + // Build dedicated actions element for pages links. + $element = [ + '#type' => 'actions', + '#weight' => -20, + '#attributes' => [ + 'class' => ['webform-wizard-pages-links', 'js-webform-wizard-pages-links'], + ], + // Only process the container and prevent .form-actions from being added + // which force submit buttons to be rendered in dialogs. + // @see \Drupal\Core\Render\Element\Actions + // @see Drupal.behaviors.dialog.prepareDialogButtons + '#process' => [ + ['\Drupal\Core\Render\Element\Actions', 'processContainer'], + ], + ]; + if ($this->getWebformSetting('wizard_progress_link')) { + $element['#attributes']['data-wizard-progress-link'] = 'true'; + } + if ($this->getWebformSetting('wizard_preview_link')) { + $element['#attributes']['data-wizard-preview-link'] = 'true'; + } + + $index = 1; + $total = count($pages); + foreach ($pages as $page_name => $page) { + // Always include submit button for each page but only allows access + // to previous and visible pages. + // + // Developers who want to allow users to jump to any wizard page can + // expose these buttons via a form alter hook. Beware that + // skipped pages will not be validated. + $access = ($page['#access'] && ($page_indexes[$page_name] <= $current_index)) ? TRUE : FALSE; + $t_args = [ + '@label' => $page['#title'], + '@start' => $index++, + '@end' => $total, + ]; + $element[$page_name] = [ + '#type' => 'submit', + '#value' => $this->t('Edit'), + '#page' => $page_name, + '#validate' => ['::noValidate'], + '#submit' => ['::gotoPage'], + '#name' => 'webform_wizard_page-' . $page_name, + '#attributes' => [ + 'data-webform-page' => $page_name, + 'formnovalidate' => 'formnovalidate', + 'class' => ['webform-wizard-pages-link', 'js-webform-wizard-pages-link'], + 'title' => $this->t("Edit '@label' (Page @start of @end)", $t_args), + ], + '#access' => $access, + ]; + } + + $element['#attached']['library'][] = 'webform/webform.wizard.pages'; + + return $element; + } + /** * {@inheritdoc} */ @@ -983,7 +1187,10 @@ protected function actions(array $form, FormStateInterface $form_state) { // @see \Drupal\webform\WebformSubmissionForm::noValidate '#validate' => ['::noValidate'], '#submit' => ['::previous'], - '#attributes' => ['class' => ['webform-button--previous', 'js-webform-novalidate']], + '#attributes' => [ + 'formnovalidate' => 'formnovalidate', + 'class' => ['webform-button--previous'], + ], '#weight' => 0, ]; if ($track) { @@ -1006,7 +1213,10 @@ protected function actions(array $form, FormStateInterface $form_state) { // @see \Drupal\webform\WebformSubmissionForm::noValidate '#validate' => ['::noValidate'], '#submit' => ['::previous'], - '#attributes' => ['class' => ['webform-button--previous', 'js-webform-novalidate']], + '#attributes' => [ + 'formnovalidate' => 'formnovalidate', + 'class' => ['webform-button--previous'], + ], '#weight' => 0, ]; if ($track) { @@ -1052,7 +1262,9 @@ protected function actions(array $form, FormStateInterface $form_state) { } } } - $element['#attached']['library'][] = 'webform/webform.form.wizard'; + if ($track) { + $element['#attached']['library'][] = 'webform/webform.wizard.track'; + } } // Draft. @@ -1062,7 +1274,10 @@ protected function actions(array $form, FormStateInterface $form_state) { '#value' => $this->config('webform.settings')->get('settings.default_draft_button_label'), '#validate' => ['::draft'], '#submit' => ['::submitForm', '::save', '::rebuild'], - '#attributes' => ['class' => ['webform-button--draft', 'js-webform-novalidate']], + '#attributes' => [ + 'formnovalidate' => 'formnovalidate', + 'class' => ['webform-button--draft'], + ], '#weight' => -10, ]; } @@ -1074,7 +1289,10 @@ protected function actions(array $form, FormStateInterface $form_state) { '#value' => $this->config('webform.settings')->get('settings.default_reset_button_label'), '#validate' => ['::noValidate'], '#submit' => ['::reset'], - '#attributes' => ['class' => ['webform-button--reset', 'js-webform-novalidate']], + '#attributes' => [ + 'formnovalidate' => 'formnovalidate', + 'class' => ['webform-button--reset'], + ], '#weight' => 10, ]; } @@ -1084,6 +1302,20 @@ protected function actions(array $form, FormStateInterface $form_state) { return $element; } + /** + * Webform submission handler for the 'goto' action. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function gotoPage(array &$form, FormStateInterface $form_state) { + $element = $form_state->getTriggeringElement(); + $form_state->set('current_page', $element['#page']); + $this->wizardSubmit($form, $form_state); + } + /** * Webform submission handler for the 'next' action. * @@ -1097,8 +1329,15 @@ public function next(array &$form, FormStateInterface $form_state) { return; } $pages = $this->getPages($form, $form_state); + + // Get next page. $current_page = $this->getCurrentPage($form, $form_state); - $form_state->set('current_page', $this->getNextPage($pages, $current_page)); + $next_page = $this->getNextPage($pages, $current_page); + + // Set next page. + $form_state->set('current_page', $next_page); + + // Submit next page. $this->wizardSubmit($form, $form_state); } @@ -1112,8 +1351,15 @@ public function next(array &$form, FormStateInterface $form_state) { */ public function previous(array &$form, FormStateInterface $form_state) { $pages = $this->getPages($form, $form_state); + + // Get previous page. $current_page = $this->getCurrentPage($form, $form_state); - $form_state->set('current_page', $this->getPreviousPage($pages, $current_page)); + $previous_page = $this->getPreviousPage($pages, $current_page); + + // Set previous page. + $form_state->set('current_page', $previous_page); + + // Submit previous page. $this->wizardSubmit($form, $form_state); } @@ -1135,7 +1381,7 @@ protected function wizardSubmit(array &$form, FormStateInterface $form_state) { $this->confirmForm($form, $form_state); } elseif ($this->draftEnabled() && $this->getWebformSetting('draft_auto_save') && !$this->entity->isCompleted()) { - $form_state->setValue('in_draft', TRUE); + $form_state->set('in_draft', TRUE); $this->submitForm($form, $form_state); $this->save($form, $form_state); @@ -1145,6 +1391,25 @@ protected function wizardSubmit(array &$form, FormStateInterface $form_state) { $this->submitForm($form, $form_state); $this->rebuild($form, $form_state); } + + // Announce current page with progress. + // @see template_preprocess_webform_progress() + if ($this->isAjax()) { + $pages = $this->getPages($form, $form_state); + + $page_keys = array_keys($pages); + $page_indexes = array_flip($page_keys); + $current_index = $page_indexes[$current_page]; + $total_pages = count($page_keys); + + $t_args = [ + '@title' => $this->getWebform()->label(), + '@page' => $pages[$current_page]['#title'], + '@start' => ($current_index + 1), + '@end' => $total_pages, + ]; + $this->announce($this->t('"@title: @page" loaded. (Page @start of @end)', $t_args)); + } } /** @@ -1158,7 +1423,7 @@ protected function wizardSubmit(array &$form, FormStateInterface $form_state) { public function autosave(array &$form, FormStateInterface $form_state) { if ($form_state->hasAnyErrors()) { if ($this->draftEnabled() && $this->getWebformSetting('draft_auto_save') && !$this->entity->isCompleted()) { - $form_state->setValue('in_draft', TRUE); + $form_state->set('in_draft', TRUE); $this->submitForm($form, $form_state); $this->save($form, $form_state); @@ -1177,7 +1442,7 @@ public function autosave(array &$form, FormStateInterface $form_state) { */ public function draft(array &$form, FormStateInterface $form_state) { $form_state->clearErrors(); - $form_state->setValue('in_draft', TRUE); + $form_state->set('in_draft', TRUE); $form_state->set('draft_saved', TRUE); $this->entity->validate(); } @@ -1191,7 +1456,7 @@ public function draft(array &$form, FormStateInterface $form_state) { * The current state of the form. */ public function complete(array &$form, FormStateInterface $form_state) { - $form_state->setValue('in_draft', FALSE); + $form_state->set('in_draft', FALSE); } /** @@ -1209,7 +1474,7 @@ public function complete(array &$form, FormStateInterface $form_state) { * More complex (web)form elements user #validate callbacks * to process and alter an element's submitted value. Element's that rely on * #validate to alter the submitted value include 'Password Confirm', - * 'Email Confirm', 'Composite Elements', 'Other Elements', and more... + * 'Email Confirm', 'Composite Elements', 'Other Elements', and more… * * If the #limit_validation_errors property is used within a multi-step wizard * form, previously submitted values will be corrupted. @@ -1247,6 +1512,12 @@ public function rebuild(array &$form, FormStateInterface $form_state) { public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); + // Disable inline form error when performing validation via the API. + if ($this->operation === 'api') { + // @see \Drupal\webform\WebformSubmissionForm::submitWebformSubmission + $form['#disable_inline_form_errors'] = TRUE; + } + // Build webform submission with validated and processed data. $this->entity = $this->buildEntity($form, $form_state); @@ -1271,9 +1542,47 @@ public function validateForm(array &$form, FormStateInterface $form_state) { }); // @see \Drupal\Core\Form\FormValidator::executeValidateHandlers foreach ($handlers as $callback) { - call_user_func_array($form_state->prepareCallback($callback), [&$form, &$form_state]); + $arguments = [&$form, &$form_state]; + call_user_func_array($form_state->prepareCallback($callback), $arguments); + } + } + + // Validate file (upload) limit. + // @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase::validateManagedFileLimit + $file_limit = $this->getWebform()->getSetting('form_file_limit') + ?: $this->configFactory->get('webform.settings')->get('settings.default_form_file_limit') + ?: ''; + $file_limit = Bytes::toInt($file_limit); + if (!$file_limit) { + return; + } + + // Validate file upload limit. + $file_names = []; + $total_file_size = 0; + $element_keys = $this->getWebform()->getElementsManagedFiles(); + foreach ($element_keys as $element_key) { + $data = $this->entity->getElementData($element_key); + if ($data) { + $fids = (array) $data; + /** @var \Drupal\file\FileInterface[] $files */ + $files = $this->entityTypeManager->getStorage('file')->loadMultiple($fids); + foreach ($files as $file) { + $total_file_size += (int) $file->getSize(); + $file_names[] = $file->getFilename() . ' - ' . format_size($file->getSize(), $this->entity->language()->getId()); + } } } + if ($total_file_size > $file_limit) { + $t_args = ['%quota' => format_size($file_limit)]; + $message = []; + $message['content'] = ['#markup' => $this->t("This form's file upload quota of %quota has been exceeded. Please remove some files.", $t_args)]; + $message['files'] = [ + '#theme' => 'item_list', + '#items' => $file_names, + ]; + $form_state->setErrorByName(NULL, $this->renderer->renderPlain($message)); + } } /** @@ -1281,6 +1590,10 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); + + // Server side #states API submit. + $this->conditionsValidator->submitForm($form, $form_state); + // Submit webform via webform handler. $this->getWebform()->invokeHandlers('submitForm', $form, $form_state, $this->entity); } @@ -1302,17 +1615,26 @@ public function confirmForm(array &$form, FormStateInterface $form_state) { // Confirm webform via webform handler. $this->getWebform()->invokeHandlers('confirmForm', $form, $form_state, $webform_submission); - // Reset the form if reloading the current form via AJAX, and just displaying a message. + // Get confirmation type. $confirmation_type = $this->getWebformSetting('confirmation_type'); + + // Rebuild or reset the form if reloading the current form via AJAX. if ($this->isAjax()) { + // On update, rebuild and display message unless ?destination= is set. + // @see \Drupal\webform\WebformSubmissionForm::setConfirmation $state = $webform_submission->getState(); - if ($confirmation_type == WebformInterface::CONFIRMATION_MESSAGE || $state == WebformSubmissionInterface::STATE_UPDATED) { + if ($state === WebformSubmissionInterface::STATE_UPDATED) { + if (!$this->getRequest()->get('destination')) { + static::rebuild($form, $form_state); + } + } + elseif ($confirmation_type === WebformInterface::CONFIRMATION_MESSAGE || $confirmation_type === WebformInterface::CONFIRMATION_NONE) { static::reset($form, $form_state); } } // Always reset the form to trigger a modal dialog. - if ($confirmation_type == WebformInterface::CONFIRMATION_MODAL) { + if ($confirmation_type === WebformInterface::CONFIRMATION_MODAL) { static::reset($form, $form_state); } } @@ -1328,7 +1650,7 @@ public function save(array $form, FormStateInterface $form_state) { // Make sure the uri and remote addr are set correctly because // Ajax requests can cause these values to be reset. if ($webform_submission->isNew()) { - if (preg_match('/\.webform\.test$/', $this->getRouteMatch()->getRouteName())) { + if (preg_match('/\.webform\.test_form$/', $this->getRouteMatch()->getRouteName())) { // For test submissions use the source URL. $source_url = $webform_submission->set('uri', NULL)->getSourceUrl()->setAbsolute(FALSE); $uri = preg_replace('#^' . base_path() . '#', '/', $source_url->toString()); @@ -1342,12 +1664,9 @@ public function save(array $form, FormStateInterface $form_state) { $uri = preg_replace('/\?$/', '', $uri); } $webform_submission->set('uri', $uri); + $webform_submission->set('remote_addr', ($this->getWebform()->hasRemoteAddr()) ? $this->getRequest()->getClientIp() : ''); if ($this->isConfidential()) { $webform_submission->setOwnerId(0); - $webform_submission->set('remote_addr', ''); - } - else { - $webform_submission->set('remote_addr', $this->getRequest()->getClientIp()); } } @@ -1375,6 +1694,13 @@ public function save(array $form, FormStateInterface $form_state) { * The current state of the form. */ public function reset(array &$form, FormStateInterface $form_state) { + // Delete save draft. + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $this->getEntity(); + if ($webform_submission->isDraft()) { + $webform_submission->delete(); + } + // Create new webform submission. /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */ $webform_submission = $this->getEntity()->createDuplicate(); @@ -1435,10 +1761,10 @@ protected function setFormPropertiesFromElements(array &$form, array &$elements) /****************************************************************************/ /** - * Determine if this is a multistep wizard form. + * Determine if this is a multi-step wizard form. * * @return bool - * TRUE if this multistep wizard form. + * TRUE if this multi-step wizard form. */ protected function hasPages() { return $this->getWebform()->getPages($this->operation); @@ -1505,7 +1831,7 @@ protected function getCurrentPage(array &$form, FormStateInterface $form_state) } else { $current_page = $this->entity->getCurrentPage(); - if ($current_page && isset($pages[$current_page]) && $this->draftEnabled()) { + if ($current_page && isset($pages[$current_page]) && !$this->entity->isCompleted()) { $form_state->set('current_page', $current_page); } else { @@ -1701,7 +2027,7 @@ protected function setConfirmation(FormStateInterface $form_state) { $confirmation_url = $this->aliasManager->getPathByAlias($confirmation_url); if ($redirect_url = $this->pathValidator->getUrlIfValid($confirmation_url)) { if ($confirmation_type == WebformInterface::CONFIRMATION_URL_MESSAGE) { - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION); + $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); } $this->setTrustedRedirectUrl($form_state, $redirect_url); return; @@ -1713,14 +2039,14 @@ protected function setConfirmation(FormStateInterface $form_state) { ]; // Display warning to use who can update the webform. if ($webform->access('update')) { - drupal_set_message($this->t('Confirmation URL %url is not valid.', $t_args), 'warning'); + $this->messenger()->addWarning($this->t('Confirmation URL %url is not valid.', $t_args)); } // Log warning. $this->getLogger('webform')->warning('@webform: Confirmation URL %url is not valid.', $t_args); } // If confirmation URL is invalid display message. - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION); + $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); $route_options['query']['webform_id'] = $webform->id(); $form_state->setRedirect($route_name, $route_parameters, $route_options); return; @@ -1731,11 +2057,11 @@ protected function setConfirmation(FormStateInterface $form_state) { return; case WebformInterface::CONFIRMATION_MESSAGE: - $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION); + $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); return; case WebformInterface::CONFIRMATION_MODAL: - $message = $this->getMessageManager()->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION); + $message = $this->getMessageManager()->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION_MESSAGE); if ($message) { // Set webform confirmation modal in $form_state. $form_state->set('webform_confirmation_modal', [ @@ -1745,6 +2071,9 @@ protected function setConfirmation(FormStateInterface $form_state) { } return; + case WebformInterface::CONFIRMATION_NONE: + return; + case WebformInterface::CONFIRMATION_DEFAULT: default: $this->getMessageManager()->display(WebformMessageManagerInterface::SUBMISSION_DEFAULT_CONFIRMATION); @@ -1752,6 +2081,21 @@ protected function setConfirmation(FormStateInterface $form_state) { } } + /** + * Hide confirmation modal during form validation. + * + * This prevent duplicate modal dialog from appearing. + */ + public static function removeConfirmationModal(&$element, FormStateInterface $form_state, &$complete_form) { + // Reset confirmation modal. + $storage = $form_state->getStorage(); + unset($storage['webform_confirmation_modal']); + $form_state->setStorage($storage); + + // Remove modal from form. + unset($complete_form['webform_confirmation_modal']); + } + /****************************************************************************/ // Elements functions /****************************************************************************/ @@ -1764,7 +2108,7 @@ protected function setConfirmation(FormStateInterface $form_state) { */ protected function hideElements(array &$elements) { foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -1787,18 +2131,49 @@ protected function hideElements(array &$elements) { */ protected function prepareElements(array &$elements, array &$form, FormStateInterface $form_state) { foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } // Build the webform element. $this->elementManager->buildElement($element, $form, $form_state); + if (isset($element['#states'])) { + $element['#states'] = $this->addStatesPrefix($element['#states']); + } + // Recurse and prepare nested elements. $this->prepareElements($element, $form, $form_state); } } + /** + * Add unique class prefix to all :input #states selectors. + * + * @param array $array + * An associative array. + * + * @return array + * An associative array with unique class prefix added to all :input + * #states selectors. + */ + protected function addStatesPrefix(array $array) { + $prefixed_array = []; + foreach ($array as $key => $value) { + if (strpos($key, ':input') === 0) { + $key = $this->statesPrefix . ' ' . $key; + $prefixed_array[$key] = $value; + } + elseif (is_array($value)) { + $prefixed_array[$key] = $this->addStatesPrefix($value); + } + else { + $prefixed_array[$key] = $value; + } + } + return $prefixed_array; + } + /** * Prepopulate element data. * @@ -1829,7 +2204,7 @@ protected function prepopulateData(array &$data) { */ protected function populateElements(array &$elements, array $values) { foreach ($elements as $key => &$element) { - if (Element::property($key) || !is_array($element)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -1890,11 +2265,18 @@ protected function populateElements(array &$elements, array $values) { protected function checkTotalLimit() { $webform = $this->getWebform(); + // Get limit total to unique submission per webform/source entity. + $limit_total_unique = $this->getWebformSetting('limit_total_unique'); + // Check per source entity total limit. $entity_limit_total = $this->getWebformSetting('entity_limit_total'); $entity_limit_total_interval = $this->getWebformSetting('entity_limit_total_interval'); + if ($limit_total_unique) { + $entity_limit_total = 1; + $entity_limit_total_interval = NULL; + } if ($entity_limit_total && ($source_entity = $this->getLimitSourceEntity())) { - if ($this->storage->getTotal($webform, $source_entity, NULL, ['interval' => $entity_limit_total_interval]) >= $entity_limit_total) { + if ($this->getStorage()->getTotal($webform, $source_entity, NULL, ['interval' => $entity_limit_total_interval]) >= $entity_limit_total) { return TRUE; } } @@ -1902,7 +2284,11 @@ protected function checkTotalLimit() { // Check total limit. $limit_total = $this->getWebformSetting('limit_total'); $limit_total_interval = $this->getWebformSetting('limit_total_interval'); - if ($limit_total && $this->storage->getTotal($webform, NULL, NULL, ['interval' => $limit_total_interval]) >= $limit_total) { + if ($limit_total_unique) { + $limit_total = 1; + $limit_total_interval = NULL; + } + if ($limit_total && $this->getStorage()->getTotal($webform, NULL, NULL, ['interval' => $limit_total_interval]) >= $limit_total) { return TRUE; } @@ -1919,17 +2305,8 @@ protected function checkUserLimit() { // Allow anonymous and authenticated users edit own submission. /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $this->getEntity(); - if ($webform_submission->id()) { - if ($this->currentUser()->isAnonymous()) { - if (!empty($_SESSION['webform_submissions']) && in_array($webform_submission->id(), $_SESSION['webform_submissions'])) { - return FALSE; - } - } - else { - if ($webform_submission->getOwnerId() === $this->currentUser()->id()) { - return FALSE; - } - } + if ($webform_submission->id() && $webform_submission->isOwner($this->currentUser())) { + return FALSE; } // Get the submission owner and not current user. @@ -1942,7 +2319,7 @@ protected function checkUserLimit() { $entity_limit_user = $this->getWebformSetting('entity_limit_user'); $entity_limit_user_interval = $this->getWebformSetting('entity_limit_user_interval'); if ($entity_limit_user && ($source_entity = $this->getLimitSourceEntity())) { - if ($this->storage->getTotal($webform, $source_entity, $account, ['interval' => $entity_limit_user_interval]) >= $entity_limit_user) { + if ($this->getStorage()->getTotal($webform, $source_entity, $account, ['interval' => $entity_limit_user_interval]) >= $entity_limit_user) { return TRUE; } } @@ -1950,7 +2327,7 @@ protected function checkUserLimit() { // Check user limit. $limit_user = $this->getWebformSetting('limit_user'); $limit_user_interval = $this->getWebformSetting('limit_user_interval'); - if ($limit_user && $this->storage->getTotal($webform, NULL, $account, ['interval' => $limit_user_interval]) >= $limit_user) { + if ($limit_user && $this->getStorage()->getTotal($webform, NULL, $account, ['interval' => $limit_user_interval]) >= $limit_user) { return TRUE; } @@ -2066,26 +2443,13 @@ protected function isWebformEntityReferenceFromSourceEntity() { // Helper functions /****************************************************************************/ - /** - * Get the message manager. - * - * We need to wrap the message manager service because the webform submission - * entity is being continuous cloned and updated during form processing. - * - * @see \Drupal\Core\Entity\EntityForm::buildEntity - */ - protected function getMessageManager() { - $this->messageManager->setWebformSubmission($this->getEntity()); - return $this->messageManager; - } - /** * Get the webform submission's webform. * * @return \Drupal\webform\WebformInterface * A webform. */ - protected function getWebform() { + public function getWebform() { /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ $webform_submission = $this->getEntity(); return $webform_submission->getWebform(); @@ -2101,6 +2465,29 @@ protected function getSourceEntity() { return $this->sourceEntity; } + /** + * Get the webform submission entity storage. + * + * @return \Drupal\Webform\WebformSubmissionStorageInterface + * The webform submission entity storage. + */ + protected function getStorage() { + return $this->entityManager->getStorage('webform_submission'); + } + + /** + * Get the message manager. + * + * We need to wrap the message manager service because the webform submission + * entity is being continuous cloned and updated during form processing. + * + * @see \Drupal\Core\Entity\EntityForm::buildEntity + */ + protected function getMessageManager() { + $this->messageManager->setWebformSubmission($this->getEntity()); + return $this->messageManager; + } + /** * Get source entity for use with entity limit total and user submissions. * @@ -2132,7 +2519,7 @@ protected function getLastSubmission($completed = TRUE) { $source_entity = $this->getSourceEntity(); $account = $this->getEntity()->getOwner(); $options = ($completed) ? ['in_draft' => FALSE] : []; - return $this->storage->getLastSubmission($webform, $source_entity, $account, $options); + return $this->getStorage()->getLastSubmission($webform, $source_entity, $account, $options); } /** @@ -2147,20 +2534,12 @@ protected function getLastSubmission($completed = TRUE) { * A webform setting. */ protected function getWebformSetting($name, $default_value = NULL) { - // Get webform settings with default values. - if (empty($this->settings)) { - $this->settings = $this->getWebform()->getSettings(); - $default_settings = $this->config('webform.settings')->get('settings'); - foreach ($default_settings as $key => $value) { - $key = str_replace('default_', '', $key); - if (empty($this->settings[$key])) { - $this->settings[$key] = $value; - } - } - } + $value = $this->getWebform()->getSetting($name) + ?: $this->config('webform.settings')->get('settings.default_' . $name) + ?: NULL; - if (isset($this->settings[$name])) { - return $this->tokenManager->replace($this->settings[$name], $this->getEntity()); + if ($value !== NULL) { + return $this->tokenManager->replace($value, $this->getEntity()); } else { return $default_value; @@ -2302,12 +2681,28 @@ public static function submitWebformSubmission(WebformSubmissionInterface $webfo // form is submitted. $form_state = new FormState(); + // Set the triggering element to an empty element to prevent + // errors from managed files. + // @see \Drupal\file\Element\ManagedFile::validateManagedFile + $form_state->setTriggeringElement(['#parents' => []]); + + // Get existing error messages. + $error_messages = \Drupal::messenger()->messagesByType(MessengerInterface::TYPE_ERROR); + // Submit the form. \Drupal::formBuilder()->submitForm($form_object, $form_state); // Get the errors but skip drafts. $errors = ($webform_submission->isDraft() && !$validate_only) ? [] : $form_state->getErrors(); + // Delete all form related error messages. + \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_ERROR); + + // Restore existing error message. + foreach ($error_messages as $error_message) { + \Drupal::messenger()->addError($error_message); + } + if ($errors) { return $errors; } diff --git a/web/modules/webform/src/WebformSubmissionGenerate.php b/web/modules/webform/src/WebformSubmissionGenerate.php index 62c1c313f1..265c787c9b 100644 --- a/web/modules/webform/src/WebformSubmissionGenerate.php +++ b/web/modules/webform/src/WebformSubmissionGenerate.php @@ -2,7 +2,6 @@ namespace Drupal\webform; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\OptGroup; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Config\ConfigFactoryInterface; @@ -116,9 +115,19 @@ public function getTestValue(WebformInterface $webform, $name, array $element, a } // Apply #maxlength to values. + // @see \Drupal\webform\Plugin\WebformElement\TextBase if (!empty($element['#maxlength'])) { + $maxlength = $element['#maxlength']; + } + elseif (!empty($element['#counter_type']) && !empty($element['#counter_maximum']) && $element['#counter_type'] === 'character') { + $maxlength = $element['#counter_maximum']; + } + else { + $maxlength = NULL; + } + if ($maxlength) { foreach ($values as $index => $value) { - $values[$index] = Unicode::substr($value, 0, $element['#maxlength']); + $values[$index] = mb_substr($value, 0, $maxlength); } } @@ -170,11 +179,6 @@ protected function getTestValues(WebformInterface $webform, $name, array $elemen return $element['#test']; } - // Never populate hidden and value elements. - if (in_array($element['#type'], ['hidden', 'value'])) { - return NULL; - } - // Invoke WebformElement::test and get a test value. // If test value is NULL this element should never be populated with // test data. diff --git a/web/modules/webform/src/WebformSubmissionInterface.php b/web/modules/webform/src/WebformSubmissionInterface.php index 45a5796f50..1cc0d5679d 100644 --- a/web/modules/webform/src/WebformSubmissionInterface.php +++ b/web/modules/webform/src/WebformSubmissionInterface.php @@ -2,6 +2,7 @@ namespace Drupal\webform; +use Drupal\Core\Session\AccountInterface; use Drupal\user\EntityOwnerInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\user\UserInterface; @@ -239,6 +240,17 @@ public function isCompleted(); */ public function isSticky(); + /** + * Test whether the provided account is owner of this webform submission. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Account whose ownership to test. + * + * @return bool + * Whether the provided account is owner of this webform submission. + */ + public function isOwner(AccountInterface $account); + /** * Checks submission notes. * @@ -334,10 +346,13 @@ public function getWebform(); /** * Gets the webform submission's source entity. * + * @param bool $translate + * (optional) If TRUE the source entity will be translated. + * * @return \Drupal\Core\Entity\EntityInterface|null * The entity that this webform submission was created from. */ - public function getSourceEntity(); + public function getSourceEntity($translate = FALSE); /** * Gets the webform submission's source URL. @@ -351,7 +366,7 @@ public function getSourceUrl(); * Gets the webform submission's secure tokenized URL. * * @return \Drupal\Core\Url - * The the webform submission's secure tokenized URL. + * The webform submission's secure tokenized URL. */ public function getTokenUrl(); @@ -372,7 +387,7 @@ public function invokeWebformHandlers($method); public function invokeWebformElements($method); /** - * Convert anonymous submission to authenicated. + * Convert anonymous submission to authenticated. * * @param \Drupal\user\UserInterface $account * An authenticated user account. diff --git a/web/modules/webform/src/WebformSubmissionListBuilder.php b/web/modules/webform/src/WebformSubmissionListBuilder.php index 600a164b9e..4e05ee416d 100644 --- a/web/modules/webform/src/WebformSubmissionListBuilder.php +++ b/web/modules/webform/src/WebformSubmissionListBuilder.php @@ -3,14 +3,26 @@ namespace Drupal\webform; use Drupal\Component\Render\FormattableMarkup; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Database; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityListBuilder; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Link; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Drupal\views\Views; +use Drupal\webform\Controller\WebformSubmissionController; +use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformDialogHelper; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Provides a list controller for webform submission entity. @@ -49,6 +61,41 @@ class WebformSubmissionListBuilder extends EntityListBuilder { */ const STATE_DRAFT = 'draft'; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * The current request. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + /** * The webform request handler. * @@ -56,6 +103,13 @@ class WebformSubmissionListBuilder extends EntityListBuilder { */ protected $requestHandler; + /** + * The webform element manager. + * + * @var \Drupal\webform\Plugin\WebformElementManagerInterface + */ + protected $elementManager; + /** * The webform message manager. * @@ -84,6 +138,14 @@ class WebformSubmissionListBuilder extends EntityListBuilder { */ protected $account; + /** + * Draft flag. + * + * @var bool + */ + protected $draft; + + /** * The columns being displayed. * @@ -108,6 +170,13 @@ class WebformSubmissionListBuilder extends EntityListBuilder { 'element_format' => 'value', ]; + /** + * The submission link type. (canonical, table, or edit) + * + * @var array + */ + protected $linkType = 'canonical'; + /** * The webform elements. * @@ -129,6 +198,13 @@ class WebformSubmissionListBuilder extends EntityListBuilder { */ protected $sort; + /** + * Search source entity. + * + * @var string + */ + protected $sourceEntityTypeId; + /** * Sort direction. * @@ -158,99 +234,194 @@ class WebformSubmissionListBuilder extends EntityListBuilder { protected $customize; /** - * The webform element manager. + * The name of current submission view. * - * @var \Drupal\webform\Plugin\WebformElementManagerInterface + * @var string */ - protected $elementManager; + protected $submissionView; + + /** + * An associative array of submission views. + * + * @var string + */ + protected $submissionViews; /** * {@inheritdoc} */ - public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage) { + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getStorage($entity_type->id()), + $container->get('entity.manager'), + $container->get('current_route_match'), + $container->get('request_stack'), + $container->get('current_user'), + $container->get('config.factory'), + $container->get('webform.request'), + $container->get('plugin.manager.webform.element'), + $container->get('webform.message_manager') + ); + } + + /** + * Constructs a new WebformSubmissionListBuilder object. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type definition. + * @param \Drupal\Core\Entity\EntityStorageInterface $storage + * The entity storage class. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The current route match. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory. + * @param \Drupal\webform\WebformRequestInterface $webform_request + * The webform request handler. + * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager + * The webform element manager. + * @param \Drupal\webform\WebformMessageManagerInterface $message_manager + * The webform message manager. + */ + public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $route_match, RequestStack $request_stack, AccountInterface $current_user, ConfigFactoryInterface $config_factory, WebformRequestInterface $webform_request, WebformElementManagerInterface $element_manager, WebformMessageManagerInterface $message_manager) { parent::__construct($entity_type, $storage); + $this->entityTypeManager = $entity_type_manager; + $this->routeMatch = $route_match; + $this->request = $request_stack->getCurrentRequest(); + $this->currentUser = $current_user; + $this->configFactory = $config_factory; + $this->requestHandler = $webform_request; + $this->elementManager = $element_manager; + $this->messageManager = $message_manager; + + $this->keys = $this->request->query->get('search'); + $this->state = $this->request->query->get('state'); + $this->sourceEntityTypeId = $this->request->query->get('entity'); - $this->requestHandler = \Drupal::service('webform.request'); + list($this->webform, $this->sourceEntity) = $this->requestHandler->getWebformEntities(); - $this->keys = \Drupal::request()->query->get('search'); - $this->state = \Drupal::request()->query->get('state'); + $this->messageManager->setWebform($this->webform); + $this->messageManager->setSourceEntity($this->sourceEntity); - list($this->webform, $this->sourceEntity) = $this->requestHandler->getWebformEntities(); + /** @var WebformSubmissionStorageInterface $webform_submission_storage */ + $webform_submission_storage = $this->getStorage(); + $route_name = $this->routeMatch->getRouteName(); $base_route_name = ($this->webform) ? $this->requestHandler->getBaseRouteName($this->webform, $this->sourceEntity) : ''; - if (in_array(\Drupal::routeMatch()->getRouteName(), ["$base_route_name.webform.user.submissions", "$base_route_name.webform.user.drafts"])) { - $this->account = \Drupal::currentUser(); - // Set submission filter so that we can support user.submissions and - // user.drafts routes. - $this->state = (\Drupal::routeMatch()->getRouteName() === "$base_route_name.webform.user.submissions") ? self::STATE_COMPLETED : self::STATE_DRAFT; + + // Set account and state based on the current route. + switch ($route_name) { + case 'entity.webform_submission.user': + $this->account = $this->routeMatch->getParameter('user'); + break; + + case "$base_route_name.webform.user.submissions": + $this->account = $this->currentUser; + $this->draft = FALSE; + break; + + case "$base_route_name.webform.user.drafts": + $this->account = $this->currentUser; + $this->draft = TRUE; + break; + + default: + $this->account = NULL; + break; } - else { - $this->account = NULL; + + // Initialize submission views and view. + $this->submissionView = $this->routeMatch->getParameter('submission_view') ?: ''; + $this->submissionViews = $this->getSubmissionViews(); + if ($this->submissionViews && empty($this->submissionView) && $this->isSubmissionViewResultsReplaced()) { + $this->submissionView = WebformArrayHelper::getFirstKey($this->submissionViews); } - $this->elementManager = \Drupal::service('plugin.manager.webform.element'); + // Check submission view access. + if ($this->submissionView && !isset($this->submissionViews[$this->submissionView])) { + $submission_views = $this->getSubmissionViewsConfig(); + if (isset($submission_views[$this->submissionView])) { + throw new AccessDeniedHttpException(); + } + else { + throw new NotFoundHttpException(); + } + } - /** @var \Drupal\webform\WebformMessageManagerInterface $message_manager */ - $this->messageManager = \Drupal::service('webform.message_manager'); - $this->messageManager->setWebform($this->webform); - $this->messageManager->setSourceEntity($this->sourceEntity); + // If there is a submission view, we do not need to initialize + // the entity list. + if ($this->submissionView) { + return; + } - /** @var WebformSubmissionStorageInterface $webform_submission_storage */ - $webform_submission_storage = $this->getStorage(); + // Set default display settings. + $this->direction = 'desc'; + $this->limit = 20; + $this->customize = FALSE; + $this->sort = 'created'; + $this->total = $this->getTotal($this->keys, $this->state, $this->sourceEntityTypeId); - $route_name = \Drupal::routeMatch()->getRouteName(); - if ($route_name == "$base_route_name.webform.results_submissions") { - // Display submission properties and elements. + switch ($route_name) { + // Display webform submissions which includes properties and elements. // @see /admin/structure/webform/manage/{webform}/results/submissions // @see /node/{node}/webform/results/submissions - $this->columns = $webform_submission_storage->getCustomColumns($this->webform, $this->sourceEntity, $this->account, TRUE); - $this->sort = $webform_submission_storage->getCustomSetting('sort', 'serial', $this->webform, $this->sourceEntity); - $this->direction = $webform_submission_storage->getCustomSetting('direction', 'desc', $this->webform, $this->sourceEntity); - $this->limit = $webform_submission_storage->getCustomSetting('limit', 50, $this->webform, $this->sourceEntity); - $this->format = $webform_submission_storage->getCustomSetting('format', $this->format, $this->webform, $this->sourceEntity); - $this->customize = $this->webform->access('update'); - if ($this->format['element_format'] == 'raw') { - foreach ($this->columns as &$column) { - $column['format'] = 'raw'; - if (isset($column['element'])) { - $column['element']['#format'] = 'raw'; + case "$base_route_name.webform.results_submissions": + $this->columns = $webform_submission_storage->getCustomColumns($this->webform, $this->sourceEntity, $this->account, TRUE); + $this->sort = $webform_submission_storage->getCustomSetting('sort', 'created', $this->webform, $this->sourceEntity); + $this->direction = $webform_submission_storage->getCustomSetting('direction', 'desc', $this->webform, $this->sourceEntity); + $this->limit = $webform_submission_storage->getCustomSetting('limit', 20, $this->webform, $this->sourceEntity); + $this->format = $webform_submission_storage->getCustomSetting('format', $this->format, $this->webform, $this->sourceEntity); + $this->linkType = $webform_submission_storage->getCustomSetting('link_type', $this->linkType, $this->webform, $this->sourceEntity); + $this->customize = $this->webform->access('update'); + if ($this->format['element_format'] == 'raw') { + foreach ($this->columns as &$column) { + $column['format'] = 'raw'; + if (isset($column['element'])) { + $column['element']['#format'] = 'raw'; + } } } - } - } - else { - if ($route_name == 'entity.webform_submission.collection') { + break; + + // Display all submissions. + // @see /admin/structure/webform/submissions/manage + case 'entity.webform_submission.collection': // Display only submission properties. // @see /admin/structure/webform/submissions/manage - $this->columns = $webform_submission_storage->getDefaultColumns($this->webform, $this->sourceEntity, $this->account, FALSE); - // Replace serial with sid when showing results from all webforms. - unset($this->columns['serial']); - $this->columns = [ - 'sid' => [ - 'title' => $this->t('SID'), - 'name' => 'sid', - 'format' => 'value', - ], - ] + $this->columns; - $this->sort = 'sid'; - } - else { + $this->columns = $webform_submission_storage->getSubmissionsColumns(); + break; + + // Display user's submissions. + // @see /user/{user}/submissions + case 'entity.webform_submission.user': + $this->columns = $webform_submission_storage->getUsersSubmissionsColumns(); + break; + + // Display user's submissions. + // @see /webform/{webform}/submissions + // @see /webform/{webform}/drafts + // @see /node/{node}/webform/submissions + // @see /node/{node}/webform/drafts + case "$base_route_name.webform.user.drafts": + case "$base_route_name.webform.user.submissions": + default: $this->columns = $webform_submission_storage->getUserColumns($this->webform, $this->sourceEntity, $this->account, TRUE); - unset($this->columns['sid']); - $this->sort = 'serial'; - } - $this->direction = 'desc'; - $this->limit = 50; - $this->customize = FALSE; + break; } - - $this->total = $this->getTotal($this->keys, $this->state); } /** * {@inheritdoc} */ public function render() { + $build = []; + // Set user specific page title. if ($this->webform && $this->account) { $t_args = [ @@ -264,6 +435,9 @@ public function render() { $build['#title'] = $this->t('Submissions to %webform for %user', $t_args); } } + elseif ($this->account) { + $build['#title'] = $this->account->getDisplayName(); + } // Display warning when the webform has a submission but saving of results. // are disabled. @@ -271,47 +445,123 @@ public function render() { $this->messageManager->display(WebformMessageManagerInterface::FORM_SAVE_EXCEPTION, 'warning'); } - // Add the filter. - if (empty($this->account)) { - $state_options = [ - '' => $this->t('All [@total]', ['@total' => $this->getTotal(NULL, NULL)]), - self::STATE_STARRED => $this->t('Starred [@total]', ['@total' => $this->getTotal(NULL, self::STATE_STARRED)]), - self::STATE_UNSTARRED => $this->t('Unstarred [@total]', ['@total' => $this->getTotal(NULL, self::STATE_UNSTARRED)]), - self::STATE_LOCKED => $this->t('Locked [@total]', ['@total' => $this->getTotal(NULL, self::STATE_LOCKED)]), - self::STATE_UNLOCKED => $this->t('Unlocked [@total]', ['@total' => $this->getTotal(NULL, self::STATE_UNLOCKED)]), - ]; - // Add draft to state options. - if (!$this->webform || $this->webform->getSetting('draft') != WebformInterface::DRAFT_NONE) { - $state_options += [ - self::STATE_COMPLETED => $this->t('Completed [@total]', ['@total' => $this->getTotal(NULL, self::STATE_COMPLETED)]), - self::STATE_DRAFT => $this->t('Draft [@total]', ['@total' => $this->getTotal(NULL, self::STATE_DRAFT)]), - ]; + $build += $this->buildSubmissionViewsMenu(); + + if ($this->submissionView) { + $build += $this->buildSubmissionViews(); + } + else { + $build += $this->buildEntityList(); + } + + $build['#attached']['library'][] = 'webform/webform.admin'; + + return $build; + } + + /** + * Build the webform submission view. + * + * @return array + * A renderable array containing a submission view. + */ + protected function buildSubmissionViews() { + $settings = $this->submissionViews[$this->submissionView]; + + // Get view name and display id. + list($name, $display_id) = explode(':', $settings['view']); + + // Load the view and set custom property used to fix the exposed + // filter action. + // @see webform_form_views_exposed_form_alter() + $view = Views::getView($name); + $view->webform_submission_view = TRUE; + + // Get the current display or default arguments. + $displays = $view->storage->get('display'); + if (!empty($displays[$display_id]['display_options']['arguments'])) { + $display_arguments = $displays[$display_id]['display_options']['arguments']; + } + elseif (!empty($displays['default']['display_options']['arguments'])) { + $display_arguments = $displays['default']['display_options']['arguments']; + } + else { + $display_arguments = []; + } + + // Populate the views arguments. + $arguments = []; + foreach ($display_arguments as $argument_name => $display_argument) { + if ($display_argument['table'] !== 'webform_submission') { + $arguments[] = 'all'; + } + else { + switch ($argument_name) { + case 'webform_id': + $arguments[] = (isset($this->webform)) ? $this->webform->id() : 'all'; + break; + + case 'entity_type': + $arguments[] = (isset($this->sourceEntity)) ? $this->sourceEntity->getEntityTypeId() : 'all'; + break; + + case 'entity_id': + $arguments[] = (isset($this->sourceEntity)) ? $this->sourceEntity->id() : 'all'; + break; + + case 'uid': + $arguments[] = (isset($this->account)) ? $this->account->id() : 'all'; + break; + + case 'in_draft': + $arguments[] = (isset($this->draft)) ? ($this->draft ? '1' : '0') : 'all'; + break; + + default: + $arguments[] = 'all'; + break; + } } - $build['filter_form'] = \Drupal::formBuilder() - ->getForm('\Drupal\webform\Form\WebformSubmissionFilterForm', $this->keys, $this->state, $state_options); } - // Customize. + $build = []; + $build['view'] = [ + '#type' => 'view', + '#view' => $view, + '#display_id' => $display_id, + '#arguments' => $arguments, + ]; + return $build; + } + + /** + * Build the webform submission entity list. + * + * @return array + * A renderable array containing the entity list. + */ + protected function buildEntityList() { + $build = []; + + // Filter form. + if (empty($this->account)) { + $build['filter_form'] = $this->buildFilterForm(); + } + + // Customize buttons. if ($this->customize) { $build['custom_top'] = $this->buildCustomizeButton(); } // Display info. if ($this->total) { - if ($this->account && $this->state == self::STATE_DRAFT) { - $info = $this->formatPlural($this->total, '@total draft', '@total drafts', ['@total' => $this->total]); - } - else { - $info = $this->formatPlural($this->total, '@total submission', '@total submissions', ['@total' => $this->total]); - } - $build['info'] = [ - '#markup' => $info, - '#prefix' => '<div>', - '#suffix' => '</div>', - ]; + $build['info'] = $this->buildInfo(); } + // Table. $build += parent::render(); + $build['table']['#sticky'] = TRUE; + $build['table']['#attributes']['class'][] = 'webform-results-table'; // Customize. // Only displayed when more than 20 submissions are being displayed. @@ -322,16 +572,117 @@ public function render() { } } - $build['table']['#attributes']['class'][] = 'webform-results__table'; - - $build['#attached']['library'][] = 'webform/webform.admin'; - // Must preload libraries required by (modal) dialogs. WebformDialogHelper::attachLibraries($build); return $build; } + /** + * Build the submission views menu. + * + * @return array + * A render array representing the submission views menu. + */ + protected function buildSubmissionViewsMenu() { + if (empty($this->submissionViews)) { + return []; + } + + $route_name = $this->routeMatch->getRouteName(); + $route_parameters = $this->routeMatch->getRawParameters()->all(); + unset($route_parameters['submission_view']); + + $links = []; + if (!$this->isSubmissionViewResultsReplaced()) { + $links['_default_'] = [ + 'title' => $this->t('Submissions'), + 'url' => Url::fromRoute($route_name, $route_parameters), + ]; + } + foreach ($this->submissionViews as $name => $submission_view) { + $links[$name] = [ + 'title' => $submission_view['title'], + 'url' => Url::fromRoute($route_name, $route_parameters + ['submission_view' => $name]), + ]; + } + + // Only display the submission views menu when there is more than 1 link. + if (count($links) <= 1) { + return []; + } + + // Make sure the current submission view is first. + if ($this->submissionView) { + $links = [$this->submissionView => $links[$this->submissionView]] + $links; + } + + $build = []; + $build['submission_views'] = [ + '#type' => 'operations', + '#links' => $links, + '#prefix' => '<div class="webform-dropbutton webform-submission-views-dropbutton">', + '#suffix' => '</div>' . ($this->submissionView ? '<p><hr/></p>' : ''), + ]; + return $build; + } + + /** + * Build the filter form. + * + * @return array + * A render array representing the filter form. + */ + protected function buildFilterForm() { + // State options. + $state_options = [ + '' => $this->t('All [@total]', ['@total' => $this->getTotal(NULL, NULL, $this->sourceEntityTypeId)]), + self::STATE_STARRED => $this->t('Starred [@total]', ['@total' => $this->getTotal(NULL, self::STATE_STARRED, $this->sourceEntityTypeId)]), + self::STATE_UNSTARRED => $this->t('Unstarred [@total]', ['@total' => $this->getTotal(NULL, self::STATE_UNSTARRED, $this->sourceEntityTypeId)]), + self::STATE_LOCKED => $this->t('Locked [@total]', ['@total' => $this->getTotal(NULL, self::STATE_LOCKED, $this->sourceEntityTypeId)]), + self::STATE_UNLOCKED => $this->t('Unlocked [@total]', ['@total' => $this->getTotal(NULL, self::STATE_UNLOCKED, $this->sourceEntityTypeId)]), + ]; + // Add draft to state options. + if (!$this->webform || $this->webform->getSetting('draft') != WebformInterface::DRAFT_NONE) { + $state_options += [ + self::STATE_COMPLETED => $this->t('Completed [@total]', ['@total' => $this->getTotal(NULL, self::STATE_COMPLETED, $this->sourceEntityTypeId)]), + self::STATE_DRAFT => $this->t('Draft [@total]', ['@total' => $this->getTotal(NULL, self::STATE_DRAFT, $this->sourceEntityTypeId)]), + ]; + } + + // Source entity options. + if ($this->webform && !$this->sourceEntity) { + // < 100 source entities a select menuwill be displayed. + // > 100 source entities an autocomplete input will be displayed. + $source_entity_total = $this->storage->getSourceEntitiesTotal($this->webform); + if ($source_entity_total < 100) { + $source_entity_options = $this->storage->getSourceEntitiesAsOptions($this->webform); + $source_entity_default_value = $this->sourceEntityTypeId; + } + elseif ($this->sourceEntityTypeId && strpos($this->sourceEntityTypeId, ':') !== FALSE) { + $source_entity_options = $this->webform; + try { + list($source_entity_type, $source_entity_id) = explode(':', $this->sourceEntityTypeId); + $source_entity = $this->entityTypeManager->getStorage($source_entity_type)->load($source_entity_id); + $source_entity_default_value = $source_entity->label() . " ($source_entity_type:$source_entity_id)"; + } + catch (\Exception $exception) { + $source_entity_default_value = ''; + } + } + else { + $source_entity_options = $this->webform; + $source_entity_default_value = ''; + } + } + else { + $source_entity_options = NULL; + $source_entity_default_value = ''; + } + + return \Drupal::formBuilder()->getForm('\Drupal\webform\Form\WebformSubmissionFilterForm', $this->keys, $this->state, $state_options, $source_entity_default_value, $source_entity_options); + } + /** * Build the customize button. * @@ -340,7 +691,7 @@ public function render() { */ protected function buildCustomizeButton() { $route_name = $this->requestHandler->getRouteName($this->webform, $this->sourceEntity, 'webform.results_submissions.custom'); - $route_parameters = $this->requestHandler->getRouteParameters($this->webform, $this->sourceEntity) + ['webform' => $this->webform->id()]; + $route_parameters = $this->requestHandler->getRouteParameters($this->webform, $this->sourceEntity, $route_name) + ['webform' => $this->webform->id()]; return [ '#type' => 'link', '#title' => $this->t('Customize'), @@ -349,6 +700,26 @@ protected function buildCustomizeButton() { ]; } + /** + * Build information summary. + * + * @return array + * A render array representing the information summary. + */ + protected function buildInfo() { + if ($this->account && $this->state == self::STATE_DRAFT) { + $info = $this->formatPlural($this->total, '@total draft', '@total drafts', ['@total' => $this->total]); + } + else { + $info = $this->formatPlural($this->total, '@total submission', '@total submissions', ['@total' => $this->total]); + } + return [ + '#markup' => $info, + '#prefix' => '<div>', + '#suffix' => '</div>', + ]; + } + /****************************************************************************/ // Header functions. /****************************************************************************/ @@ -413,8 +784,8 @@ protected function buildHeaderColumn(array $column) { case 'sticky': case 'locked': return [ - 'data' => new FormattableMarkup('<span class="webform-icon webform-icon-@name webform-icon-@name--link"></span>', ['@name' => $name]), - 'class' => ['webform-results__icon'], + 'data' => new FormattableMarkup('<span class="webform-icon webform-icon-@name webform-icon-@name--link"></span><span class="visually-hidden">@title</span> ', ['@name' => $name, '@title' => $title]), + 'class' => ['webform-results-table__icon'], 'field' => $name, 'specifier' => $name, ]; @@ -468,6 +839,8 @@ public function buildRow(EntityInterface $entity) { * Throw exception if table row column is not found. */ public function buildRowColumn(array $column, EntityInterface $entity) { + /** @var $entity \Drupal\webform\WebformSubmissionInterface */ + $is_raw = ($column['format'] == 'raw'); $name = $column['name']; @@ -475,14 +848,14 @@ public function buildRowColumn(array $column, EntityInterface $entity) { case 'created': case 'completed': case 'changed': - return ($is_raw) ? $entity->created->value : $entity->created->value ? \Drupal::service('date.formatter')->format($entity->created->value) : ''; + return ($is_raw) ? $entity->{$name}->value : $entity->{$name}->value ? \Drupal::service('date.formatter')->format($entity->{$name}->value) : ''; case 'entity': $source_entity = $entity->getSourceEntity(); if (!$source_entity) { return ''; } - return ($is_raw) ? $source_entity->getEntityTypeId . ':' . $source_entity->id() : ($source_entity->hasLinkTemplate('canonical') ? $source_entity->toLink() : ''); + return ($is_raw) ? $source_entity->getEntityTypeId . ':' . $source_entity->id() : ($source_entity->hasLinkTemplate('canonical') ? $source_entity->toLink() : $source_entity->label()); case 'langcode': $langcode = $entity->langcode->value; @@ -500,14 +873,16 @@ public function buildRowColumn(array $column, EntityInterface $entity) { case 'notes': $notes_url = $this->ensureDestination($this->requestHandler->getUrl($entity, $entity->getSourceEntity(), 'webform_submission.notes_form')); $state = $entity->get('notes')->value ? 'on' : 'off'; + $t_args = ['@label' => $entity->label()]; + $label = $entity->get('notes')->value ? $this->t('Edit @label notes', $t_args) : $this->t('Add notes to @label', $t_args); return [ 'data' => [ '#type' => 'link', - '#title' => new FormattableMarkup('<span class="webform-icon webform-icon-notes webform-icon-notes--@state"></span>', ['@state' => $state]), + '#title' => new FormattableMarkup('<span class="webform-icon webform-icon-notes webform-icon-notes--@state"></span><span class="visually-hidden">@label</span>', ['@state' => $state, '@label' => $label]), '#url' => $notes_url, - '#attributes' => WebformDialogHelper::getOffCanvasDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), ], - 'class' => ['webform-results__icon'], + 'class' => ['webform-results-table__icon'], ]; case 'operations': @@ -521,62 +896,66 @@ public function buildRowColumn(array $column, EntityInterface $entity) { case 'serial': case 'label': - // Note: Using source entity associate with the submission and not - // the current webform. + // Note: Use the submission's token URL which points to the + // submission's source URL with a secure token. + // @see \Drupal\webform\Entity\WebformSubmission::getTokenUrl if ($entity->isDraft()) { - if ($entity->getSourceEntity() && $entity->getSourceEntity()->hasLinkTemplate('canonical')) { - $link_url = $entity->getSourceEntity()->toUrl('canonical', ['query' => ['token' => $entity->getToken()]]); - } - else { - $link_url = $this->webform->toUrl('canonical', ['query' => ['token' => $entity->getToken()]]); - } + $link_url = $entity->getTokenUrl(); } else { $link_url = $this->requestHandler->getUrl($entity, $entity->getSourceEntity(), $this->getSubmissionRouteName()); } if ($name == 'serial') { - $link_text = $entity->serial() . ($entity->isDraft() ? ' (' . $this->t('draft') . ')' : ''); + $link_text = $entity->serial(); } else { $link_text = $entity->label(); } - return Link::fromTextAndUrl($link_text, $link_url); + $link = Link::fromTextAndUrl($link_text, $link_url)->toRenderable(); + if ($name == 'serial') { + $link['#attributes']['title'] = $entity->label(); + $link['#attributes']['aria-label'] = $entity->label(); + } + if ($entity->isDraft()) { + $link['#suffix'] = ' (' . $this->t('draft') . ')'; + } + return ['data' => $link]; case 'in_draft': return ($entity->isDraft()) ? $this->t('Yes') : $this->t('No'); case 'sticky': + // @see \Drupal\webform\Controller\WebformSubmissionController::sticky $route_name = 'entity.webform_submission.sticky_toggle'; $route_parameters = ['webform' => $entity->getWebform()->id(), 'webform_submission' => $entity->id()]; - $state = $entity->isSticky() ? 'on' : 'off'; return [ 'data' => [ '#type' => 'link', - '#title' => new FormattableMarkup('<span class="webform-icon webform-icon-sticky webform-icon-sticky--@state"></span>', ['@state' => $state]), + '#title' => WebformSubmissionController::buildSticky($entity), '#url' => Url::fromRoute($route_name, $route_parameters), '#attributes' => [ 'id' => 'webform-submission-' . $entity->id() . '-sticky', 'class' => ['use-ajax'], ], ], - 'class' => ['webform-results__icon'], + 'class' => ['webform-results-table__icon'], ]; case 'locked': + // @see \Drupal\webform\Controller\WebformSubmissionController::locked $route_name = 'entity.webform_submission.locked_toggle'; $route_parameters = ['webform' => $entity->getWebform()->id(), 'webform_submission' => $entity->id()]; - $state = $entity->isLocked() ? 'on' : 'off'; return [ 'data' => [ '#type' => 'link', - '#title' => new FormattableMarkup('<span class="webform-icon webform-icon-lock webform-icon-locked--@state"></span>', ['@state' => $state]), + '#title' => WebformSubmissionController::buildLocked($entity), '#url' => Url::fromRoute($route_name, $route_parameters), '#attributes' => [ 'id' => 'webform-submission-' . $entity->id() . '-locked', 'class' => ['use-ajax'], ], ], - 'class' => ['webform-results__icon'], + 'class' => ['webform-results-table__icon'], ]; case 'uid': @@ -613,9 +992,9 @@ public function buildRowColumn(array $column, EntityInterface $entity) { */ public function buildOperations(EntityInterface $entity) { return parent::buildOperations($entity) + [ - '#prefix' => '<div class="webform-dropbutton">', - '#suffix' => '</div>', - ]; + '#prefix' => '<div class="webform-dropbutton">', + '#suffix' => '</div>', + ]; } /** @@ -627,59 +1006,99 @@ public function getDefaultOperations(EntityInterface $entity) { $operations = []; - if ($entity->access('update')) { - $operations['edit'] = [ - 'title' => $this->t('Edit'), - 'weight' => 10, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.edit_form'), - ]; - } + if ($this->account) { + if ($entity->access('update')) { + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'weight' => 10, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform.user.submission.edit'), + ]; + } - if ($entity->access('view')) { - $operations['view'] = [ - 'title' => $this->t('View'), - 'weight' => 20, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.canonical'), - ]; - } + if ($entity->access('view')) { + $operations['view'] = [ + 'title' => $this->t('View'), + 'weight' => 20, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform.user.submission'), + ]; + } - if ($entity->access('notes')) { - $operations['notes'] = [ - 'title' => $this->t('Notes'), - 'weight' => 21, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.notes_form'), - ]; - } + if ($entity->access('create') && $webform->getSetting('submission_user_duplicate')) { + $operations['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'weight' => 23, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform.user.submission.duplicate'), + ]; + } - if ($webform->access('submission_update_any') && $webform->hasMessageHandler()) { - $operations['resend'] = [ - 'title' => $this->t('Resend'), - 'weight' => 22, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.resend_form'), - ]; - } - if ($webform->access('submission_update_any')) { - $operations['duplicate'] = [ - 'title' => $this->t('Duplicate'), - 'weight' => 23, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.duplicate_form'), - ]; + if ($entity->access('delete')) { + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'weight' => 100, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform.user.submission.delete'), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; + } } + else { + if ($entity->access('update')) { + $operations['edit'] = [ + 'title' => $this->t('Edit'), + 'weight' => 10, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.edit_form'), + ]; + } - if ($entity->access('delete')) { - $operations['delete'] = [ - 'title' => $this->t('Delete'), - 'weight' => 100, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.delete_form'), - ]; - } + if ($entity->access('view')) { + $operations['view'] = [ + 'title' => $this->t('View'), + 'weight' => 20, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.canonical'), + ]; + } - if ($entity->access('view_any') && \Drupal::currentUser()->hasPermission('access webform submission log')) { - $operations['log'] = [ - 'title' => $this->t('Log'), - 'weight' => 100, - 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.log'), - ]; + if ($entity->access('notes')) { + $operations['notes'] = [ + 'title' => $this->t('Notes'), + 'weight' => 21, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.notes_form'), + ]; + } + + if ($webform->access('submission_update_any') && $webform->hasMessageHandler()) { + $operations['resend'] = [ + 'title' => $this->t('Resend'), + 'weight' => 22, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.resend_form'), + ]; + } + if ($webform->access('submission_update_any')) { + $operations['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'weight' => 23, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.duplicate_form'), + ]; + } + + if ($entity->access('delete')) { + $operations['delete'] = [ + 'title' => $this->t('Delete'), + 'weight' => 100, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.delete_form'), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(WebformDialogHelper::DIALOG_NARROW), + ]; + } + + if ($entity->access('view_any') + && $this->currentUser->hasPermission('access webform submission log') + && $webform->hasSubmissionLog() + && $this->moduleHandler->moduleExists('webform_submission_log')) { + $operations['log'] = [ + 'title' => $this->t('Log'), + 'weight' => 100, + 'url' => $this->requestHandler->getUrl($entity, $this->sourceEntity, 'webform_submission.log'), + ]; + } } // Add destination to all operation links. @@ -702,7 +1121,7 @@ public function getDefaultOperations(EntityInterface $entity) { * or 'webform_submission.canonical. */ protected function getSubmissionRouteName() { - return (strpos(\Drupal::routeMatch()->getRouteName(), 'webform.user.submissions') !== FALSE) ? 'webform.user.submission' : 'webform_submission.canonical'; + return (strpos($this->routeMatch->getRouteName(), 'webform.user.submissions') !== FALSE) ? 'webform.user.submission' : 'webform_submission.' . $this->linkType; } /** @@ -732,6 +1151,112 @@ protected function getRouteParameters(WebformSubmissionInterface $webform_submis return $route_parameters; } + /****************************************************************************/ + // Submission views functions. + /****************************************************************************/ + + /** + * Get the submission view type for the current route. + * + * @return string + * The submission view type for the current route. + */ + protected function getSubmissionViewType() { + if (!$this->webform) { + return 'global'; + } + elseif ($this->sourceEntity && $this->sourceEntity->getEntityTypeId() === 'node') { + return 'node'; + } + else { + return 'webform'; + } + } + + /** + * Determine if the submission view(s) replaced the default results table. + * + * @return bool + * TRUE if the submission view(s) replaced the default results table. + */ + protected function isSubmissionViewResultsReplaced() { + $type = $this->getSubmissionViewType(); + + $replace_routes = []; + + $default_replace = $this->configFactory->get('webform.settings')->get('settings.default_submission_views_replace') ?: []; + if (isset($default_replace[$type . '_routes'])) { + $replace_routes = array_merge($replace_routes, $default_replace[$type . '_routes']); + } + + if ($this->webform) { + $webform_replace = $this->webform->getSetting('submission_views_replace') ?: []; + if (isset($webform_replace[$type . '_routes'])) { + $replace_routes = array_merge($replace_routes, $webform_replace[$type . '_routes']); + } + } + + $route_name = $this->routeMatch->getRouteName(); + return ($replace_routes && in_array($route_name, $replace_routes)) ? TRUE : FALSE; + } + + /** + * Get all submission views applicable. + * + * @return array + * An associative array of all submission views. + */ + protected function getSubmissionViewsConfig() { + // Merge webform submission views with global submission views. + $submission_views = []; + if ($this->webform) { + $submission_views += $this->webform->getSetting('submission_views') ?: []; + } + $submission_views += $this->configFactory->get('webform.settings')->get('settings.default_submission_views') ?: []; + return $submission_views; + } + + /** + * Get submission views applicable for the current route and user. + * + * @return array + * An associative array of submission views applicable for the + * current route and user. + */ + protected function getSubmissionViews() { + if (!$this->moduleHandler()->moduleExists('views')) { + return []; + } + + $type = $this->getSubmissionViewType(); + $route_name = $this->routeMatch->getRouteName(); + + $submission_views = $this->getSubmissionViewsConfig(); + foreach ($submission_views as $name => $submission_view) { + $submission_view += [ + 'global_routes' => [], + 'webform_routes' => [], + 'node_routes' => [], + ]; + + // Check global, webform, or node routes. + $routes = $submission_view[$type . '_routes']; + if (empty($routes) || !in_array($route_name, $routes)) { + unset($submission_views[$name]); + continue; + } + + list($view_name, $view_display_id) = explode(':', $submission_view['view']); + $view = Views::getView($view_name); + if (!$view || !$view->access($view_display_id)) { + unset($submission_views[$name]); + continue; + } + } + + return $submission_views; + } + /****************************************************************************/ // Query functions. /****************************************************************************/ @@ -740,7 +1265,7 @@ protected function getRouteParameters(WebformSubmissionInterface $webform_submis * {@inheritdoc} */ protected function getEntityIds() { - $query = $this->getQuery($this->keys, $this->state); + $query = $this->getQuery($this->keys, $this->state, $this->sourceEntityTypeId); $query->pager($this->limit); $header = $this->buildHeader(); @@ -748,14 +1273,15 @@ protected function getEntityIds() { $direction = tablesort_get_sort($header); // If query is order(ed) by 'element__*' we need to build a custom table - // sort using hook_query_alter(). - // @see: webform_query_alter() + // sort using hook_query_TAG_alter(). + // @see webform_query_webform_submission_list_builder_alter() if ($order && strpos($order['sql'], 'element__') === 0) { $name = $order['sql']; $column = $this->columns[$name]; - $query->addMetaData('webform_submission_element_name', $column['key']); - $query->addMetaData('webform_submission_element_property_name', $column['property_name']); - $query->addMetaData('webform_submission_element_direction', $direction); + $query->addTag('webform_submission_list_builder') + ->addMetaData('webform_submission_element_name', $column['key']) + ->addMetaData('webform_submission_element_property_name', $column['property_name']) + ->addMetaData('webform_submission_element_direction', $direction); $result = $query->execute(); // Must manually initialize the pager because the DISTINCT clause in the // query is breaking the row counting. @@ -764,7 +1290,7 @@ protected function getEntityIds() { return $result; } else { - $order = \Drupal::request()->query->get('order', ''); + $order = $this->request->query->get('order', ''); if ($order) { $query->tableSort($header); } @@ -792,12 +1318,14 @@ protected function getEntityIds() { * (optional) Search key. * @param string $state * (optional) Submission state. + * @param string $source_entity + * (optional) Source entity (type:id). * * @return int * The total number of submissions. */ - protected function getTotal($keys = '', $state = '') { - return $this->getQuery($keys, $state) + protected function getTotal($keys = '', $state = '', $source_entity = '') { + return $this->getQuery($keys, $state, $source_entity) ->count() ->execute(); } @@ -809,11 +1337,13 @@ protected function getTotal($keys = '', $state = '') { * (optional) Search key. * @param string $state * (optional) Submission state. + * @param string $source_entity + * (optional) Source entity (type:id). * * @return \Drupal\Core\Entity\Query\QueryInterface * An entity query. */ - protected function getQuery($keys = '', $state = '') { + protected function getQuery($keys = '', $state = '', $source_entity = '') { /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ $submission_storage = $this->getStorage(); $query = $submission_storage->getQuery(); @@ -828,14 +1358,17 @@ protected function getQuery($keys = '', $state = '') { $submission_storage->addQueryConditions($sub_query, $this->webform); // Search UUID and Notes. + $or_condition = $query->orConditionGroup(); + $or_condition->condition('notes', '%' . $keys . '%', 'LIKE'); + // Only search UUID if keys is alphanumeric with dashes. + // @see Issue #2978420: Error SQL with accent mark submissions filter. + if (preg_match('/^[0-9a-z-]+$/', $keys)) { + $or_condition->condition('uuid', $keys); + } $query->condition( $query->orConditionGroup() ->condition('sid', $sub_query, 'IN') - ->condition( - $query->orConditionGroup() - ->condition('uuid', $keys) - ->condition('notes', '%' . $keys . '%', 'LIKE') - ) + ->condition($or_condition) ); } @@ -866,25 +1399,19 @@ protected function getQuery($keys = '', $state = '') { break; } - return $query; - } + // Filter by source entity. + if ($source_entity && strpos($source_entity, ':') !== FALSE) { + list($entity_type, $entity_id) = explode(':', $source_entity); + $query->condition('entity_type', $entity_type); + $query->condition('entity_id', $entity_id); + } - /****************************************************************************/ - // Backport of Drupal 8.5.x+ methods. - // @todo Remove once only Drupal 8.5.x+ is supported. - /****************************************************************************/ + // Filter by draft. (Only applies to user submissions and drafts) + if (isset($this->draft)) { + $query->condition('in_draft', $this->draft); + } - /** - * Ensures that a destination is present on the given URL. - * - * @param \Drupal\Core\Url $url - * The URL object to which the destination should be added. - * - * @return \Drupal\Core\Url - * The updated URL object. - */ - protected function ensureDestination(Url $url) { - return $url->mergeOptions(['query' => \Drupal::destination()->getAsArray()]); + return $query; } } diff --git a/web/modules/webform/src/WebformSubmissionNotesForm.php b/web/modules/webform/src/WebformSubmissionNotesForm.php index 206862224b..748dbfc3e8 100644 --- a/web/modules/webform/src/WebformSubmissionNotesForm.php +++ b/web/modules/webform/src/WebformSubmissionNotesForm.php @@ -29,8 +29,8 @@ class WebformSubmissionNotesForm extends ContentEntityForm { * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. - * @param Drupal\webform\WebformRequestInterface $webform_request - * The webform request. + * @param \Drupal\webform\WebformRequestInterface $webform_request + * The webform request handler. * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * The entity type bundle service. * @param \Drupal\Component\Datetime\TimeInterface $time @@ -64,12 +64,12 @@ public function form(array $form, FormStateInterface $form_state) { list($webform_submission, $source_entity) = $this->requestHandler->getWebformSubmissionEntities(); $form['navigation'] = [ - '#theme' => 'webform_submission_navigation', + '#type' => 'webform_submission_navigation', '#webform_submission' => $webform_submission, '#access' => $this->isDialog() ? FALSE : TRUE, ]; $form['information'] = [ - '#theme' => 'webform_submission_information', + '#type' => 'webform_submission_information', '#webform_submission' => $webform_submission, '#source_entity' => $source_entity, '#access' => $this->isDialog() ? FALSE : TRUE, @@ -107,6 +107,7 @@ public function form(array $form, FormStateInterface $form_state) { ], '#required' => TRUE, '#default_value' => $webform_submission->getOwner(), + '#access' => $this->currentUser()->hasPermission('administer users'), ]; $form['#attached']['library'][] = 'webform/webform.admin'; @@ -136,7 +137,7 @@ protected function actions(array $form, FormStateInterface $form_state) { */ public function save(array $form, FormStateInterface $form_state) { parent::save($form, $form_state); - drupal_set_message($this->t('Submission @sid notes saved.', ['@sid' => '#' . $this->entity->id()])); + $this->messenger()->addStatus($this->t('Submission @sid notes saved.', ['@sid' => '#' . $this->entity->id()])); } /** diff --git a/web/modules/webform/src/WebformSubmissionStorage.php b/web/modules/webform/src/WebformSubmissionStorage.php index 498d43c9eb..71687e7450 100644 --- a/web/modules/webform/src/WebformSubmissionStorage.php +++ b/web/modules/webform/src/WebformSubmissionStorage.php @@ -2,6 +2,7 @@ namespace Drupal\webform; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\AlterableInterface; @@ -19,6 +20,7 @@ use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\user\Entity\User; use Drupal\user\UserInterface; +use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -33,6 +35,13 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor */ protected $elementDataSchema = []; + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + /** * The current user. * @@ -40,13 +49,24 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor */ protected $currentUser; + /** + * Webform access rules manager service. + * + * @var \Drupal\webform\WebformAccessRulesManagerInterface + */ + protected $accessRulesManager; + /** * WebformSubmissionStorage constructor. + * + * @todo Webform 8.x-6.x: Move $time before $access_rules_manager. */ - public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccountProxyInterface $current_user) { + public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, AccountProxyInterface $current_user, WebformAccessRulesManagerInterface $access_rules_manager, TimeInterface $time = NULL) { parent::__construct($entity_type, $database, $entity_manager, $cache, $language_manager); $this->currentUser = $current_user; + $this->accessRulesManager = $access_rules_manager; + $this->time = $time; } /** @@ -59,7 +79,9 @@ public static function createInstance(ContainerInterface $container, EntityTypeI $container->get('entity.manager'), $container->get('cache.entity'), $container->get('language_manager'), - $container->get('current_user') + $container->get('current_user'), + $container->get('webform.access_rules_manager'), + $container->get('datetime.time') ); } @@ -95,7 +117,7 @@ public function getFieldDefinitions() { * {@inheritdoc} */ public function checkFieldDefinitionAccess(WebformInterface $webform, array $definitions) { - if (!$webform->access('submission_upates_any')) { + if (!$webform->access('submission_update_any')) { unset($definitions['token']); } return $definitions; @@ -117,9 +139,9 @@ protected function doCreate(array $values) { /** * {@inheritdoc} */ - public function loadMultiple(array $ids = NULL) { + protected function doLoadMultiple(array $ids = NULL) { /** @var \Drupal\webform\WebformSubmissionInterface[] $webform_submissions */ - $webform_submissions = parent::loadMultiple($ids); + $webform_submissions = parent::doLoadMultiple($ids); $this->loadData($webform_submissions); return $webform_submissions; } @@ -191,8 +213,10 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value // Add account query wheneven filter by uid. if (isset($values['uid'])) { $account = User::load($values['uid']); - $this->addQueryConditions($entity_query, NULL, NULL, $account); - unset($values['uid']); + if ($account instanceof UserInterface) { + $this->addQueryConditions($entity_query, NULL, NULL, $account); + unset($values['uid']); + } } parent::buildPropertyQuery($entity_query, $values); @@ -203,6 +227,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value */ public function deleteAll(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, $limit = NULL, $max_sid = NULL) { $query = $this->getQuery(); + $query->accessCheck(FALSE); $this->addQueryConditions($query, $webform, $source_entity, NULL); if ($max_sid) { $query->condition('sid', $max_sid, '<='); @@ -228,6 +253,7 @@ public function getTotal(WebformInterface $webform = NULL, EntityInterface $sour ]; $query = $this->getQuery(); + $query->accessCheck(FALSE); $this->addQueryConditions($query, $webform, $source_entity, $account, $options); // Issue: Query count method is not working for SQL Lite. @@ -241,6 +267,7 @@ public function getTotal(WebformInterface $webform = NULL, EntityInterface $sour */ public function getMaxSubmissionId(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { $query = $this->getQuery(); + $query->accessCheck(FALSE); $this->addQueryConditions($query, $webform, $source_entity, $account); $query->sort('sid', 'DESC'); $query->range(0, 1); @@ -262,6 +289,69 @@ public function hasSubmissionValue(WebformInterface $webform, $element_key) { return $result->fetchAssoc() ? TRUE : FALSE; } + /****************************************************************************/ + // Source entity methods. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function getSourceEntitiesTotal(WebformInterface $webform) { + $query = $this->database->select('webform_submission', 's') + ->fields('s', ['entity_type', 'entity_id']) + ->condition('webform_id', $webform->id()) + ->condition('entity_type', '', '<>') + ->isNotNull('entity_type') + ->condition('entity_id', '', '<>') + ->isNotNull('entity_id') + ->distinct(); + return (int) $query->countQuery()->execute()->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function getSourceEntities(WebformInterface $webform) { + /** @var \Drupal\Core\Database\StatementInterface $result */ + $result = $this->database->select('webform_submission', 's') + ->fields('s', ['entity_type', 'entity_id']) + ->condition('webform_id', $webform->id()) + ->condition('entity_type', '', '<>') + ->isNotNull('entity_type') + ->condition('entity_id', '', '<>') + ->isNotNull('entity_id') + ->distinct() + ->execute(); + $source_entities = []; + while ($record = $result->fetchAssoc()) { + $source_entities[$record['entity_type']][$record['entity_id']] = $record['entity_id']; + } + return $source_entities; + } + + /** + * {@inheritdoc} + */ + public function getSourceEntitiesAsOptions(WebformInterface $webform) { + $options = []; + $source_entities = $this->getSourceEntities($webform); + foreach ($source_entities as $entity_type => $entity_ids) { + $optgroup = (string) $this->entityManager->getDefinition($entity_type)->getCollectionLabel(); + $entities = $this->entityManager->getStorage($entity_type)->loadMultiple($entity_ids); + foreach ($entities as $entity_id => $entity) { + if ($entity instanceof TranslatableInterface && $entity->hasTranslation($this->languageManager->getCurrentLanguage()->getId())) { + $entity = $entity->getTranslation($this->languageManager->getCurrentLanguage()->getId()); + } + + $option_value = "$entity_type:$entity_id"; + $option_text = $entity->label(); + $options[$optgroup][$option_value] = $option_text; + } + asort($options[$optgroup]); + } + return (count($options) === 1) ? reset($options) : $options; + } + /****************************************************************************/ // Query methods. /****************************************************************************/ @@ -439,27 +529,6 @@ protected function getSiblingSubmission(WebformSubmissionInterface $webform_subm // WebformSubmissionEntityList methods. /****************************************************************************/ - /** - * Get specified columns in specified order. - * - * @param array $column_names - * An associative array of column names. - * @param array $columns - * An associative array containing all available columns. - * - * @return array - * An associative array containing all specified columns. - */ - protected function filterColumns(array $column_names, array $columns) { - $filtered_columns = []; - foreach ($column_names as $column_name) { - if (isset($columns[$column_name])) { - $filtered_columns[$column_name] = $columns[$column_name]; - } - } - return $filtered_columns; - } - /** * {@inheritdoc} */ @@ -491,35 +560,88 @@ public function getUserColumns(WebformInterface $webform = NULL, EntityInterface $column_names = ($webform) ? $webform->getSetting('submission_user_columns', []) : []; $column_names = $column_names ?: $this->getUserDefaultColumnNames($webform, $source_entity, $account, $include_elements); $columns = $this->getColumns($webform, $source_entity, $account, $include_elements); + unset($columns['sid']); return $this->filterColumns($column_names, $columns); } /** * {@inheritdoc} */ - public function getUserDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { - return ['serial', 'created', 'remote_addr']; + public function getDefaultColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { + $columns = $this->getColumns($webform, $source_entity, $account, $include_elements); + + // Unset columns. + unset( + // Admin columns. + $columns['sid'], + $columns['label'], + $columns['uuid'], + $columns['in_draft'], + $columns['completed'], + $columns['changed'] + ); + + // Hide certain unnecessary columns, that have default set to FALSE. + foreach ($columns as $column_name => $column) { + if (isset($column['default']) && $column['default'] === FALSE) { + unset($columns[$column_name]); + } + } + + return $columns; } /** * {@inheritdoc} */ - public function getDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { - $columns = $this->getDefaultColumns($webform, $source_entity, $account, $include_elements); - return array_keys($columns); + public function getSubmissionsColumns() { + $columns = $this->getColumns(NULL, NULL, NULL, FALSE); + + // Unset columns. + // Note: 'serial' is displayed instead of 'sid'. + unset( + // Admin columns. + $columns['serial'], + $columns['label'], + $columns['uuid'], + $columns['in_draft'], + $columns['completed'], + $columns['changed'], + // User columns. + $columns['sticky'], + $columns['locked'], + $columns['notes'], + $columns['uid'] + ); + return $columns; } /** * {@inheritdoc} */ - public function getDefaultColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { - $columns = $this->getColumns($webform, $source_entity, $account, $include_elements); - // Hide certain unnecessary columns, that have default set to FALSE. - foreach ($columns as $column_name => $column) { - if (isset($column['default']) && $column['default'] === FALSE) { - unset($columns[$column_name]); - } - } + public function getUsersSubmissionsColumns() { + $columns = $this->getColumns(NULL, NULL, NULL, FALSE); + // Unset columns. + // Note: Displaying 'label' instead of 'serial' or 'sid'. + unset( + // Admin columns. + $columns['sid'], + $columns['serial'], + $columns['uuid'], + $columns['in_draft'], + $columns['completed'], + $columns['changed'], + // User columns. + $columns['sticky'], + $columns['locked'], + $columns['notes'], + $columns['uid'], + // References columns. + $columns['webform_id'], + $columns['entity'], + // Operations. + $columns['operations'] + ); return $columns; } @@ -539,26 +661,22 @@ public function getColumns(WebformInterface $webform = NULL, EntityInterface $so // Submission ID. $columns['sid'] = [ 'title' => $this->t('SID'), - 'default' => FALSE, ]; // Submission label. $columns['label'] = [ 'title' => $this->t('Submission title'), - 'default' => FALSE, 'sort' => FALSE, ]; // UUID. $columns['uuid'] = [ 'title' => $this->t('UUID'), - 'default' => FALSE, ]; // Draft. $columns['in_draft'] = [ 'title' => $this->t('In draft'), - 'default' => FALSE, ]; if (empty($account)) { @@ -586,13 +704,11 @@ public function getColumns(WebformInterface $webform = NULL, EntityInterface $so // Completed. $columns['completed'] = [ 'title' => $this->t('Completed'), - 'default' => FALSE, ]; // Changed. $columns['changed'] = [ 'title' => $this->t('Changed'), - 'default' => FALSE, ]; // Source entity. @@ -622,11 +738,16 @@ public function getColumns(WebformInterface $webform = NULL, EntityInterface $so 'title' => $this->t('IP address'), ]; - // Webform. + // Webform and source entity for entity.webform_submission.collection. + // @see /admin/structure/webform/submissions/manage if (empty($webform) && empty($source_entity)) { $columns['webform_id'] = [ 'title' => $this->t('Webform'), ]; + $columns['entity'] = [ + 'title' => $this->t('Submitted to'), + 'sort' => FALSE, + ]; } // Webform elements. @@ -637,17 +758,17 @@ public function getColumns(WebformInterface $webform = NULL, EntityInterface $so foreach ($elements as $element) { /** @var \Drupal\webform\Plugin\WebformElementInterface $element_plugin */ $element_plugin = $element_manager->createInstance($element['#type']); + // Replace tokens which can be used in an element's #title. + $element_plugin->replaceTokens($element, $webform); $columns += $element_plugin->getTableColumn($element); } } // Operations. - if (empty($account)) { - $columns['operations'] = [ - 'title' => $this->t('Operations'), - 'sort' => FALSE, - ]; - } + $columns['operations'] = [ + 'title' => $this->t('Operations'), + 'sort' => FALSE, + ]; // Add name and format to all columns. foreach ($columns as $name => &$column) { @@ -658,6 +779,46 @@ public function getColumns(WebformInterface $webform = NULL, EntityInterface $so return $columns; } + /** + * {@inheritdoc} + */ + public function getUserDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { + return ['serial', 'created', 'remote_addr']; + } + + /** + * {@inheritdoc} + */ + public function getDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { + $columns = $this->getDefaultColumns($webform, $source_entity, $account, $include_elements); + return array_keys($columns); + } + + /** + * Get specified columns in specified order. + * + * @param array $column_names + * An associative array of column names. + * @param array $columns + * An associative array containing all available columns. + * + * @return array + * An associative array containing all specified columns. + */ + protected function filterColumns(array $column_names, array $columns) { + $filtered_columns = []; + foreach ($column_names as $column_name) { + if (isset($columns[$column_name])) { + $filtered_columns[$column_name] = $columns[$column_name]; + } + } + return $filtered_columns; + } + + /****************************************************************************/ + // Custom settings methods. + /****************************************************************************/ + /** * {@inheritdoc} */ @@ -772,84 +933,91 @@ protected function doPostSave(EntityInterface $entity, $update) { /** @var \Drupal\webform\WebformSubmissionInterface $entity */ parent::doPostSave($entity, $update); - // Log transaction. $webform = $entity->getWebform(); - if (!$entity->getWebform()->getSetting('results_disabled')) { + + if ($webform->hasSubmissionLog()) { + // Log webform submission events to the 'webform_submission' log. $context = [ - '@id' => $entity->id(), - '@form' => $webform->label(), - 'link' => $entity->toLink($this->t('Edit'), 'edit-form')->toString(), + '@title' => $entity->label(), + 'link' => ($entity->id()) ? $entity->toLink($this->t('Edit'), 'edit-form')->toString() : NULL, + 'webform_submission' => $entity, ]; - switch ($entity->getState()) { - case WebformSubmissionInterface::STATE_DRAFT: - \Drupal::logger('webform')->notice('@form: Submission #@id draft saved.', $context); - break; - - case WebformSubmissionInterface::STATE_UPDATED: - \Drupal::logger('webform')->notice('@form: Submission #@id updated.', $context); - break; - - case WebformSubmissionInterface::STATE_COMPLETED: - if ($update) { - \Drupal::logger('webform')->notice('@form: Submission #@id completed.', $context); - } - else { - \Drupal::logger('webform')->notice('@form: Submission #@id created.', $context); - } - break; - } - } - - // Log submission events. - if ($entity->getWebform()->hasSubmissionLog()) { - $t_args = ['@title' => $entity->label()]; switch ($entity->getState()) { case WebformSubmissionInterface::STATE_DRAFT: if ($update) { - $operation = 'draft updated'; - $message = $this->t('@title draft updated.', $t_args); + $message = '@title draft updated.'; + $context['operation'] = 'draft updated'; } else { - $operation = 'draft created'; - $message = $this->t('@title draft created.', $t_args); + $message = '@title draft created.'; + $context['operation'] = 'draft created'; } break; case WebformSubmissionInterface::STATE_COMPLETED: if ($update) { - $operation = 'submission completed'; - $message = $this->t('@title completed using saved draft.', $t_args); + $message = '@title completed using saved draft.'; + $context['operation'] = 'submission completed'; } else { - $operation = 'submission created'; - $message = $this->t('@title created.', $t_args); + $message = '@title created.'; + $context['operation'] = 'submission created'; } break; case WebformSubmissionInterface::STATE_CONVERTED: - $operation = 'submission converted'; - $message = $this->t('@title converted from anonymous to @user.', $t_args + ['@user' => $entity->getOwner()->label()]); + $message = '@title converted from anonymous to @user.'; + $context['operation'] = 'submission converted'; + $context['@user'] = $entity->getOwner()->label(); break; case WebformSubmissionInterface::STATE_UPDATED: - $operation = 'submission updated'; - $message = $this->t('@title updated.', $t_args); + $message = '@title updated.'; + $context['operation'] = 'submission updated'; break; case WebformSubmissionInterface::STATE_UNSAVED: - $operation = 'submission submitted'; - $message = $this->t('@title submitted.', $t_args); + $message = '@title submitted.'; + $context['operation'] = 'submission submitted'; + break; + + case WebformSubmissionInterface::STATE_LOCKED: + $message = '@title locked.'; + $context['operation'] = 'submission locked'; break; default: throw new \Exception('Unexpected webform submission state'); } + \Drupal::logger('webform_submission')->notice($message, $context); + } + elseif (!$webform->getSetting('results_disabled')) { + // Log general events to the 'webform'. + switch ($entity->getState()) { + case WebformSubmissionInterface::STATE_DRAFT: + $message = '@title draft saved.'; + break; + + case WebformSubmissionInterface::STATE_UPDATED: + $message = '@title updated.'; + break; + + case WebformSubmissionInterface::STATE_COMPLETED: + $message = ($update) ? '@title completed.' : '@title created.'; + break; - $this->log($entity, [ - 'handler_id' => '', - 'operation' => $operation, - 'message' => $message, - ]); + default: + $message = NULL; + break; + } + if ($message) { + $context = [ + '@id' => $entity->id(), + '@title' => $entity->label(), + 'link' => ($entity->id()) ? $entity->toLink($this->t('Edit'), 'edit-form')->toString() : NULL, + ]; + \Drupal::logger('webform')->notice($message, $context); + } } $this->invokeWebformElements('postSave', $entity, $update); @@ -898,6 +1066,8 @@ public function delete(array $entities) { foreach ($entities as $entity) { $this->invokeWebformElements('postDelete', $entity); $this->invokeWebformHandlers('postDelete', $entity); + + WebformManagedFileBase::deleteFiles($entity); } // Remove empty webform submission specific file directory @@ -917,9 +1087,6 @@ public function delete(array $entities) { } } - // Delete submission log after all pre and post delete hooks are called. - $this->deleteLog($entities); - // Log deleted. foreach ($entities as $entity) { \Drupal::logger('webform') @@ -963,6 +1130,7 @@ public function purge($count) { $days_to_seconds = 60 * 60 * 24; $query = $this->entityManager->getStorage('webform')->getQuery(); + $query->accessCheck(FALSE); $query->condition('settings.purge', [self::PURGE_DRAFT, self::PURGE_COMPLETED, self::PURGE_ALL], 'IN'); $query->condition('settings.purge_days', 0, '>'); $webforms_to_purge = array_values($query->execute()); @@ -973,7 +1141,11 @@ public function purge($count) { $webforms_to_purge = $this->entityManager->getStorage('webform')->loadMultiple($webforms_to_purge); foreach ($webforms_to_purge as $webform) { $query = $this->getQuery(); - $query->condition('created', REQUEST_TIME - ($webform->getSetting('purge_days') * $days_to_seconds), '<'); + // Since results of this query are never displayed to the user and we + // actually need to query the entire dataset of webform submissions, we + // are disabling access check. + $query->accessCheck(FALSE); + $query->condition('created', $this->time->getRequestTime() - ($webform->getSetting('purge_days') * $days_to_seconds), '<'); $query->condition('webform_id', $webform->id()); switch ($webform->getSetting('purge')) { case self::PURGE_DRAFT: @@ -1153,42 +1325,24 @@ protected function deleteData(array $webform_submissions) { /** * {@inheritdoc} */ - public function log(WebformSubmissionInterface $webform_submission, array $values = []) { + public function log(WebformSubmissionInterface $webform_submission, array $context = []) { // Submission ID is required for logging. - // @todo Enable logging for submissions not saved to the database. if (empty($webform_submission->id())) { return; } - $values += [ + $message = $context['message']; + unset($context['message']); + + $context += [ 'uid' => $this->currentUser->id(), - 'webform_id' => $webform_submission->getWebform()->id(), - 'sid' => $webform_submission->id() ?: NULL, + 'webform_submission' => $webform_submission, 'handler_id' => NULL, 'data' => [], - 'timestamp' => time(), + 'link' => $webform_submission->toLink($this->t('Edit'), 'edit-form')->toString(), ]; - $values['data'] = serialize($values['data']); - \Drupal::database() - ->insert('webform_submission_log') - ->fields($values) - ->execute(); - } - /** - * Delete webform submission events from the 'webform_submission_log' table. - * - * @param array $webform_submissions - * An array of webform submissions. - */ - protected function deleteLog(array $webform_submissions) { - $sids = []; - foreach ($webform_submissions as $webform_submission) { - $sids[$webform_submission->id()] = $webform_submission->id(); - } - $this->database->delete('webform_submission_log') - ->condition('sid', $sids, 'IN') - ->execute(); + \Drupal::logger('webform_submission')->notice($message, $context); } /****************************************************************************/ @@ -1205,6 +1359,11 @@ public function loadDraft(WebformInterface $webform, EntityInterface $source_ent ]; $query = $this->getQuery(); + // Because draft is somewhat different from a complete webform submission, + // we allow to bypass access check. Moreover, draft here is enforced to be + // authored by the $account user. Thus we hardly open any security breach + // here. + $query->accessCheck(FALSE); $this->addQueryConditions($query, $webform, $source_entity, $account, $options); // Only load the most recent draft. @@ -1227,6 +1386,7 @@ public function userLogin(UserInterface $account) { // Move all anonymous submissions to UID of this account. $query = $this->getQuery(); + $query->accessCheck(FALSE); $query->condition('uid', 0); $query->condition('sid', $_SESSION['webform_submissions'], 'IN'); $query->sort('sid'); @@ -1302,7 +1462,7 @@ protected function checkAnonymousSubmissionAccess(WebformSubmissionInterface $we if ($this->currentUser->hasPermission('view own webform submission')) { return TRUE; } - elseif ($webform->checkAccessRules('view_own', $this->currentUser)->isAllowed()) { + elseif ($this->accessRulesManager->checkWebformSubmissionAccess('view_own', $this->currentUser, $webform_submission)->isAllowed()) { return TRUE; } elseif ($webform->getSetting('form_convert_anonymous')) { @@ -1320,7 +1480,7 @@ protected function checkAnonymousSubmissionAccess(WebformSubmissionInterface $we } /** - * Get anonymous users sumbmission ids. + * Get anonymous user's submission ids. * * @param \Drupal\Core\Session\AccountInterface|null $account * A user account. @@ -1342,6 +1502,9 @@ protected function getAnonymousSubmissionIds(AccountInterface $account) { // Cleanup sids because drafts could have been purged or the webform // submission could have been deleted. $_SESSION['webform_submissions'] = $this->getQuery() + // Disable access check because user having 'sid' in his $_SESSION already + // implies he has access to it. + ->accessCheck(FALSE) ->condition('sid', $_SESSION['webform_submissions'], 'IN') ->sort('sid') ->execute(); diff --git a/web/modules/webform/src/WebformSubmissionStorageInterface.php b/web/modules/webform/src/WebformSubmissionStorageInterface.php index aca641574a..b7988e1e66 100644 --- a/web/modules/webform/src/WebformSubmissionStorageInterface.php +++ b/web/modules/webform/src/WebformSubmissionStorageInterface.php @@ -63,7 +63,7 @@ public function getFieldDefinitions(); /** * Check field definition access. * - * Access checks include... + * Access checks include… * - Only allowing user who can update any access to the 'token' field. * * @param \Drupal\webform\WebformInterface $webform @@ -175,6 +175,45 @@ public function getMaxSubmissionId(WebformInterface $webform = NULL, EntityInter */ public function hasSubmissionValue(WebformInterface $webform, $element_key); + /****************************************************************************/ + // Source entity methods. + /****************************************************************************/ + + /** + * Get total number of source entities. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return int + * Total number of source entities. + */ + public function getSourceEntitiesTotal(WebformInterface $webform); + + /** + * Get source entities associated for a specified webform. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return array + * An associative array contain source entities associated for + * a specified webform grouped by entity type. + */ + public function getSourceEntities(WebformInterface $webform); + + /** + * Get source entities as options for a specified webform. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return array + * An associative array contain ource entities as options for + * a specified webform. + */ + public function getSourceEntitiesAsOptions(WebformInterface $webform); + /****************************************************************************/ // Query methods. /****************************************************************************/ @@ -299,7 +338,7 @@ public function getSourceEntityTypes(WebformInterface $webform); * @param bool $include_elements * Flag that include all form element in the list of columns. * - * @return array|mixed + * @return array * An associative array of columns keyed by name. */ public function getCustomColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); @@ -316,13 +355,13 @@ public function getCustomColumns(WebformInterface $webform = NULL, EntityInterfa * @param bool $include_elements * Flag that include all form element in the list of columns. * - * @return array|mixed + * @return array * An associative array of columns keyed by name. */ public function getUserColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); /** - * Get user default submission columns used to display results. + * Get default submission columns used to display results. * * @param \Drupal\webform\WebformInterface|null $webform * A webform. @@ -333,13 +372,29 @@ public function getUserColumns(WebformInterface $webform = NULL, EntityInterface * @param bool $include_elements * Flag that include all form element in the list of columns. * - * @return array|mixed - * An associative array of columns names. + * @return array + * An associative array of columns keyed by name. */ - public function getUserDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); + public function getDefaultColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); /** - * Get default submission columns used to display results. + * Get submissions columns. + * + * @return array + * An associative array of columns keyed by name. + */ + public function getSubmissionsColumns(); + + /** + * Get user submissions columns. + * + * @return array + * An associative array of columns keyed by name. + */ + public function getUsersSubmissionsColumns(); + + /** + * Get submission columns used to display results table. * * @param \Drupal\webform\WebformInterface|null $webform * A webform. @@ -350,13 +405,13 @@ public function getUserDefaultColumnNames(WebformInterface $webform = NULL, Enti * @param bool $include_elements * Flag that include all form element in the list of columns. * - * @return array|mixed + * @return array * An associative array of columns keyed by name. */ - public function getDefaultColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); + public function getColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); /** - * Get submission columns used to display results table. + * Get user default submission columns used to display results. * * @param \Drupal\webform\WebformInterface|null $webform * A webform. @@ -367,10 +422,31 @@ public function getDefaultColumns(WebformInterface $webform = NULL, EntityInterf * @param bool $include_elements * Flag that include all form element in the list of columns. * - * @return array|mixed - * An associative array of columns keyed by name. + * @return array + * An associative array of columns names. */ - public function getColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); + public function getUserDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); + + /** + * Get default submission columns used to display results. + * + * @param \Drupal\webform\WebformInterface|null $webform + * A webform. + * @param \Drupal\Core\Entity\EntityInterface|null $source_entity + * A webform submission source entity. + * @param \Drupal\Core\Session\AccountInterface|null $account + * A user account. + * @param bool $include_elements + * Flag that include all form element in the list of columns. + * + * @return array + * An associative array of columns names. + */ + public function getDefaultColumnNames(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE); + + /****************************************************************************/ + // Custom settings methods. + /****************************************************************************/ /** * Get customize setting. @@ -480,10 +556,22 @@ public function saveData(WebformSubmissionInterface $webform_submission, $delete * * @param \Drupal\webform\WebformSubmissionInterface $webform_submission * A webform submission. - * @param array $values - * The value to be logged includes 'handler_id', 'operation', 'message', and 'data'. - */ - public function log(WebformSubmissionInterface $webform_submission, array $values = []); + * @param array $context + * The values/context to be logged includes 'handler_id', 'operation', 'message', and 'data'. + * + * @deprecated Instead call the 'webform_submission' logger channel directly. + * + * $message = 'Some message with an %argument.' + * $context = [ + * '%argument' => 'Some value' + * 'link' => $webform_submission->toLink($this->t('Edit'), 'edit-form')->toString(), + * 'webform_submission' => $webform_submission, + * 'handler_id' => NULL, + * 'data' => [], + * ]; + * \Drupal::logger('webform_submission')->notice($message, $context); + */ + public function log(WebformSubmissionInterface $webform_submission, array $context = []); /****************************************************************************/ // Draft methods. diff --git a/web/modules/webform/src/WebformSubmissionStorageSchema.php b/web/modules/webform/src/WebformSubmissionStorageSchema.php index 0a6e612210..c3705f9aba 100644 --- a/web/modules/webform/src/WebformSubmissionStorageSchema.php +++ b/web/modules/webform/src/WebformSubmissionStorageSchema.php @@ -14,7 +14,7 @@ class WebformSubmissionStorageSchema extends SqlContentEntityStorageSchema { * {@inheritdoc} */ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) { - $schema = parent::getEntitySchema($entity_type, $reset = FALSE); + $schema = parent::getEntitySchema($entity_type, $reset); $schema['webform_submission_data'] = [ 'description' => 'Stores all submitted data for webform submissions.', @@ -65,75 +65,6 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res ], ]; - $schema['webform_submission_log'] = [ - 'description' => 'Table that contains logs of all webform submission events.', - 'fields' => [ - 'lid' => [ - 'type' => 'serial', - 'not null' => TRUE, - 'description' => 'Primary Key: Unique log event ID.', - ], - 'webform_id' => [ - 'description' => 'The webform id.', - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - ], - 'sid' => [ - 'description' => 'The webform submission id.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => FALSE, - ], - 'handler_id' => [ - 'description' => 'The webform handler id.', - 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - ], - 'uid' => [ - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - 'description' => 'The {users}.uid of the user who triggered the event.', - ], - 'operation' => [ - 'type' => 'varchar_ascii', - 'length' => 64, - 'not null' => TRUE, - 'default' => '', - 'description' => 'Type of operation, for example "save", "sent", or "update."', - ], - 'message' => [ - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'big', - 'description' => 'Text of log message.', - ], - 'data' => [ - 'type' => 'blob', - 'not null' => TRUE, - 'size' => 'big', - 'description' => 'Serialized array of data.', - ], - 'timestamp' => [ - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'description' => 'Unix timestamp of when event occurred.', - ], - ], - 'primary key' => ['lid'], - 'indexes' => [ - 'webform_id' => ['webform_id'], - 'sid' => ['sid'], - 'uid' => ['uid'], - 'handler_id' => ['handler_id'], - 'handler_id_operation' => ['handler_id', 'operation'], - ], - ]; - return $schema; } diff --git a/web/modules/webform/src/WebformSubmissionViewBuilder.php b/web/modules/webform/src/WebformSubmissionViewBuilder.php index 79146369c5..6ef69d5c28 100644 --- a/web/modules/webform/src/WebformSubmissionViewBuilder.php +++ b/web/modules/webform/src/WebformSubmissionViewBuilder.php @@ -2,14 +2,14 @@ namespace Drupal\webform; -use Drupal\Component\Serialization\Yaml; use Drupal\Core\Access\AccessResultInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityViewBuilder; use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Render\Element; use Drupal\webform\Plugin\WebformElementManagerInterface; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformYaml; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -23,7 +23,7 @@ class WebformSubmissionViewBuilder extends EntityViewBuilder implements WebformS * * @var \Drupal\webform\WebformRequestInterface */ - protected $requestManager; + protected $requestHandler; /** * The webform element manager service. @@ -57,7 +57,7 @@ class WebformSubmissionViewBuilder extends EntityViewBuilder implements WebformS */ public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, WebformRequestInterface $webform_request, WebformElementManagerInterface $element_manager, WebformSubmissionConditionsValidatorInterface $conditions_validator) { parent::__construct($entity_type, $entity_manager, $language_manager); - $this->requestManager = $webform_request; + $this->requestHandler = $webform_request; $this->elementManager = $element_manager; $this->conditionsValidator = $conditions_validator; } @@ -76,6 +76,20 @@ public static function createInstance(ContainerInterface $container, EntityTypeI ); } + /** + * {@inheritdoc} + */ + protected function getBuildDefaults(EntityInterface $entity, $view_mode) { + $build = parent::getBuildDefaults($entity, $view_mode); + // The webform submission will be rendered in the wrapped webform submission + // template already and thus has no entity template itself. + // @see \Drupal\contact_storage\ContactMessageViewBuilder + // @see \Drupal\comment\CommentViewBuilder::getBuildDefaults + // @see \Drupal\block_content\BlockContentViewBuilder::getBuildDefaults + unset($build['#theme']); + return $build; + } + /** * {@inheritdoc} */ @@ -90,23 +104,29 @@ public function buildComponents(array &$build, array $entities, array $displays, if ($view_mode === 'preview') { $options = [ + 'view_mode' => $view_mode, 'excluded_elements' => $webform->getSetting('preview_excluded_elements'), 'exclude_empty' => $webform->getSetting('preview_exclude_empty'), + 'exclude_empty_checkbox' => $webform->getSetting('preview_exclude_empty_checkbox'), ]; } else { $options = [ - 'excluded_elements' => $webform->getSetting('excluded_elements'), - 'exclude_empty' => $webform->getSetting('exclude_empty'), + 'view_mode' => $view_mode, + 'excluded_elements' => $webform->getSetting('submission_excluded_elements'), + 'exclude_empty' => $webform->getSetting('submission_exclude_empty'), + 'exclude_empty_checkbox' => $webform->getSetting('submission_exclude_empty_checkbox'), ]; } switch ($view_mode) { case 'yaml': + // Note that the YAML view ignores all access controls and excluded + // settings. $data = $webform_submission->toArray(TRUE, TRUE); $build[$id]['data'] = [ '#theme' => 'webform_codemirror', - '#code' => WebformYaml::tidy(Yaml::encode($data)), + '#code' => WebformYaml::encode($data), '#type' => 'yaml', ]; break; @@ -143,8 +163,7 @@ public function buildElements(array $elements, WebformSubmissionInterface $webfo $build = []; foreach ($elements as $key => $element) { - // Make sure this is an element. - if (!is_array($element) || Element::property($key)) { + if (!WebformElementHelper::isElement($element, $key)) { continue; } @@ -181,9 +200,16 @@ public function buildTable(array $elements, WebformSubmissionInterface $webform_ // Replace tokens before building the element. $webform_element->replaceTokens($element, $webform_submission); + // Check if empty value is excluded. + if ($webform_element->isEmptyExcluded($element, $options) && !$webform_element->getValue($element, $webform_submission, $options)) { + continue; + } + $title = $element['#admin_title'] ?: $element['#title'] ?: '(' . $key . ')'; + // Note: Not displaying an empty message since empty values just render + // an empty table cell. $html = $webform_element->formatHtml($element, $webform_submission, $options); - $rows[] = [ + $rows[$key] = [ ['header' => TRUE, 'data' => $title], ['data' => (is_string($html)) ? ['#markup' => $html] : $html], ]; @@ -193,7 +219,7 @@ public function buildTable(array $elements, WebformSubmissionInterface $webform_ '#type' => 'table', '#rows' => $rows, '#attributes' => [ - 'class' => ['webform-submission__table'], + 'class' => ['webform-submission-table'], ], ]; } @@ -216,7 +242,6 @@ public function buildTable(array $elements, WebformSubmissionInterface $webform_ * * @see \Drupal\webform\WebformSubmissionConditionsValidatorInterface::isElementVisible * @see \Drupal\Core\Render\Element::isVisibleElement - * */ protected function isElementVisible(array $element, WebformSubmissionInterface $webform_submission, array $options) { // Checked excluded elements. diff --git a/web/modules/webform/src/WebformSubmissionViewsData.php b/web/modules/webform/src/WebformSubmissionViewsData.php index d64b7abaaf..267e3682b6 100644 --- a/web/modules/webform/src/WebformSubmissionViewsData.php +++ b/web/modules/webform/src/WebformSubmissionViewsData.php @@ -15,6 +15,8 @@ class WebformSubmissionViewsData extends EntityViewsData { public function getViewsData() { $data = parent::getViewsData(); + $data['webform_submission']['table']['base']['access query tag'] = 'webform_submission_access'; + $data['webform_submission']['webform_submission_bulk_form'] = [ 'title' => $this->t('Webform submission operations bulk form'), 'help' => $this->t('Add a form element that lets you run operations on multiple submissions.'), diff --git a/web/modules/webform/src/WebformThemeManager.php b/web/modules/webform/src/WebformThemeManager.php index 050389444c..525c353ce8 100644 --- a/web/modules/webform/src/WebformThemeManager.php +++ b/web/modules/webform/src/WebformThemeManager.php @@ -4,14 +4,18 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Extension\ThemeHandlerInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Core\Theme\ThemeInitializationInterface; /** - * Defines a class to manage webform themeing. + * Defines a class to manage webform theming. */ class WebformThemeManager implements WebformThemeManagerInterface { + use StringTranslationTrait; + /** * The configuration object factory. * @@ -26,6 +30,13 @@ class WebformThemeManager implements WebformThemeManagerInterface { */ protected $themeManager; + /** + * The theme handler. + * + * @var \Drupal\Core\Extension\ThemeHandlerInterface + */ + protected $themeHandler; + /** * The theme initialization. * @@ -56,16 +67,45 @@ class WebformThemeManager implements WebformThemeManagerInterface { * The renderer. * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager * The theme manager. + * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler + * The theme handler. * @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization * The theme initialization. */ - public function __construct(ConfigFactoryInterface $config_factory, RendererInterface $renderer, ThemeManagerInterface $theme_manager, ThemeInitializationInterface $theme_initialization) { + public function __construct(ConfigFactoryInterface $config_factory, RendererInterface $renderer, ThemeManagerInterface $theme_manager, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization) { $this->configFactory = $config_factory; $this->renderer = $renderer; $this->themeManager = $theme_manager; + $this->themeHandler = $theme_handler; $this->themeInitialization = $theme_initialization; } + /** + * Get a theme's name. + * + * @return string + * A theme's name + */ + public function getThemeName($name) { + return $this->themeHandler->getName($name); + } + + /** + * Get themes as associative array. + * + * @return array + * An associative array containing theme name. + */ + public function getThemeNames() { + $themes = ['' => $this->t('Default')]; + foreach ($this->themeHandler->listInfo() as $name => $theme) { + if ($theme->status === 1) { + $themes[$name] = $theme->info['name']; + } + } + return $themes; + } + /** * {@inheritdoc} */ @@ -85,13 +125,13 @@ public function isActiveTheme($theme_name) { /** * {@inheritdoc} */ - public function setDefaultTheme() { + public function setCurrentTheme($theme_name = NULL) { if (!isset($this->activeTheme)) { $this->activeTheme = $this->themeManager->getActiveTheme(); } - $default_theme_name = $this->configFactory->get('system.theme')->get('default'); - $default_theme = $this->themeInitialization->getActiveThemeByName($default_theme_name); - $this->themeManager->setActiveTheme($default_theme); + $current_theme_name = $this->configFactory->get('system.theme')->get($theme_name ?: 'default'); + $current_theme = $this->themeInitialization->getActiveThemeByName($current_theme_name); + $this->themeManager->setActiveTheme($current_theme); } /** @@ -106,12 +146,12 @@ public function setActiveTheme() { /** * {@inheritdoc} */ - public function render(array &$elements, $default_theme = TRUE) { - if ($default_theme) { - $this->setDefaultTheme(); + public function render(array &$elements, $theme_name = NULL) { + if ($theme_name !== NULL) { + $this->setCurrentTheme($theme_name); } $markup = $this->renderer->render($elements); - if ($default_theme) { + if ($theme_name !== NULL) { $this->setActiveTheme(); } return $markup; @@ -120,12 +160,12 @@ public function render(array &$elements, $default_theme = TRUE) { /** * {@inheritdoc} */ - public function renderPlain(array &$elements, $default_theme = TRUE) { - if ($default_theme) { - $this->setDefaultTheme(); + public function renderPlain(array &$elements, $theme_name = NULL) { + if ($theme_name !== NULL) { + $this->setCurrentTheme($theme_name); } $markup = $this->renderer->renderPlain($elements); - if ($default_theme) { + if ($theme_name !== NULL) { $this->setActiveTheme(); } return $markup; diff --git a/web/modules/webform/src/WebformThemeManagerInterface.php b/web/modules/webform/src/WebformThemeManagerInterface.php index 4e8bfb59b2..179f913818 100644 --- a/web/modules/webform/src/WebformThemeManagerInterface.php +++ b/web/modules/webform/src/WebformThemeManagerInterface.php @@ -7,6 +7,22 @@ */ interface WebformThemeManagerInterface { + /** + * Get a theme's name. + * + * @return string + * A theme's name + */ + public function getThemeName($name); + + /** + * Get themes as associative array. + * + * @return array + * An associative array containing theme name. + */ + public function getThemeNames(); + /** * Get all active theme names. * @@ -27,9 +43,12 @@ public function getActiveThemeNames(); public function isActiveTheme($theme_name); /** - * Sets the current theme the default theme. + * Sets the current theme the theme. + * + * @param string $theme_name + * (optional) A theme name. Defaults the default theme. */ - public function setDefaultTheme(); + public function setCurrentTheme($theme_name = NULL); /** * Sets the current theme the active theme. diff --git a/web/modules/webform/src/WebformThirdPartySettingsManager.php b/web/modules/webform/src/WebformThirdPartySettingsManager.php index 57b88f09af..2647ced5ce 100644 --- a/web/modules/webform/src/WebformThirdPartySettingsManager.php +++ b/web/modules/webform/src/WebformThirdPartySettingsManager.php @@ -67,7 +67,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle $this->pathValidator = $path_validator; $this->addonsManager = $addons_manager; - $this->config = $this->configFactory->getEditable('webform.settings'); + $this->config = $this->configFactory->get('webform.settings'); $this->loadIncludes(); } diff --git a/web/modules/webform/src/WebformTokenManager.php b/web/modules/webform/src/WebformTokenManager.php index 7a8821c72a..74efbf7bd5 100644 --- a/web/modules/webform/src/WebformTokenManager.php +++ b/web/modules/webform/src/WebformTokenManager.php @@ -2,10 +2,16 @@ namespace Drupal\webform; +use Drupal\Component\Utility\Xss; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Theme\ThemeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\BubbleableMetadata; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Url; use Drupal\Core\Utility\Token; use Drupal\webform\Utility\WebformFormHelper; @@ -14,6 +20,22 @@ */ class WebformTokenManager implements WebformTokenManagerInterface { + use StringTranslationTrait; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + /** * The configuration object factory. * @@ -29,35 +51,51 @@ class WebformTokenManager implements WebformTokenManagerInterface { protected $moduleHandler; /** - * The theme manager. + * The token service. * - * @var \Drupal\Core\Theme\ThemeManagerInterface + * @var \Drupal\Core\Utility\Token */ - protected $themeManager; + protected $token; /** - * The token service. + * An array of support token suffixes. * - * @var \Drupal\Core\Utility\Token + * @var array + * + * @see webform_token_info_alter() */ - protected $token; + static protected $suffixes = [ + // Removes the token when not replaced. + 'clear', + // Decodes HTML entities. + 'htmldecode', + // Removes all HTML tags from the token's value. + 'striptags', + // URL encodes the token's value. + 'urlencode', + // XML encodes the token's value. + 'xmlencode', + ]; /** * Constructs a WebformTokenManager object. * + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration object factory. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. - * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager - * The theme manager. * @param \Drupal\Core\Utility\Token $token * The token service. */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, Token $token) { + public function __construct(AccountInterface $current_user, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token) { + $this->currentUser = $current_user; + $this->languageManager = $language_manager; $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; - $this->themeManager = $theme_manager; $this->token = $token; $this->config = $this->configFactory->get('webform.settings'); @@ -66,11 +104,11 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle /** * {@inheritdoc} */ - public function replace($text, EntityInterface $entity = NULL, array $data = [], array $options = []) { + public function replace($text, EntityInterface $entity = NULL, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata = NULL) { // Replace tokens within an array. if (is_array($text)) { - foreach ($text as $key => $value) { - $text[$key] = $this->replace($value, $entity, $data, $options); + foreach ($text as $key => $token_value) { + $text[$key] = $this->replace($token_value, $entity, $data, $options); } return $text; } @@ -86,44 +124,135 @@ public function replace($text, EntityInterface $entity = NULL, array $data = [], // Set token data based on entity type. $this->setTokenData($data, $entity); + + // Set token options based on entity. + $this->setTokenOptions($options, $entity); } - // Track the active theme, if there is no active theme it means tokens - // are being replaced during the initial page request. - $has_active_theme = $this->themeManager->hasActiveTheme(); + // For anonymous users remove all [current-user] tokens to prevent + // anonymous user properties from being displayed. + // For example, the [current-user:display-name] token will return + // 'Anonymous', which is not an expected behavior. + if ($this->currentUser->isAnonymous() && strpos($text, '[current-user:') !== FALSE) { + $text = preg_replace('/\[current-user:[^]]+\]/', '', $text); + } + + // Get supported suffixes. + $suffixes = $this->getSuffixes($options); + + // Prepare suffixes. + $text = $this->prepareSuffixes($text, $suffixes); + + // Replace the webform related tokens. + $text = $this->token->replace($text, $data, $options, $bubbleable_metadata); - // Replace the tokens. - $result = $this->token->replace($text, $data, $options); + // Process token suffixes. + $text = $this->processSuffixes($text); - // If there was no active theme and now there is one. - // Reset the active theme, so that theme negotiators can determine the - // correct active theme. - if (!$has_active_theme && $this->themeManager->hasActiveTheme()) { - $this->themeManager->resetActiveTheme(); + // Clear current user tokens for undefined values. + if (strpos($text, '[current-user:') !== FALSE) { + $text = preg_replace('/\[current-user:[^\]]+\]/', '', $text); } - return $result; + return $text; } /** * {@inheritdoc} */ - public function buildTreeLink(array $token_types = ['webform', 'webform_submission', 'webform_handler'], $description = NULL) { + public function replaceNoRenderContext($text, EntityInterface $entity = NULL, array $data = [], array $options = []) { + // Create BubbleableMetadata object which will be ignored. + $bubbleable_metadata = new BubbleableMetadata(); + return $this->replace($text, $entity, $data, $options, $bubbleable_metadata); + } + + /** + * Get token data based on an entity's type. + * + * @param array $data + * An array of token data. + * @param \Drupal\Core\Entity\EntityInterface $entity + * A Webform, Webform submission entity, or other entity. + */ + protected function setTokenData(array &$data, EntityInterface $entity) { + if ($entity instanceof WebformSubmissionInterface) { + $data['webform_submission'] = $entity; + $data['webform'] = $entity->getWebform(); + } + elseif ($entity instanceof WebformInterface) { + $data['webform'] = $entity; + } + else { + $data[$entity->getEntityTypeId()] = $entity; + } + } + + /** + * Set token option based on the entity. + * + * @param array $options + * An array of token data. + * @param \Drupal\Core\Entity\EntityInterface $entity + * A Webform or Webform submission entity. + */ + protected function setTokenOptions(array &$options, EntityInterface $entity) { + $token_options = []; + if ($entity instanceof WebformSubmissionInterface) { + $token_options['langcode'] = $entity->language()->getId(); + } + elseif ($entity instanceof WebformInterface) { + $token_options['langcode'] = $this->languageManager->getCurrentLanguage()->getId(); + } + $options += $token_options; + } + + /****************************************************************************/ + // Token elements. + /****************************************************************************/ + + /** + * {@inheritdoc} + */ + public function buildTreeLink(array $token_types = ['webform', 'webform_submission', 'webform_handler']) { + if (!$this->moduleHandler->moduleExists('token')) { + return [ + '#type' => 'link', + '#title' => $this->t('You may use tokens.'), + '#url' => Url::fromUri('https://www.drupal.org/project/token'), + ]; + } + else { + return [ + '#theme' => 'token_tree_link', + '#text' => $this->t('You may use tokens.'), + '#token_types' => $token_types, + '#click_insert' => TRUE, + '#dialog' => TRUE, + '#attached' => ['library' => ['webform/webform.token']], + ]; + } + } + + /** + * {@inheritdoc} + */ + public function buildTreeElement(array $token_types = ['webform', 'webform_submission', 'webform_handler'], $description = NULL) { if (!$this->moduleHandler->moduleExists('token')) { return []; } - // @todo Issue #2235581: Make Token Dialog support inserting in WYSIWYGs. $build = [ '#theme' => 'token_tree_link', '#token_types' => $token_types, - '#click_insert' => FALSE, + '#click_insert' => TRUE, '#dialog' => TRUE, + '#attached' => ['library' => ['webform/webform.token']], ]; if ($description) { if ($this->config->get('ui.description_help')) { return [ + '#type' => 'container', 'token_tree_link' => $build, 'help' => [ '#type' => 'webform_help', @@ -133,6 +262,7 @@ public function buildTreeLink(array $token_types = ['webform', 'webform_submissi } else { return [ + '#type' => 'container', 'token_tree_link' => $build, 'description' => [ '#prefix' => ' ', @@ -142,10 +272,17 @@ public function buildTreeLink(array $token_types = ['webform', 'webform_submissi } } else { - return $build; + return [ + '#type' => 'container', + 'token_tree_link' => $build, + ]; } } + /****************************************************************************/ + // Token validation. + /****************************************************************************/ + /** * {@inheritdoc} */ @@ -170,33 +307,150 @@ public function elementValidate(array &$form, array $token_types = ['webform', ' 'webform_select_other' => 'webform_select_other', 'webform_radios_other' => 'webform_radios_other', ]; + + // If $form render array is an element then see if we should add + // validation callback. + if (isset($form['#type']) && isset($text_element_types[$form['#type']])) { + $form['#element_validate'][] = [get_called_class(), 'validateElement']; + $form['#token_types'] = $token_types; + } + $elements =& WebformFormHelper::flattenElements($form); foreach ($elements as &$element) { if (!isset($element['#type']) || !isset($text_element_types[$element['#type']])) { continue; } - $element['#element_validate'][] = 'token_element_validate'; + $element['#element_validate'][] = [get_called_class(), 'validateElement']; $element['#token_types'] = $token_types; } } /** - * Get token data based on an entity's type. + * Validates an element's tokens. * - * @param array $token_data - * An array of token data. - * @param \Drupal\Core\Entity\EntityInterface $entity - * A Webform or Webform submission entity. + * Note: + * Element is not being based by reference since the #value is being altered. */ - protected function setTokenData(array &$token_data, EntityInterface $entity) { - if ($entity instanceof WebformSubmissionInterface) { - $token_data['webform_submission'] = $entity; - $token_data['webform'] = $entity->getWebform(); + public static function validateElement($element, FormStateInterface $form_state, &$complete_form) { + $value = isset($element['#value']) ? $element['#value'] : $element['#default_value']; + + if (!mb_strlen($value)) { + return $element; } - elseif ($entity instanceof WebformInterface) { - $token_data['webform'] = $entity; + + // Remove suffixes which are not valid. + $element['#value'] = preg_replace('/\[(webform[^]]+)((?::' . implode('|:', static::$suffixes) . ')+)\]/', '[\1]', $value); + + // Convert all token field deltas to 0 to prevent unexpected + // token validation errors. + $element['#value'] = preg_replace('/:\d+:/', ':0:', $element['#value']); + + token_element_validate($element, $form_state); + } + + /****************************************************************************/ + // Suffix handling. + /****************************************************************************/ + + /** + * Get an array of supported token suffixes. + * + * @param array $options + * A keyed array of settings and flags to control the token + * replacement process. + * + * @return array + * An array of supported token suffixes, + */ + protected function getSuffixes(array $options) { + $suffixes = static::$suffixes; + // Unset any $option['suffixes'] set to FALSE. + if (isset($options['suffixes'])) { + foreach ($suffixes as $index => $suffix) { + if (isset($options['suffixes'][$suffix]) && $options['suffixes'][$suffix] === FALSE) { + unset($suffixes[$index]); + } + } + } + return $suffixes; + } + + /** + * Prepare token suffixes to be replaced and processed. + * + * Prepare token suffixes by wrapping them in temp + * {webform-token-suffixes} tags. + * + * [webform:token:clear:urlencode] becomes + * {webform-token-suffixes:clear:urlencode}[webform:token]{/webform-token-suffixes}. + * + * @param string|array $text + * A string of text that may contain tokens. + * @param array $suffixes + * An array of supported suffixes. + * + * @return string + * A string of text with token suffixes wrapped in + * {webform-token-suffixes} tags. + */ + protected function prepareSuffixes($text, array $suffixes) { + if (preg_match_all('/\[(.+?)((?::' . implode('|:', $suffixes) . ')+)\]/', $text, $matches)) { + foreach ($matches[0] as $index => $match) { + $value = $matches[1][$index]; + $suffixes = $matches[2][$index]; + $wrapper = '{webform-token-suffixes' . $suffixes . '}[' . $value . ']{/webform-token-suffixes}'; + $text = str_replace($match, $wrapper, $text); + } + } + return $text; + } + + /** + * Process token suffixes after all tokens are replaced. + * + * @param string|array $text + * A string of text that may contain {webform-token-suffixes} tags. + * + * @return string + * String to text with all tokens suffixes processed. + */ + protected function processSuffixes($text) { + if (preg_match_all('/{webform-token-suffixes:([^}]+)}(.*?){\/webform-token-suffixes}/ms', $text, $matches)) { + foreach ($matches[0] as $index => $match) { + $search = $matches[0][$index]; + $replace = $matches[2][$index]; + + $value = $matches[2][$index]; + $suffixes = explode(':', $matches[1][$index]); + $suffixes = array_combine($suffixes, $suffixes); + + // If token is not replaced then only the :clear suffix is applicable. + if (preg_match('/^\[[^}]+\]$/', $value)) { + // Clear token text or restore the original token. + $original = str_replace(']', ':' . $matches[1][$index] . ']', $value); + $replace = (isset($suffixes['clear'])) ? '' : $original; + } + else { + // Decode and XSS filter value first. + if (isset($suffixes['htmldecode'])) { + $replace = html_entity_decode($replace); + $replace = (isset($suffixes['striptags'])) ? strip_tags($replace) : html_entity_decode(Xss::filterAdmin($replace)); + } + // Encode URL. + if (isset($suffixes['urlencode'])) { + $replace = urlencode($replace); + } + // Encode xml. + if (isset($suffixes['xmlencode'])) { + $replace = htmlspecialchars($replace, ENT_XML1); + } + } + + $text = str_replace($search, $replace, $text); + } } + return $text; } } diff --git a/web/modules/webform/src/WebformTokenManagerInterface.php b/web/modules/webform/src/WebformTokenManagerInterface.php index 9ebe444122..4eef6ed235 100644 --- a/web/modules/webform/src/WebformTokenManagerInterface.php +++ b/web/modules/webform/src/WebformTokenManagerInterface.php @@ -3,6 +3,7 @@ namespace Drupal\webform; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Render\BubbleableMetadata; /** * Defines an interface for token manager classes. @@ -27,23 +28,69 @@ interface WebformTokenManagerInterface { * array of token replacements after they are generated. * - clear: A boolean flag indicating that tokens should be removed from the * final text if no replacement value can be generated. + * @param \Drupal\Core\Render\BubbleableMetadata|null $bubbleable_metadata + * (optional) An object to which static::generate() and the hooks and + * functions that it invokes will add their required bubbleable metadata. * * @return string|array * Text or array with tokens replaced. * * @see \Drupal\Core\Utility\Token::replace */ - public function replace($text, EntityInterface $entity = NULL, array $data = [], array $options = []); + public function replace($text, EntityInterface $entity = NULL, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata = NULL); + + /** + * Replace tokens in text with no render context. + * + * This method allows tokens to be replaced when there is no render context + * via REST and JSON API requests. + * + * @param string|array $text + * A string of text that may contain tokens. + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * A Webform or Webform submission entity. + * @param array $data + * (optional) An array of keyed objects. + * @param array $options + * (optional) A keyed array of settings and flags to control the token + * replacement process. Supported options are: + * - langcode: A language code to be used when generating locale-sensitive + * tokens. + * - callback: A callback function that will be used to post-process the + * array of token replacements after they are generated. + * - clear: A boolean flag indicating that tokens should be removed from the + * final text if no replacement value can be generated. + * + * @return string|array + * Text or array with tokens replaced. + * + * @see \Drupal\Core\Utility\Token::replace + */ + public function replaceNoRenderContext($text, EntityInterface $entity = NULL, array $data = [], array $options = []); /** * Build token tree link if token.module is installed. * * @param array $token_types * An array containing token types that should be shown in the tree. + * + * @return array + * A render array containing a token tree link. + */ + public function buildTreeLink(array $token_types = ['webform', 'webform_submission']); + + /** + * Build token tree element if token.module is installed. + * + * @param array $token_types + * An array containing token types that should be shown in the tree. * @param string $description * (optional) Description to appear after the token tree link. + * + * @return array + * A render array containing a token tree link wrapped in a div. */ - public function buildTreeLink(array $token_types = ['webform', 'webform_submission'], $description = NULL); + public function buildTreeElement(array $token_types = ['webform', 'webform_submission'], $description = NULL); /** * Validate form that should have tokens in it. diff --git a/web/modules/webform/src/WebformTranslationManager.php b/web/modules/webform/src/WebformTranslationManager.php index cc8436eb6d..684c19f892 100644 --- a/web/modules/webform/src/WebformTranslationManager.php +++ b/web/modules/webform/src/WebformTranslationManager.php @@ -2,19 +2,29 @@ namespace Drupal\webform; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Utility\WebformArrayHelper; use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformYaml; +use Drupal\Core\Routing\RouteMatchInterface; /** * Defines a class to translate webform elements. */ class WebformTranslationManager implements WebformTranslationManagerInterface { + /** + * The current route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + /** * The language manager. * @@ -43,22 +53,54 @@ class WebformTranslationManager implements WebformTranslationManagerInterface { */ protected $translatableProperties; + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a WebformTranslationManager object. * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The current route match. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration object factory. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. * @param \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager * The webform element manager. */ - public function __construct(LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, WebformElementManagerInterface $element_manager) { + public function __construct(RouteMatchInterface $route_match, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory, MessengerInterface $messenger, WebformElementManagerInterface $element_manager) { + $this->routeMatch = $route_match; $this->languageManager = $language_manager; $this->configFactory = $config_factory; + $this->messenger = $messenger; $this->elementManager = $element_manager; } + /** + * {@inheritdoc} + */ + public function isAdminRoute() { + $route_name = $this->routeMatch->getRouteName(); + + // Don't initialize translation on webform CRUD routes. + if (preg_match('/^entity\.webform\.(?:edit_form|duplicate_form|delete_form)$/', $route_name)) { + return TRUE; + } + + // Don't initialize translation on webform UI routes. + if (strpos($route_name, 'entity.webform_ui.') === 0) { + return TRUE; + } + + return FALSE; + } + /** * {@inheritdoc} */ @@ -86,7 +128,7 @@ public function getElements(WebformInterface $webform, $langcode = NULL, $reset return []; } elseif ($error = WebformYaml::validate($elements)) { - drupal_set_message($error, 'error'); + $this->messenger->addError($error); return []; } else { @@ -102,6 +144,18 @@ public function getBaseElements(WebformInterface $webform) { $config_elements = $this->getElements($webform, $default_langcode); $elements = WebformElementHelper::getFlattened($config_elements); foreach ($elements as $element_key => &$element) { + // Always include composite element's default '#{element}__title'. + $element_plugin = $this->elementManager->getElementInstance($element); + if ($element_plugin instanceof WebformCompositeBase) { + $composite_elements = $element_plugin->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $property_key = $composite_key . '__title'; + if (empty($element["#$property_key"])) { + $element["#$property_key"] = $element_plugin->getDefaultProperty($property_key); + } + } + } + $this->removeUnTranslatablePropertiesFromElement($element); if (empty($element)) { unset($elements[$element_key]); @@ -153,7 +207,7 @@ public function getOriginalLangcode(WebformInterface $webform) { * An element. */ protected function removeUnTranslatablePropertiesFromElement(array &$element) { - $translatable_properties = $this->getTranslatableProperies(); + $translatable_properties = $this->getTranslatableProperties(); $element_type = (isset($element['#type'])) ? $element['#type'] : NULL; foreach ($element as $property_key => $property_value) { @@ -187,7 +241,7 @@ protected function removeUnTranslatablePropertiesFromElement(array &$element) { * @return array * An array of translated properties prefixed with a hashes (#). */ - protected function getTranslatableProperies() { + protected function getTranslatableProperties() { if ($this->translatableProperties) { return $this->translatableProperties; } diff --git a/web/modules/webform/src/WebformTranslationManagerInterface.php b/web/modules/webform/src/WebformTranslationManagerInterface.php index ba81f31c61..2c724e5b3c 100644 --- a/web/modules/webform/src/WebformTranslationManagerInterface.php +++ b/web/modules/webform/src/WebformTranslationManagerInterface.php @@ -7,6 +7,14 @@ */ interface WebformTranslationManagerInterface { + /** + * Determine if the translated webform should be displayed. + * + * @return bool + * TRUE if the translated webform should be displayed. + */ + public function isAdminRoute(); + /** * Get webform elements for specific language. * diff --git a/web/modules/webform/templates/webform-composite-address.html.twig b/web/modules/webform/templates/webform-composite-address.html.twig index c71432eb94..e6ddb7db4b 100644 --- a/web/modules/webform/templates/webform-composite-address.html.twig +++ b/web/modules/webform/templates/webform-composite-address.html.twig @@ -13,7 +13,6 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite') }} {% if flexbox %} {% if content.address %} diff --git a/web/modules/webform/templates/webform-composite-contact.html.twig b/web/modules/webform/templates/webform-composite-contact.html.twig index 52f124fc10..15bd443fae 100644 --- a/web/modules/webform/templates/webform-composite-contact.html.twig +++ b/web/modules/webform/templates/webform-composite-contact.html.twig @@ -13,7 +13,6 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite') }} {% if flexbox %} {% if content.name or content.company %} diff --git a/web/modules/webform/templates/webform-composite-link.html.twig b/web/modules/webform/templates/webform-composite-link.html.twig index b30ef739df..30fdb40907 100644 --- a/web/modules/webform/templates/webform-composite-link.html.twig +++ b/web/modules/webform/templates/webform-composite-link.html.twig @@ -11,5 +11,4 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite') }} {{ content }} diff --git a/web/modules/webform/templates/webform-composite-location.html.twig b/web/modules/webform/templates/webform-composite-location.html.twig index 18cb15649d..84eb2cd179 100644 --- a/web/modules/webform/templates/webform-composite-location.html.twig +++ b/web/modules/webform/templates/webform-composite-location.html.twig @@ -11,5 +11,4 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite') }} {{ content }} diff --git a/web/modules/webform/templates/webform-composite-name.html.twig b/web/modules/webform/templates/webform-composite-name.html.twig index d84a553578..6a55d243a9 100644 --- a/web/modules/webform/templates/webform-composite-name.html.twig +++ b/web/modules/webform/templates/webform-composite-name.html.twig @@ -13,7 +13,6 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite') }} {% if flexbox %} <div class="webform-flexbox"> {% if content.title %} diff --git a/web/modules/webform/templates/webform-composite-telephone.html.twig b/web/modules/webform/templates/webform-composite-telephone.html.twig index a5c766739e..939fcf5d7f 100644 --- a/web/modules/webform/templates/webform-composite-telephone.html.twig +++ b/web/modules/webform/templates/webform-composite-telephone.html.twig @@ -13,8 +13,6 @@ * @ingroup themeable */ #} -{{ attach_library('webform/webform.composite.telephone') }} - {{ content.type }} {{ content.phone }} {{ content.ext }} diff --git a/web/modules/webform/templates/webform-confirmation.html.twig b/web/modules/webform/templates/webform-confirmation.html.twig index 0547fe05ad..c5db7adfb6 100644 --- a/web/modules/webform/templates/webform-confirmation.html.twig +++ b/web/modules/webform/templates/webform-confirmation.html.twig @@ -27,7 +27,7 @@ {% if back %} <div class="webform-confirmation__back"> - <a href="{{ back_url }}" rel="back" title="{{ back_label }}"{{ back_attributes }}>{{ back_label }}</a> + <a href="{{ back_url }}" rel="prev" title="{{ back_label }}"{{ back_attributes }}>{{ back_label }}</a> </div> {% endif %} diff --git a/web/modules/webform/templates/webform-element-help.html.twig b/web/modules/webform/templates/webform-element-help.html.twig index 5dac43c06c..167433f895 100644 --- a/web/modules/webform/templates/webform-element-help.html.twig +++ b/web/modules/webform/templates/webform-element-help.html.twig @@ -15,6 +15,6 @@ #} {% spaceless %} {{ attach_library('webform/webform.element.help') }} -{{ help_icon }} +<span{{ attributes }}><span aria-hidden="true">?</span></span> {% endspaceless %} diff --git a/web/modules/webform/templates/webform-element-more.html.twig b/web/modules/webform/templates/webform-element-more.html.twig index f9f12cb819..458449469d 100644 --- a/web/modules/webform/templates/webform-element-more.html.twig +++ b/web/modules/webform/templates/webform-element-more.html.twig @@ -7,8 +7,11 @@ * - title: More label. * - content: More content. * - * @see template_preprocess_webform_element_more() + * Based on WAI-ARIA Authoring Practices 1.1: Disclosure (Show/Hide) * + * @see https://www.w3.org/TR/wai-aria-practices-1.1/#disclosure + * @see https://www.w3.org/TR/wai-aria-practices-1.1/examples/disclosure/disclosure-faq.html + * @see template_preprocess_webform_element_more() * @ingroup themeable */ #} @@ -20,6 +23,6 @@ set classes = [ ] %} <div{{ attributes.addClass(classes) }}> - <div class="webform-element-more--link"><a href="#more">{{ more_title }}</a></div> - <div class="webform-element-more--content">{{ more }}</div> + <div class="webform-element-more--link"><a role="button" href="#more">{{ more_title }}</a></div> + <div id="{{ attributes.id }}--content" class="webform-element-more--content">{{ more }}</div> </div> diff --git a/web/modules/webform/templates/webform-email-html.html.twig b/web/modules/webform/templates/webform-email-html.html.twig new file mode 100644 index 0000000000..c13483b72d --- /dev/null +++ b/web/modules/webform/templates/webform-email-html.html.twig @@ -0,0 +1,16 @@ +{# +/** + * @file + * Default theme implementation for webform email wrapper template as HTML. + * + * @ingroup themeable + */ +#} +<html> +<head> + <title>{{ subject }}</title> +</head> +<body> +{{ body|raw }} +</body> +</html> diff --git a/web/modules/webform/templates/webform-email-message-html.html.twig b/web/modules/webform/templates/webform-email-message-html.html.twig index c3954117c4..ee6a5966c9 100644 --- a/web/modules/webform/templates/webform-email-message-html.html.twig +++ b/web/modules/webform/templates/webform-email-message-html.html.twig @@ -4,7 +4,7 @@ * Default theme implementation for webform email wrapper template as HTML. * * Available variables: - * - message: The email message which contains to, from, subject, message, etc... + * - message: The email message which contains to, from, subject, message, etc… * - webform_submission: The webform submission. * - handler: The webform handler. * diff --git a/web/modules/webform/templates/webform-email-message-text.html.twig b/web/modules/webform/templates/webform-email-message-text.html.twig index dad0ddfced..7474c4bd22 100644 --- a/web/modules/webform/templates/webform-email-message-text.html.twig +++ b/web/modules/webform/templates/webform-email-message-text.html.twig @@ -4,7 +4,7 @@ * Default theme implementation for webform email wrapper template as plain text. * * Available variables: - * - message: The email message which contains to, from, subject, message, etc... + * - message: The email message which contains to, from, subject, message, etc… * - webform_submission: The webform submission. * - handler: The webform handler. * diff --git a/web/modules/webform/templates/webform-handler-email-summary.html.twig b/web/modules/webform/templates/webform-handler-email-summary.html.twig index 69cd89ad40..9984f4c7c2 100644 --- a/web/modules/webform/templates/webform-handler-email-summary.html.twig +++ b/web/modules/webform/templates/webform-handler-email-summary.html.twig @@ -16,5 +16,6 @@ {% if settings.bcc_mail %}<b>{{ 'BCC:'|t }}</b> {{ settings.bcc_mail }}<br />{% endif %} <b>{{ 'From:'|t }}</b> {% if settings.from_name %}{{ settings.from_name }}{% endif %} <{{ settings.from_mail }}><br /> <b>{{ 'Subject:'|t }}</b> {{ settings.subject }}<br /> -<b>{{ 'Settings:'|t }}</b> {{ settings.html ? 'HTML' : 'Plain text'|t }} {{ settings.html and settings.attachments ? '/' : '' }}{{ settings.attachments ? 'Attachments'|t : '' }} {{ settings.twig ? '(Twig)'|t : '' }}<br /> +<b>{{ 'Settings:'|t }}</b> {{ settings.html ? 'HTML' : 'Plain text'|t }} {{ settings.html and settings.attachments ? '/' : '' }} {{ settings.attachments ? 'Attachments'|t : '' }} {{ settings.twig ? '(Twig)'|t : '' }}<br /> <b>{{ 'Sent when:'|t }}</b> {% if settings.states %}{{ settings.states|join('; ') }}{% else %}{{ 'Custom'|t }}{% endif %}<br /> +{% if settings.theme_name %}<b>{{ 'Theme:'|t }}</b> {{ settings.theme_name }}<br />{% endif %} diff --git a/web/modules/webform/templates/webform-help-video-youtube.html.twig b/web/modules/webform/templates/webform-help-video-youtube.html.twig index 45198f834a..b27cf7f71d 100644 --- a/web/modules/webform/templates/webform-help-video-youtube.html.twig +++ b/web/modules/webform/templates/webform-help-video-youtube.html.twig @@ -12,6 +12,6 @@ {{ attach_library('webform/webform.help') }} <div class="webform-help-video-youtube"> <div class="webform-help-video-youtube--container"> - <iframe width="560" height="315" src="https://www.youtube.com/embed/{{ youtube_id }}{{ autoplay ? '?autoplay=1' : '' }}" frameborder="0" allowfullscreen></iframe> + <iframe width="560" height="315" src="https://www.youtube.com/embed/{{ youtube_id }}?rel=0&modestbranding=1{{ autoplay ? '&autoplay=1' : '' }}"{{ autoplay ? 'allow="autoplay"' : '' }} frameborder="0" allowfullscreen></iframe> </div> </div> diff --git a/web/modules/webform/templates/webform-html-editor-markup.html.twig b/web/modules/webform/templates/webform-html-editor-markup.html.twig new file mode 100644 index 0000000000..2560548dc2 --- /dev/null +++ b/web/modules/webform/templates/webform-html-editor-markup.html.twig @@ -0,0 +1,21 @@ +{# +/** + * @file + * Theme implementation for webform html editor markup. + * + * Available variables + * - markup: HTML markup. + * - allowed_tags: Allowed tags. + * - content: Renderable HTML markup. + * + * Using Twig output modifer to remove extra carriage returns at the end of + * the generated markup. + * + * @see template_preprocess_webform_html_editor_markup() + * @see \Drupal\webform\Element\WebformHtmlEditor::checkMarkup + * @see https://www.drupal.org/project/drupal/issues/2245901 + * @see https://twig.symfony.com/doc/2.x/templates.html#whitespace-control + * @ingroup themeable + */ +#} +{{- content -}} diff --git a/web/modules/webform/templates/webform-progress-tracker.html.twig b/web/modules/webform/templates/webform-progress-tracker.html.twig index da1f3cba39..2de6d27158 100644 --- a/web/modules/webform/templates/webform-progress-tracker.html.twig +++ b/web/modules/webform/templates/webform-progress-tracker.html.twig @@ -12,6 +12,7 @@ * - max_pages: Maximum number of pages that progress text should be displayed on. * * @see template_preprocess_webform_progress_bar() + * @see https://www.w3.org/WAI/tutorials/forms/multi-page/ * * @ingroup themeable */ @@ -20,20 +21,27 @@ <ul class="webform-progress-tracker progress-tracker progress-tracker--center"> {% for index, page in progress %} - {% - set classes = [ - 'progress-step', - index < current_index ? 'is-complete', - index == current_index ? 'is-active', - ] - %} - <li{{ attributes.setAttribute('data-webform-page', page.name).setAttribute('class', '').addClass(classes) }}> - <span class="progress-marker">{{ index + 1 }}</span> - {% if progress|length < max_pages %} - <span class="progress-text"> - <div class="progress-title">{{ page.title }}</div> - </span> - {% endif %} - </li> + {% set is_completed = index < current_index %} + {% set is_active = index == current_index %} + {% + set classes = [ + 'progress-step', + is_completed ? 'is-complete', + is_active ? 'is-active', + ] + %} + <li{{ attributes.setAttribute('data-webform-page', page.name).setAttribute('title', page.title).setAttribute('class', '').addClass(classes) }}> + <span class="progress-marker">{{ index + 1 }}</span> + {% if progress|length < max_pages %} + <span class="progress-text"> + <span class="progress-title"> + {% if is_active or is_completed %} + <span class="visually-hidden">{{ is_active ? 'Current'|t : 'Completed'|t }}: </span> + {% endif %} + {{ page.title }} + </span> + </span> + {% endif %} + </li> {% endfor %} </ul> diff --git a/web/modules/webform/templates/webform-submission-form.html.twig b/web/modules/webform/templates/webform-submission-form.html.twig new file mode 100644 index 0000000000..0969e22da7 --- /dev/null +++ b/web/modules/webform/templates/webform-submission-form.html.twig @@ -0,0 +1,12 @@ +{# +/** + * @file + * Default theme implementation for a webform submission form. + * + * Available variables: + * - form: The webform submission form. + * + * @ingroup themeable + */ +#} +{{ form }} diff --git a/web/modules/webform/templates/webform-submission-information.html.twig b/web/modules/webform/templates/webform-submission-information.html.twig index 2fdd381250..a85e5bf806 100644 --- a/web/modules/webform/templates/webform-submission-information.html.twig +++ b/web/modules/webform/templates/webform-submission-information.html.twig @@ -4,62 +4,80 @@ * Default theme implementation for webform submission information. * * Available variables: - * @todo Add variables. + * - serial: The serial number of the submission entity. + * - sid: The ID of the submission entity. + * - uuid: The UUID of the submission entity. + * - token: A secure token used to look up a submission. + * - uri: The URI the user submitted the webform. + * - created: The time that the submission was first saved. + * - completed: The time that the submission was completed. + * - changed: The time that the submission was saved. + * - in_draft: Is this a draft of the submission? + * - current_page: The current wizard page. + * - remote_addr: The IP address of the user that submitted the webform. + * - submitted_by: The user that submitted the webform. + * - submitted_to: Link to the submission's URI. + * - langcode: The submission language code. + * - locked: A flag that indicates a locked submission. + * - sticky: A flag that indicate the status of the submission. + * - notes: Administrative notes about the submission. + * - webform: The associated webform. + * - token_update: The tokenize URL used to update the submission. + * - delete: Link to delete the submission. * * @see template_preprocess_webform_submission_information() * * @ingroup themeable */ #} -<details data-webform-element-id="{{ webform_id }}--submission_information"{% if open %} open="open"{% endif %}> - <summary role="button"{% if open %} aria-expanded="true" aria-pressed="true"{% endif %}>{{ 'Submission information'|t }}</summary> - <div class="details-wrapper"> - {% if submissions_view %} - <div><b>{{ 'Submission Number'|t }}:</b> {{ serial }}</div> - <div><b>{{ 'Submission ID'|t }}:</b> {{ sid }}</div> - <div><b>{{ 'Submission UUID'|t }}:</b> {{ uuid }}</div> - {% if uri %} - <div><b>{{ 'Submission URI'|t }}:</b> {{ uri }}</div> - {% endif %} - {% if token_update %} - <div><b>{{ 'Submission Update'|t }}:</b> {{ token_update }}</div> - {% endif %} - <br /> - <div><b>{{ 'Created'|t }}:</b> {{ created }}</div> - <div><b>{{ 'Completed'|t }}:</b> {{ completed }}</div> - <div><b>{{ 'Changed'|t }}:</b> {{ changed }}</div> - <br /> - <div><b>{{ 'Remote IP address'|t }}:</b> {{ remote_addr }}</div> - <div><b>{{ 'Submitted by'|t }}:</b> {{ submitted_by }}</div> - <div><b>{{ 'Language'|t }}:</b> {{ language }}</div> - <br /> - <div><b>{{ 'Is draft'|t }}:</b> {{ is_draft }}</div> - {% if current_page %} - <div><b>{{ 'Current page'|t }}:</b> {{ current_page }}</div> - {% endif %} - <div><b>{{ 'Webform'|t }}:</b> {{ webform }}</div> - {% if submitted_to %} - <div><b>{{ 'Submitted to'|t }}:</b> {{ submitted_to }}</div> - {% endif %} - {% if sticky or locked or notes %} - <br /> - {% if sticky %} - <div><b>{{ 'Flagged'|t }}:</b> {{ sticky }}</div> - {% endif %} - {% if locked %} - <div><b>{{ 'Locked'|t }}:</b> {{ locked }}</div> - {% endif %} - {% if notes %} - <div><b>{{ 'Notes'|t }}:</b><br/> - <pre>{{ notes }}</pre> - </div> - {% endif %} - {% endif %} - - {% else %} - <div><b>{{ 'Submission Number'|t }}:</b> {{ serial }}</div> - <div><b>{{ 'Created'|t }}:</b> {{ created }}</div> +{% if submissions_view %} + <div><b>{{ 'Submission Number'|t }}:</b> {{ serial }}</div> + <div><b>{{ 'Submission ID'|t }}:</b> {{ sid }}</div> + <div><b>{{ 'Submission UUID'|t }}:</b> {{ uuid }}</div> + {% if uri %} + <div><b>{{ 'Submission URI'|t }}:</b> {{ uri }}</div> + {% endif %} + {% if token_update %} + <div><b>{{ 'Submission Update'|t }}:</b> {{ token_update }}</div> + {% endif %} + <br /> + <div><b>{{ 'Created'|t }}:</b> {{ created }}</div> + <div><b>{{ 'Completed'|t }}:</b> {{ completed }}</div> + <div><b>{{ 'Changed'|t }}:</b> {{ changed }}</div> + <br /> + <div><b>{{ 'Remote IP address'|t }}:</b> {{ remote_addr }}</div> + <div><b>{{ 'Submitted by'|t }}:</b> {{ submitted_by }}</div> + <div><b>{{ 'Language'|t }}:</b> {{ language }}</div> + <br /> + <div><b>{{ 'Is draft'|t }}:</b> {{ is_draft }}</div> + {% if current_page %} + <div><b>{{ 'Current page'|t }}:</b> {{ current_page }}</div> + {% endif %} + <div><b>{{ 'Webform'|t }}:</b> {{ webform }}</div> + {% if submitted_to %} + <div><b>{{ 'Submitted to'|t }}:</b> {{ submitted_to }}</div> + {% endif %} + {% if sticky or locked or notes %} + <br /> + {% if sticky %} + <div><b>{{ 'Flagged'|t }}:</b> {{ sticky }}</div> + {% endif %} + {% if locked %} + <div><b>{{ 'Locked'|t }}:</b> {{ locked }}</div> {% endif %} + {% if notes %} + <div><b>{{ 'Notes'|t }}:</b><br/> + <pre>{{ notes }}</pre> + </div> + {% endif %} + {% endif %} + +{% else %} + <div><b>{{ 'Submission Number'|t }}:</b> {{ serial }}</div> + <div><b>{{ 'Created'|t }}:</b> {{ created }}</div> +{% endif %} - </div> -</details> +{% if delete %} + <br/> + <div>{{ delete }}</div> +{% endif %} diff --git a/web/modules/webform/templates/webform-submission-navigation.html.twig b/web/modules/webform/templates/webform-submission-navigation.html.twig index 5e65624243..131c3b0f49 100644 --- a/web/modules/webform/templates/webform-submission-navigation.html.twig +++ b/web/modules/webform/templates/webform-submission-navigation.html.twig @@ -7,6 +7,7 @@ * - prev_url: URL to the previous webform submission. * - next_url: URL to the next webform submission. * - webform_id: The webform ID. Provided for context. + * - webform_title: The webform title. Provided for context. * * @see template_preprocess_webform_submission_navigation() * @@ -15,6 +16,7 @@ #} {% if prev_url or next_url %} <nav id="webform-submission-navigation-{{ webform_id }}" class="webform-submission-navigation" role="navigation" aria-labelledby="webform-submission-label-{{ webform_id }}"> + <h2 class="visually-hidden" id="webform-submission-label-{{ webform_id }}">Submission navigation links for {{ webform_title }}</h2> <ul class="webform-submission-pager"> {% if prev_url %} <li class="webform-submission-pager__item webform-submission-pager__item--previous"> diff --git a/web/modules/webform/templates/webform-submission.html.twig b/web/modules/webform/templates/webform-submission.html.twig index 65cb9dfb08..1675c0b876 100644 --- a/web/modules/webform/templates/webform-submission.html.twig +++ b/web/modules/webform/templates/webform-submission.html.twig @@ -22,5 +22,7 @@ view_mode ? 'webform-submission--view-mode-' ~ view_mode|clean_class, ] %} <div{{ attributes.addClass(classes) }}> -{{ elements }} +{{ navigation }} +{{ information }} +{{ submission }} </div> diff --git a/web/modules/webform/tests/files/sample.html b/web/modules/webform/tests/files/sample.html index bbda772c55..59950347d3 100644 --- a/web/modules/webform/tests/files/sample.html +++ b/web/modules/webform/tests/files/sample.html @@ -5,7 +5,7 @@ </head> <body> -The content of the document...... +The content of the document…… </body> -</html> \ No newline at end of file +</html> diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax.yml index 190033eeb9..cbb11887f5 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax title: 'Test: Ajax' description: 'Test Ajax enabled form with preview' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_inline.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_inline.yml index 133ef38092..641ef96243 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_inline.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_inline.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_inline title: 'Test: Ajax: Confirmation: Inline' description: 'Test Ajax inline confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_message.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_message.yml index 195a416e26..f3c68649da 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_message.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_message.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_message title: 'Test: Ajax: Confirmation: Message' description: 'Test Ajax basic confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_modal.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_modal.yml index dd164c27a4..8a0d97671a 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_modal.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_modal.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_modal title: 'Test: Ajax: Confirmation: Modal' description: 'Test Ajax confirmation modal.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_page.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_page.yml index cdd03d3a6f..63c2a406d1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_page.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_page.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_page title: 'Test: Ajax: Confirmation: Page' description: 'Test Ajax redirecting to a confirmation page.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url.yml index e792b5fc32..196feec043 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_url title: 'Test: Ajax: Confirmation: URL' description: 'Test redirecting to an internal URL.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url_msg.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url_msg.yml index 485e6f6052..2bd95a8dda 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url_msg.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_ajax_confirmation_url_msg.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_ajax_confirmation_url_msg title: 'Test: Ajax: Confirmation: URL with message' description: 'Test Ajax redirecting to an internal URL with a confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite.yml index e3fd983eb7..3c46471ec0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_composite title: 'Test: Composite: Composites' description: 'Test composite elements, includes address, contact, and name.' @@ -71,10 +73,10 @@ elements: | '#country__access': false link_basic: '#type': webform_link - '#title': 'Link' + '#title': Link '#default_value': title: Example - url: http://example.com + url: 'http://example.com' composite_elements_multiple: '#type': details '#title': 'Composite Elements Multiple' @@ -86,7 +88,7 @@ elements: | '#multiple__header': true '#default_value': - title: Example - url: http://example.com + url: 'http://example.com' '#title__help': 'This is link title help' '#url__help': 'This is link url help' css: '' @@ -97,6 +99,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -104,6 +107,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -119,22 +123,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -145,6 +164,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -163,9 +183,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -218,6 +240,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom.yml index 119bf7db02..a910896807 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_composite_custom title: 'Test: Composite: Custom composite' description: 'Test custom composite element.' @@ -16,8 +18,6 @@ elements: | webform_custom_composite_basic: '#type': webform_custom_composite '#title': webform_custom_composite_basic - '#multiple': false - '#multiple_header': false '#element': first_name: '#type': textfield @@ -26,8 +26,8 @@ elements: | '#type': textfield '#title': 'Last name' '#default_value': - - first_name: John - last_name: Smith + - first_name: John + last_name: Smith webform_custom_composite_advanced: '#type': webform_custom_composite '#title': webform_custom_composite_advanced @@ -54,17 +54,17 @@ elements: | '#title': 'Employment status' age: '#type': number - '#title': 'Age' + '#title': Age '#field_suffix': ' yrs. old' '#min': 1 '#max': 125 '#default_value': - - first_name: John - last_name: Smith - gender: Male - martial_status: Single - employment_status: Unemployed - age: 20 + - first_name: John + last_name: Smith + gender: Male + martial_status: Single + employment_status: Unemployed + age: 20 css: '' javascript: '' settings: @@ -73,6 +73,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -80,6 +81,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -95,22 +97,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -121,6 +138,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -139,9 +157,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -194,6 +214,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom_file.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom_file.yml new file mode 100644 index 0000000000..d48daa88de --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_custom_file.yml @@ -0,0 +1,200 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_composite_custom_file +title: 'Test: Composite: Custom composite file' +description: 'Test custom composite file element.' +category: 'Test: Composite' +elements: | + webform_custom_composite_file: + '#type': webform_custom_composite + '#title': webform_custom_composite_file + '#multiple__header': false + '#element': + textfield: + '#type': textfield + '#title': textfield + managed_file: + '#type': managed_file + '#title': managed_file + webform_custom_composite_file_advanced: + '#type': webform_custom_composite + '#title': webform_custom_composite_file_advanced + '#element': + textfield: + '#type': textfield + '#title': textfield + managed_file: + '#type': managed_file + '#title': managed_file +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format.yml index 35c76a8a04..96a3234ae7 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_composite_format title: 'Test: Composite: Format composite' description: 'Test composite element formatting.' @@ -115,40 +117,85 @@ elements: | '#type': details '#title': 'Composite elements' '#open': true + address: + '#type': details + '#title': 'Advanced address' + address_value: + '#type': address + '#title': 'Advanced address (Value)' + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format': value + address_raw: + '#type': address + '#title': 'Advanced address (Raw value)' + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format': raw + address_list: + '#type': address + '#title': 'Advanced address (List)' + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format': list webform_address: '#type': details - '#title': Address + '#title': 'Basic address' webform_address_value: '#type': webform_address - '#title': 'Address (Value)' + '#title': 'Basic address (Value)' '#default_value': address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': value webform_address_raw: '#type': webform_address - '#title': 'Address (Raw value)' + '#title': 'Basic address (Raw value)' '#default_value': address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': raw webform_address_list: '#type': webform_address - '#title': 'Address (List)' + '#title': 'Basic address (List)' '#default_value': address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': list webform_contact: @@ -166,7 +213,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': value webform_contact_raw: @@ -181,7 +228,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': raw webform_contact_list: @@ -196,7 +243,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format': list webform_link: @@ -223,35 +270,35 @@ elements: | title: Loremipsum url: 'http://example.com' '#format': list - webform_location: + webform_location_geocomplete: '#type': details '#title': Location - webform_location_value: - '#type': webform_location + webform_location_geocomplete_value: + '#type': webform_location_places '#title': 'Location (Value)' '#map': true '#geolocation': true '#format': value '#default_value': value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA' - webform_location_raw: - '#type': webform_location + webform_location_geocomplete_raw: + '#type': webform_location_places '#title': 'Location (Raw value)' '#map': true '#geolocation': true '#format': raw '#default_value': value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA' - webform_location_list: - '#type': webform_location + webform_location_geocomplete_list: + '#type': webform_location_places '#title': 'Location (List)' '#map': true '#geolocation': true '#format': list '#default_value': value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA' - webform_location_map: - '#type': webform_location + webform_location_geocomplete_map: + '#type': webform_location_places '#title': 'Location (Map)' '#map': true '#geolocation': true @@ -400,6 +447,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -407,6 +455,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -422,22 +471,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -448,6 +512,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -466,9 +531,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -521,6 +588,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_text: id: email @@ -532,23 +603,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -564,23 +637,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format_multiple.yml index 9c9c70a3bd..0b2ec9d80b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_composite_format_multiple.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_composite_format_multiple title: 'Test: Composite: Format composite multiple values' description: 'Test composite element formatting with multiple value.' @@ -17,103 +19,166 @@ elements: | '#type': details '#title': 'Composite elements' '#open': true + address: + '#type': details + '#title': 'Advanced address' + address_ol: + '#type': address + '#title': 'Advanced address (Ordered list)' + '#multiple': true + '#default_value': + - given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format_items': ol + address_ul: + '#type': address + '#title': 'Advanced address (Unordered list)' + '#multiple': true + '#default_value': + - given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format_items': ul + address_hr: + '#type': address + '#title': 'Advanced address (Horizontal rule)' + '#multiple': true + '#default_value': + - given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format_items': hr + address_table: + '#type': address + '#title': 'Advanced address (Table)' + '#multiple': true + '#default_value': + - given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#format_items': table webform_address: '#type': details - '#title': Address + '#title': 'Basic address' webform_address_ol: '#type': webform_address - '#title': 'Address (Ordered list)' + '#title': 'Basic address (Ordered list)' '#multiple': true '#default_value': - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': ol webform_address_ul: '#type': webform_address - '#title': 'Address (Unordered list)' + '#title': 'Basic address (Unordered list)' '#multiple': true '#default_value': - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': ul webform_address_hr: '#type': webform_address - '#title': 'Address (Horizontal rule)' + '#title': 'Basic address (Horizontal rule)' '#multiple': true '#default_value': - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': hr webform_address_table: '#type': webform_address - '#title': 'Address (Table)' + '#title': 'Basic address (Table)' '#multiple': true '#default_value': - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': table webform_contact: @@ -132,7 +197,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -142,7 +207,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -152,7 +217,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': ol webform_contact_ul: @@ -168,7 +233,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -178,7 +243,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -188,7 +253,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': ul webform_contact_hr: @@ -204,7 +269,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -214,7 +279,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -224,7 +289,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': hr webform_contact_table: @@ -240,7 +305,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -250,7 +315,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan - name: Loremipsum company: Loremipsum @@ -260,9 +325,108 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#format_items': table + webform_custom_composite: + '#type': details + '#title': 'Custom composite' + webform_custom_composite_ol: + '#type': webform_custom_composite + '#title': 'Custom composite (Ordered list)' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#multiple': true + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#format_items': ol + webform_custom_composite_ul: + '#type': webform_custom_composite + '#title': 'Custom composite (Unordered list)' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#multiple': true + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#format_items': ul + webform_custom_composite_hr: + '#type': webform_custom_composite + '#title': 'Custom composite (Horizontal rule)' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#multiple': true + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#format_items': hr + webform_custom_composite_table: + '#type': webform_custom_composite + '#title': 'Custom composite (Table)' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#multiple': true + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#format_items': table webform_link: '#type': details '#title': Link @@ -314,11 +478,11 @@ elements: | - title: Loremipsum url: 'http://example.com' '#format_items': table - webform_location: + webform_location_geocomplete: '#type': details '#title': Location - webform_location_ol: - '#type': webform_location + webform_location_geocomplete_ol: + '#type': webform_location_places '#title': 'Location (Ordered list)' '#map': true '#geolocation': true @@ -329,8 +493,8 @@ elements: | - value: 'London SW1A 1AA, United Kingdom' - value: 'Moscow, Russia, 10307' '#format_items': ol - webform_location_ul: - '#type': webform_location + webform_location_geocomplete_ul: + '#type': webform_location_places '#title': 'Location (Unordered list)' '#map': true '#geolocation': true @@ -341,8 +505,8 @@ elements: | - value: 'London SW1A 1AA, United Kingdom' - value: 'Moscow, Russia, 10307' '#format_items': ul - webform_location_hr: - '#type': webform_location + webform_location_geocomplete_hr: + '#type': webform_location_places '#title': 'Location (Horizontal rule)' '#map': true '#geolocation': true @@ -353,8 +517,8 @@ elements: | - value: 'London SW1A 1AA, United Kingdom' - value: 'Moscow, Russia, 10307' '#format_items': hr - webform_location_table: - '#type': webform_location + webform_location_geocomplete_table: + '#type': webform_location_places '#title': 'Location (Table)' '#map': true '#geolocation': true @@ -535,6 +699,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -542,6 +707,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -557,22 +723,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -583,6 +764,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -601,9 +783,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -656,6 +840,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_text: id: email @@ -667,23 +855,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -699,23 +889,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_inline.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_inline.yml index f3caadd1f1..1a4043191e 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_inline.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_inline.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_inline title: 'Test: Confirmation: Inline' description: 'Test inline confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_message.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_message.yml index 1fe89577d1..dbc73a001c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_message.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_message.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_message title: 'Test: Confirmation: Message' description: 'Test basic confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_modal.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_modal.yml index cf074c708e..2e7481f158 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_modal.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_modal.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_modal title: 'Test: Confirmation: Modal' description: 'Test confirmation modal.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_none.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_none.yml new file mode 100644 index 0000000000..94dc1bc366 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_none.yml @@ -0,0 +1,176 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_confirmation_none +title: 'Test: Confirmation: None' +description: 'Test no confirmation message.' +category: 'Test: Confirmation' +elements: | + description: + '#markup': 'This webform will not display a confirmation when submitted.' + test: + '#type': textfield + '#title': test +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: none + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page.yml index ec776fd4de..58ab287f47 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_page title: 'Test: Confirmation: Page' description: 'Test redirecting to a confirmation page.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page_custom.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page_custom.yml index d021656f6b..c4bd7d0f9f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page_custom.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_page_custom.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_page_custom title: 'Test: Confirmation: Page custom' description: 'Test redirecting to a customized confirmation page.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -95,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -150,4 +172,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url.yml index 3f42f81c06..84aaad2485 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_url title: 'Test: Confirmation: URL' description: 'Test redirecting to an internal URL.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url_message.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url_message.yml index eb7f71df85..5e069d9360 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url_message.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_confirmation_url_message.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_confirmation_url_message title: 'Test: Confirmation: URL with message' description: 'Test redirecting to an internal URL with a confirmation message.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element.yml index fc64914048..0d72dbd281 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element title: 'Test: Element' description: 'Test instances of common webform elements.' @@ -21,6 +23,9 @@ elements: | '#type': value '#title': value '#value': '{value}' + empty: + '#type': textfield + '#title': empty markup_elements: '#type': details '#title': 'Markup Elements' @@ -58,6 +63,7 @@ elements: | '#default_value': | {textarea line 1} {textarea line 2} + textfield: '#type': textfield '#title': textfield @@ -239,6 +245,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -246,6 +253,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -261,22 +269,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -287,6 +310,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -305,9 +329,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -360,4 +386,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_access.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_access.yml index 179b65581a..f77e3cff3f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_access.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_access.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_access title: 'Test: Element: Access' description: 'Test element access controls' @@ -33,14 +35,14 @@ elements: | '#type': textfield '#title': 'access_create_users (USER:1)' '#default_value': '{value}' - '#access_create_roles': { } + '#access_create_roles': { } '#access_create_users': - 1 access_create_permissions: '#type': textfield '#title': 'access_create_permissions (access user profiles)' '#default_value': '{value}' - '#access_create_roles': { } + '#access_create_roles': { } '#access_create_permissions': - 'access user profiles' access_update: @@ -63,14 +65,14 @@ elements: | '#type': textfield '#title': 'access_update_users (USER:1)' '#default_value': '{value}' - '#access_update_roles': { } + '#access_update_roles': { } '#access_update_users': - 1 access_update_permissions: '#type': textfield '#title': 'access_update_permissions (access user profiles)' '#default_value': '{value}' - '#access_update_roles': { } + '#access_update_roles': { } '#access_update_permissions': - 'access user profiles' access_view: @@ -93,14 +95,14 @@ elements: | '#type': textfield '#title': 'access_view_users (USER:1)' '#default_value': '{value}' - '#access_view_roles': { } + '#access_view_roles': { } '#access_view_users': - 1 access_view_permissions: '#type': textfield '#title': 'access_view_permissions (access user profiles)' '#default_value': '{value}' - '#access_view_roles': { } + '#access_view_roles': { } '#access_view_permissions': - 'access user profiles' css: '' @@ -111,6 +113,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -118,6 +121,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -133,22 +137,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -159,6 +178,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -177,9 +197,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -236,4 +258,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions.yml index 32f2204429..d52aa476c5 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: 1 template: false +archive: false id: test_element_actions title: 'Test: Element: Actions' description: 'Test Actions (aka Submit button(s)) element.' @@ -138,6 +140,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -145,6 +148,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -160,22 +164,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -186,6 +205,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -204,9 +224,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -259,4 +281,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions_buttons.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions_buttons.yml index 102772a8c5..5e7dffc475 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions_buttons.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_actions_buttons.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_actions_buttons title: 'Test: Element: Actions buttons' description: 'Test element actions buttons.' @@ -60,6 +62,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -67,6 +70,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -82,22 +86,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -108,6 +127,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: false @@ -126,9 +146,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -181,4 +203,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_address.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_address.yml new file mode 100644 index 0000000000..22997548e7 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_address.yml @@ -0,0 +1,234 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_address +title: 'Test: Element: Address' +description: 'Tests address element.' +category: 'Test: Element' +elements: | + address: + '#type': address + '#title': address_basic + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + address_advanced: + '#type': address + '#title': address_advanced + '#help': 'This is help text' + '#description': 'This is a description' + '#more_title': 'This is more title' + '#more': 'This is more text' + '#title_display': before + '#default_value': + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#available_countries': + US: US + '#field_overrides': + givenName: hidden + additionalName: hidden + familyName: hidden + organization: hidden + sortingCode: hidden + dependentLocality: hidden + address_none: + '#type': address + '#title': address_none + address_multiple: + '#type': address + '#title': address_multiple + '#multiple': true + '#default_value': + - given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 1 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_allowed_tags.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_allowed_tags.yml index 33428f198f..6a886c4848 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_allowed_tags.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_allowed_tags.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_allowed_tags title: 'Test: Element: Allowed tags' description: 'Test element #allowed_tags property.' @@ -16,7 +18,7 @@ elements: | item: '#type': item '#title': 'Below markup contains HTML tags' - '#markup': 'Hello <ignored></tag><b>...Goodbye</b>' + '#markup': 'Hello <ignored></tag><b>…Goodbye</b>' css: '' javascript: '' settings: @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_attributes.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_attributes.yml index a0d0a956ed..e85c43f7df 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_attributes.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_attributes.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_attributes title: 'Test: Element: Attributes' description: 'Test options attributes.' @@ -20,6 +22,7 @@ elements: | one two three + '#class__description': 'This is a custom class description.' '#style__description': 'This is a custom style description.' '#attributes__description': 'This is a custom attributes description.' @@ -38,6 +41,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -45,6 +49,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -60,22 +65,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -86,6 +106,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -104,9 +125,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -159,6 +182,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_autocomplete.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_autocomplete.yml index 033c99cae6..b5e111b5ed 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_autocomplete.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_autocomplete.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_autocomplete title: 'Test: Element: Autocomplete' description: 'Test element autocompletion.' @@ -16,7 +18,7 @@ elements: | autocomplete_off: '#type': email '#title': 'email (autocomplete=off)' - '#autocomplete': off + '#autocomplete': 'off' autocomplete_items: '#type': webform_autocomplete '#title': 'autocomplete (options)' @@ -43,6 +45,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -50,6 +53,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -65,22 +69,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -91,6 +110,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -109,9 +129,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -164,4 +186,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_buttons.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_buttons.yml index ca83785e6b..2e39e6c501 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_buttons.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_buttons.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_buttons title: 'Test: Element: buttons' description: 'Test the buttons element.' @@ -37,6 +39,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -44,6 +47,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -59,22 +63,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -85,6 +104,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -103,9 +123,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -158,6 +180,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_captcha.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_captcha.yml index 36cc235df0..f5e01999ab 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_captcha.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_captcha.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_captcha title: 'Test: Element: CAPTCHA' description: 'Tests CAPTCHA element.' @@ -38,11 +40,6 @@ elements: | captcha_recaptcha: '#type': captcha '#captcha_type': recaptcha/reCAPTCHA - captcha_recaptcha: - '#type': captcha - '#captcha_type': recaptcha/reCAPTCHA - '#captcha_title': '{captcha_recaptcha}' - '#captcha_description': '{captcha_recaptcha}' css: '' javascript: '' settings: @@ -51,6 +48,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -58,6 +56,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -73,22 +72,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -99,6 +113,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -117,9 +132,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -172,6 +189,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkbox_value.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkbox_value.yml index cb6362eef8..4cf82f0294 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkbox_value.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkbox_value.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_checkbox_value title: 'Test: Element: Checkbox value' description: 'Test the checkboxes value.' @@ -22,11 +24,10 @@ elements: | '#title': checkbox_value_filled '#value__title': 'Enter a value' '#default_value': '{default_value}' - checkbox_value_select_other: '#type': webform_checkbox_value '#title': checkbox_value_select_other - '#default_value': 'Four' + '#default_value': Four '#element': '#type': webform_select_other '#options': @@ -41,6 +42,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -48,6 +50,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -63,22 +66,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -89,6 +107,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -107,9 +126,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -162,6 +183,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkboxes.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkboxes.yml index fcc6ca9fd4..d9e1519eea 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkboxes.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_checkboxes.yml @@ -6,13 +6,26 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_checkboxes title: 'Test: Element: Checkboxes' description: 'Test the checkboxes.' category: 'Test: Element' elements: | + checkbox_example: + '#type': details + '#title': Checkbox + '#open': true + checkbox: + '#type': checkbox + '#title': checkbox + checkbox_exclude_empty: + '#type': checkbox + '#title': checkbox_exclude_empty + '#exclude_empty': true checkboxes_descriptions_example: '#type': details '#title': 'Checkboxes descriptions' @@ -81,6 +94,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -88,6 +102,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -103,22 +118,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -129,6 +159,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -147,9 +178,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -202,6 +235,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_codemirror.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_codemirror.yml index bb4656a26c..700c081360 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_codemirror.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_codemirror.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_codemirror title: 'Test: Element: CodeMirror' description: 'Tests CodeMirror element.' @@ -18,7 +20,12 @@ elements: | text_basic: '#type': webform_codemirror '#title': text_basic - '#default_value': 'Hello' + '#default_value': Hello + text_basic_no_wrap: + '#type': webform_codemirror + '#title': text_basic_no_wrap + '#wrap': false + '#default_value': 'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus,' yaml_basic: '#type': webform_codemirror '#mode': yaml @@ -65,12 +72,13 @@ elements: | </script> </body> </html> + twig_basic: '#type': webform_codemirror '#mode': twig '#title': twig_basic '#default_value': | - + {% set value = "Hello" %} {{ value }} css: '' @@ -81,6 +89,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -88,6 +97,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -103,22 +113,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -129,6 +154,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -147,9 +173,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -202,6 +230,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite.yml index 9d24504b32..1e73e92ea0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_composite title: 'Test: Element: Composite (Builder)' description: 'Test composite element builder used custom composites.' @@ -51,7 +53,7 @@ elements: | '#title': 'Employment status' age: '#type': number - '#title': 'Age' + '#title': Age '#field_suffix': ' yrs. old' '#min': 1 '#max': 125 @@ -63,6 +65,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -70,6 +73,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -85,22 +89,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -111,6 +130,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -129,9 +149,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -184,6 +206,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite_wrapper.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite_wrapper.yml new file mode 100644 index 0000000000..46a4a143e0 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_composite_wrapper.yml @@ -0,0 +1,297 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_composite_wrapper +title: 'Test: Element: Composite wrapper' +description: 'Test composite element wrapper.' +category: 'Test: Element' +elements: | + radios_wrapper_fieldset: + '#type': radios + '#title': radios_wrapper_fieldset + '#required': true + '#options': + One: One + Two: Two + Three: Three + radios_wrapper_fieldset_hidden_title: + '#type': radios + '#title': radios_wrapper_fieldset_hidden_title + '#title_display': invisible + '#required': true + '#options': + One: One + Two: Two + Three: Three + radios_wrapper_fieldset_description: + '#type': radios + '#title': radios_wrapper_fieldset_description + '#required': true + '#options': + One: One + Two: Two + Three: Three + '#description': 'This is a description' + radios_wrapper_fieldset_element_descriptions: + '#type': radios + '#title': radios_wrapper_fieldset_element_descriptions + '#required': true + '#options': + One: 'One -- This is a radio description' + Two: 'Two -- This is a radio description' + Three: 'Three -- This is a radio description' + '#description': 'This is an element description' + checkboxes_wrapper_fieldset_description: + '#type': checkboxes + '#title': checkboxes_wrapper_fieldset_description + '#required': true + '#options': + One: One + Two: Two + Three: Three + '#description': 'This is a description' + checkboxes_wrapper_fieldset_element_descriptions: + '#type': checkboxes + '#title': checkboxes_wrapper_fieldset_element_descriptions + '#required': true + '#options': + One: 'One -- This is a checkbox description' + Two: 'Two -- This is a checkbox description' + Three: 'Three -- This is a checkbox description' + '#description': 'This is an element description' + select_other_wrapper_fieldset_description: + '#type': webform_select_other + '#title': select_other_wrapper_fieldset_description + '#required': true + '#options': + One: One + Two: Two + Three: Three + '#description': 'This is a description' + radios_wrapper_form_element: + '#type': radios + '#title': radios_wrapper_form_element + '#required': true + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': form_element + radios_wrapper_container: + '#type': radios + '#title': radios_wrapper_container + '#required': true + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': container + states_checkbox: + '#type': checkbox + '#title': states_checkbox + states_fieldset: + '#type': radios + '#title': states_fieldset + '#options': + One: One + Two: Two + Three: Three + '#states': + visible: + ':input[name="states_checkbox"]': + checked: true + states_form_item: + '#type': radios + '#title': states_form_item + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': form_element + '#states': + visible: + ':input[name="states_checkbox"]': + checked: true + states_container: + '#type': radios + '#title': states_container + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': container + '#states': + visible: + ':input[name="states_checkbox"]': + checked: true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_ajax.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_ajax.yml new file mode 100644 index 0000000000..0db74973bb --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_ajax.yml @@ -0,0 +1,221 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_computed_ajax +title: 'Test: Element: Computed ajax' +description: 'Test computed element using Ajax.' +category: 'Test: Element' +elements: | + a: + '#type': webform_select_other + '#title': a + '#options': + 1: 1 + 2: 2 + 3: 3 + '#other__type': number + b: + '#type': number + '#title': b + webform_computed_token_a: + '#type': webform_computed_token + '#title': a + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:a:clear]' + webform_computed_token_b: + '#type': webform_computed_token + '#title': b + '#ajax': true + '#hide_empty': true + '#template': '[webform_submission:values:b:clear]' + webform_computed_twig: + '#type': webform_computed_twig + '#title': webform_computed_twig_data + '#ajax': true + '#template': | + {% spaceless %} + {% if data.a|length and data.b|length %} + {{ data.a }} + {{ data.b }} = {{ data.a + data.b }} + {% else %} + Please enter a value for a and b. + {% endif %} + {% endspaceless %} + + webform_computed_twig_token: + '#type': webform_computed_twig + '#title': webform_computed_twig_token + '#ajax': true + '#template': | + {% spaceless %} + {% set a = webform_token('[webform_submission:values:a:clear]', webform_submission) %} + {% set b = webform_token('[webform_submission:values:b:clear]', webform_submission) %} + {% if a|length and b|length %} + {{ a }} + {{ b }} = {{ a + b }} + {% else %} + Please enter a value for a and b. + {% endif %} + {% endspaceless %} +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 1 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_token.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_token.yml index 8363ea2235..99f205402e 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_token.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_token.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_computed_token title: 'Test: Element: Computed token' description: 'Test computed token element.' @@ -27,6 +29,7 @@ elements: | '#default_value': | <p>This is a <strong>text format</strong> string.</p> <p>It contains "double" and 'single' quotes with special characters like <, >, <>, and ><.</p> + xss: '#type': textfield '#title': xss @@ -35,38 +38,41 @@ elements: | '#type': webform_computed_token '#title': webform_computed_token_auto '#display_on': view - '#value': | + '#template': | <b class="webform_computed_token_auto">simple string:</b> [webform_submission:values:simple_string]<br /> <b class="webform_computed_token_auto">complex string :</b> [webform_submission:values:complex_string]<br /> <b class="webform_computed_token_auto">text_format:</b> [webform_submission:values:text_format]<br /> <b class="webform_computed_token_auto">xss:</b> [webform_submission:values:xss]<br /> + webform_computed_token_html: '#type': webform_computed_token '#title': webform_computed_token_html '#mode': html '#display_on': view - '#value': | + '#template': | <b class="webform_computed_token_html">simple string:</b> [webform_submission:values:simple_string]<br /> <b class="webform_computed_token_html">complex string :</b> [webform_submission:values:complex_string]<br /> <b class="webform_computed_token_html">text_format:</b> [webform_submission:values:text_format]<br /> <b class="webform_computed_token_html">xss:</b> [webform_submission:values:xss]<br /> + webform_computed_token_text: '#type': webform_computed_token '#title': webform_computed_token_text '#mode': text '#display_on': view - '#value': | + '#template': | simple string: [webform_submission:values:simple_string] complex string : [webform_submission:values:complex_string] text_format: [webform_submission:values:text_format] xss: [webform_submission:values:xss] + webform_computed_token_store: '#type': webform_computed_token '#title': webform_computed_token_store '#mode': text '#display_on': none - '#value': '[webform_submission:values:simple_string]' '#store': true + '#template': 'sid: [webform_submission:sid]' css: '' javascript: '' settings: @@ -75,6 +81,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -82,6 +89,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -97,22 +105,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -123,6 +146,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -141,9 +165,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -196,4 +222,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_twig.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_twig.yml index a14b4b9c00..e334b4ba22 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_twig.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_computed_twig.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_computed_twig title: 'Test: Element: Computed twig' description: 'Test computed twig element.' @@ -31,6 +33,7 @@ elements: | '#default_value': | <p>This is a <strong>text format</strong> string.</p> <p>It contains "double" and 'single' quotes with special characters like <, >, <>, and ><.</p> + xss: '#type': textfield '#title': xss @@ -39,51 +42,65 @@ elements: | '#type': webform_computed_twig '#title': webform_computed_twig_auto '#display_on': view - '#value': | + '#template': | <b class="webform_computed_twig_auto">number:</b> {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) }} * 2 = {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) * 2 }}<br /> <b class="webform_computed_twig_auto">simple string:</b> {{ webform_token('[webform_submission:values:simple_string]', webform_submission) }}<br /> <b class="webform_computed_twig_auto">complex string:</b> {{ webform_token('[webform_submission:values:complex_string]', webform_submission) }}<br /> <b class="webform_computed_twig_auto">text_format:</b> {{ webform_token('[webform_submission:values:text_format]', webform_submission) }}<br /> <b class="webform_computed_twig_auto">xss:</b> {{ webform_token('[webform_submission:values:xss]', webform_submission) }}<br /> + webform_computed_twig_html: '#type': webform_computed_twig '#title': webform_computed_twig_html '#mode': html '#display_on': view - '#value': | + '#template': | <b class="webform_computed_twig_html">number:</b> {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) }} * 2 = {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) * 2 }}<br /> <b class="webform_computed_twig_html">simple string:</b> {{ webform_token('[webform_submission:values:simple_string]', webform_submission) }}<br /> <b class="webform_computed_twig_html">complex string:</b> {{ webform_token('[webform_submission:values:complex_string]', webform_submission) }}<br /> <b class="webform_computed_twig_html">text_format:</b> {{ webform_token('[webform_submission:values:text_format]', webform_submission) }}<br /> <b class="webform_computed_twig_html">xss:</b> {{ webform_token('[webform_submission:values:xss]', webform_submission) }}<br /> + webform_computed_twig_text: '#type': webform_computed_twig '#title': webform_computed_twig_text '#mode': text '#display_on': view - '#value': | + '#template': | number: {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) }} * 2 = {{ (webform_token('[webform_submission:values:number:clear]', webform_submission) ?: 0) * 2 }} simple string: {{ webform_token('[webform_submission:values:simple_string]', webform_submission) }} complex string: {{ webform_token('[webform_submission:values:complex_string]', webform_submission) }} text_format: {{ webform_token('[webform_submission:values:text_format]', webform_submission) }} xss: {{ webform_token('[webform_submission:values:xss]', webform_submission) }} + webform_computed_twig_data: '#type': webform_computed_twig '#title': webform_computed_twig_data '#display_on': view - '#value': | + '#template': | <b class="webform_computed_twig_data">number:</b> {{ data.number }} * 2 = {{ data.number * 2 }}<br /> <b class="webform_computed_twig_data">simple string:</b> {{ data.simple_string }}<br /> <b class="webform_computed_twig_data">complex string:</b> {{ data.complex_string }}<br /> <b class="webform_computed_twig_data">text_format:</b> {{ data.text_format.value }}<br /> <b class="webform_computed_twig_data">xss:</b> {{ data.xss }}<br /> + webform_computed_twig_store: '#type': webform_computed_twig '#title': webform_computed_twig_store '#mode': text '#display_on': none - '#value': '{{ data.simple_string }}' '#store': true + '#template': 'sid: {{ data.sid }}' + webform_computed_twig_trim: + '#type': webform_computed_twig + '#title': webform_computed_twig_trim + '#whitespace': trim + '#template': ' <em>This is trimmed</em> <br/> ' + webform_computed_twig_spaceless: + '#type': webform_computed_twig + '#title': webform_computed_twig_spaceless + '#whitespace': spaceless + '#template': ' <em>This is spaceless</em> <br/> ' css: '' javascript: '' settings: @@ -92,6 +109,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -99,6 +117,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -114,22 +133,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -140,6 +174,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -158,9 +193,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -213,4 +250,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_container.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_container.yml index de35002342..eb4568f991 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_container.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_container.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_container title: 'Test: Element: Container' description: 'Test container elements.' @@ -36,7 +38,7 @@ elements: | '#title': details_closed '#description': 'This is a details_closed description.' '#open': false - '#format': 'details-closed' + '#format': details-closed details_value: '#markup': 'This is a details_closed value.' details_textfield: @@ -80,6 +82,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -87,6 +90,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -102,22 +106,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -128,6 +147,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -146,9 +166,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -201,4 +223,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_counter.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_counter.yml new file mode 100644 index 0000000000..ebab92f719 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_counter.yml @@ -0,0 +1,243 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_counter +title: 'Test: Element: Counter' +description: 'Tests text counter.' +category: 'Test: Element' +elements: | + counter_characters: + '#type': details + '#title': counter_characters + '#open': true + counter_characters_min: + '#type': textfield + '#title': 'counter_characters_min (5)' + '#counter_type': character + '#counter_minimum': 5 + counter_characters_max: + '#type': textfield + '#title': 'counter_characters_max (10)' + '#counter_type': character + '#counter_maximum': 10 + counter_characters_min_max: + '#type': textfield + '#title': 'counter_characters_min_max (5-10)' + '#counter_type': character + '#counter_minimum': 5 + '#counter_maximum': 10 + counter_characters_min_message: + '#type': textfield + '#title': 'counter_characters_min_message (5)' + '#counter_type': character + '#counter_minimum': 5 + '#counter_minimum_message': '%d character(s) entered. This is custom text' + counter_characters_max_message: + '#type': textfield + '#title': 'counter_characters_max_message (10)' + '#counter_type': character + '#counter_maximum': 10 + '#counter_maximum_message': '%d character(s) remaining. This is custom text' + counter_word: + '#type': details + '#title': counter_word + '#open': true + counter_words_min: + '#type': textarea + '#title': 'counter_words_min (5)' + '#counter_type': word + '#counter_minimum': 5 + counter_words_max: + '#type': textarea + '#title': 'counter_words_max (10)' + '#counter_type': word + '#counter_maximum': 10 + counter_words_min_max: + '#type': textarea + '#title': 'counter_words_min_max (5-10)' + '#counter_type': word + '#counter_minimum': 5 + '#counter_maximum': 10 + counter_words_min_message: + '#type': textarea + '#title': 'counter_words_min_message (5)' + '#counter_type': word + '#counter_minimum': 5 + '#counter_minimum_message': '%d word(s) entered. This is custom text' + counter_words_max_message: + '#type': textarea + '#title': 'counter_words_max_message (10)' + '#counter_type': word + '#counter_maximum': 10 + '#counter_maximum_message': '%d character(s) remaining. This is custom text' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_date.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_date.yml index ca0410fd58..e185b0f377 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_date.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_date.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_date title: 'Test: Element: Date' description: 'Test date element.' @@ -21,23 +23,23 @@ elements: | '#type': date '#title': date_custom '#date_date_format': d-M-Y - '#min': '2008-01-01' - '#max': '2010-12-31' + '#date_date_min': '2008-01-01' + '#date_date_max': '2010-12-31' '#default_value': '2009-08-18' '#description': '(2008-01-01 to 2010-12-31) + (d-M-Y)' date_min_max: '#type': date '#title': date_min_max '#description': '(2009-01-01 to 2009-12-31)' - '#min': '2009-01-01' - '#max': '2009-12-31' + '#date_date_min': '2009-01-01' + '#date_date_max': '2009-12-31' '#default_value': '2009-08-18' date_min_max_dynamic: '#type': date '#title': date_min_max_dynamic '#description': '(-1 year to +1 year)' - '#min': '-1 year' - '#max': '+1 year' + '#date_date_min': '-1 year' + '#date_date_max': '+1 year' '#default_value': now date_datepicker: '#type': date @@ -45,6 +47,13 @@ elements: | '#datepicker': true '#date_date_format': 'D, m/d/Y' '#default_value': '2009-08-18' + date_datepicker_button: + '#type': date + '#title': date_datepicker_button + '#datepicker': true + '#datepicker_button': true + '#date_date_format': 'D, m/d/Y' + '#default_value': '2009-08-18' date_datepicker_custom: '#type': date '#title': date_datepicker_custom @@ -57,8 +66,8 @@ elements: | '#datepicker': true '#date_date_format': 'D, m/d/Y' '#description': '(-1 year to +1 year)' - '#min': '-1 year' - '#max': '+1 year' + '#date_date_min': '-1 year' + '#date_date_max': '+1 year' '#default_value': now css: '' javascript: '' @@ -68,6 +77,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -75,6 +85,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -90,22 +101,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -116,6 +142,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -134,9 +161,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -189,4 +218,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datelist.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datelist.yml index 378c63663f..517b21c771 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datelist.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datelist.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_datelist title: 'Test: Element: Date list' description: 'Test date list element.' @@ -17,6 +19,23 @@ elements: | '#type': datelist '#title': datelist_default '#default_value': '2009-08-18T01:00:00-05:00' + datelist_no_abbreviate: + '#type': datelist + '#title': datelist_no_abbreviate + '#default_value': '2009-08-18T01:00:00-05:00' + '#date_abbreviate': false + datelist_text_parts: + '#type': datelist + '#title': datelist_text_parts + '#default_value': '2009-08-18T01:00:00-05:00' + '#date_text_parts': + - month + - day + - year + - hour + - minute + - second + - ampm datelist_datetime: '#type': datelist '#title': datelist_datetime @@ -41,20 +60,41 @@ elements: | '#type': datelist '#title': datelist_min_max '#description': '(2009-01-01 to 2009-12-31)' - '#min': '2009-01-01' - '#max': '2009-12-31' + '#date_date_min': '2009-01-01' + '#date_date_max': '2009-12-31' '#default_value': '2009-08-18' + datelist_min_max_time: + '#type': datelist + '#title': datelist_min_max_time + '#description': '(2009-01-01 09:00:00 to 2009-12-31 17:00:00)' + '#date_min': '2009-01-01 09:00:00' + '#date_max': '2009-12-31 17:00:00' + '#default_value': '2009-01-01 09:00:00' datelist_date_year_range_reverse: '#type': datelist '#title': datelist_date_year_range_reverse '#date_year_range': '2005:2010' - '#date_year_range_reverse': TRUE + '#date_year_range_reverse': true datelist_required_error: '#type': datelist '#title': datelist_required_error '#default_value': '2009-08-18T01:00:00-05:00' '#required': 'Custom required error' '#required_error': 'Custom required error' + datelist_multiple: + '#type': datelist + '#title': datelist_multiple + '#default_value': '2009-08-18T01:00:00-05:00' + '#multiple': true + datelist_custom_composite: + '#type': webform_custom_composite + '#title': datelist_custom_composite + '#element': + datelist: + '#type': datelist + '#title': datelist + '#default_value': + - datelist: '2009-08-18T01:00:00-05:00' css: '' javascript: '' settings: @@ -63,6 +103,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -70,6 +111,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -85,22 +127,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -111,6 +168,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -129,9 +187,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -184,4 +244,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datetime.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datetime.yml index 98a2a3189b..483106b105 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datetime.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_datetime.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_datetime title: 'Test: Element: Date/time' description: 'Test date/time element' @@ -45,16 +47,23 @@ elements: | datetime_year_range: '#type': datetime '#title': datetime_year_range - '#descripion': '(-10:+1)' + '#description': '(-10:+1)' '#default_value': '2009-08-18T01:00:00-05:00' '#date_year_range': '-10:+1' datetime_min_max: '#type': datetime '#title': datetime_min_max '#description': '(2009-01-01 to 2009-12-31)' - '#min': '2009-01-01' - '#max': '2009-12-31' + '#date_date_min': '2009-01-01' + '#date_date_max': '2009-12-31' '#default_value': '2009-08-18' + datetime_min_max_time: + '#type': datetime + '#title': datetime_min_max_time + '#description': '(2009-01-01 09:00:00 to 2009-12-31 17:00:00)' + '#date_min': '2009-01-01 09:00:00' + '#date_max': '2009-12-31 17:00:00' + '#default_value': '2009-01-01 09:00:00' datetime_datepicker_timepicker: '#type': datetime '#title': datetime_datepicker_timepicker @@ -63,12 +72,26 @@ elements: | '#date_date_format': 'D, m/d/Y' '#date_time_element': timepicker '#date_time_format': 'g:i A' + datetime_datepicker_time_text: + '#type': datetime + '#title': datetime_datepicker_time_text + '#default_value': '2009-08-18T01:00:00-05:00' + '#date_time_element': text + datetime_datepicker_timepicker_button: + '#type': datetime + '#title': datetime_datepicker_timepicker_button + '#default_value': '2009-08-18T01:00:00-05:00' + '#date_date_element': datepicker + '#date_date_datepicker_button': true + '#date_date_format': 'D, m/d/Y' + '#date_time_element': timepicker + '#date_time_format': 'g:i A' datetime_time_min_max: '#type': datetime '#title': datetime_time_min_max '#description': '(2009-01-01 to 2009-12-31 and 09:00:00 to 17:00:00)' - '#min': '2009-01-01' - '#max': '2009-12-31' + '#date_date_min': '2009-01-01' + '#date_date_max': '2009-12-31' '#date_time_min': '09:00:00' '#date_time_max': '17:00:00' '#date_time_step': '1800' @@ -77,8 +100,8 @@ elements: | '#type': datetime '#title': datetime_datepicker_timepicker_time_min_max '#description': '(2009-01-01 to 2009-12-31 and 09:00:00 to 17:00:00)' - '#min': '2009-01-01' - '#max': '2009-12-31' + '#date_date_min': '2009-01-01' + '#date_date_max': '2009-12-31' '#date_time_min': '09:00:00' '#date_time_max': '17:00:00' '#date_time_step': '1800' @@ -91,13 +114,38 @@ elements: | '#type': datetime '#title': datetime_no_seconds '#description': | - @see <a href="https://www.drupal.org/node/2917107">Issue #2917107: Date and Time validation problem</a><b r/> - @see <a href="https://www.drupal.org/node/2723159">Issue #2723159: Datetime form element cannot validate when using a format without seconds</a><b r/> + @see <a href="https://www.drupal.org/node/2917107">Issue #2917107: Date and Time validation problem</a><br/> + @see <a href="https://www.drupal.org/node/2723159">Issue #2723159: Datetime form element cannot validate when using a format without seconds</a><br/> + '#date_date_element': datepicker '#date_time_element': text '#date_time_format': 'H:i' '#date_time_step': '900' '#default_value': '2009-08-18T01:00:00-05:00' + datetime_multiple: + '#type': datetime + '#title': datetime_multiple + '#default_value': '2009-08-18T01:00:00-05:00' + '#date_date_element': datepicker + '#date_date_format': 'D, m/d/Y' + '#date_time_element': timepicker + '#date_time_format': 'g:i A' + '#multiple': true + datetime_custom_composite: + '#type': webform_custom_composite + '#title': datetime_custom_composite + '#element': + datetime: + '#type': datetime + '#title': datetime + '#date_date_element': datepicker + '#date_date_format': d/m/Y + '#date_time_element': timepicker + '#date_time_format': 'g:i A' + '#date_time_step': '900' + '#autocomplete': 'off' + '#default_value': + - datetime: '2009-08-18T01:00:00-05:00' css: '' javascript: '' settings: @@ -106,6 +154,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -113,6 +162,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -128,22 +178,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -154,6 +219,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -172,9 +238,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -227,4 +295,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_description_tooltip.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_description_tooltip.yml index e7c43d746b..949bd917f1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_description_tooltip.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_description_tooltip.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_description_tooltip title: 'Test: Element: Description tooltip' description: 'Test element eescription tooltip.' @@ -105,7 +107,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' '#description': 'This is a description for the ''range'' element.' @@ -141,18 +143,52 @@ elements: | '#title': URL '#description': 'This is a description for the ''url'' element.' '#description_display': tooltip + file_upload_elements: + '#type': details + '#title': 'File upload elements' + '#open': true + webform_audio_file: + '#type': webform_audio_file + '#title': 'Audio file' + '#description': 'This is a description for the ''webform_audio_file'' element.' + '#description_display': tooltip + webform_document_file: + '#type': webform_document_file + '#title': 'Document file' + '#description': 'This is a description for the ''webform_document_file'' element.' + '#description_display': tooltip + managed_file: + '#type': managed_file + '#title': File + '#description': 'This is a description for the ''managed_file'' element.' + '#description_display': tooltip + webform_image_file: + '#type': webform_image_file + '#title': 'Image file' + '#description': 'This is a description for the ''webform_image_file'' element.' + '#description_display': tooltip + webform_video_file: + '#type': webform_video_file + '#title': 'Video file' + '#description': 'This is a description for the ''webform_video_file'' element.' + '#description_display': tooltip composite_elements: '#type': details '#title': 'Composite elements' '#open': true webform_address: '#type': webform_address - '#title': Address + '#title': 'Basic address' '#description': 'This is a description for the ''webform_address'' element.' '#description_display': tooltip + webform_contact: + '#type': webform_contact + '#title': Contact + '#description': 'This is a description for the ''webform_contact'' element.' + '#description_display': tooltip webform_custom_composite: '#type': webform_custom_composite - '#title': 'Composite custom' + '#title': 'Custom composite' '#element': name: '#type': textfield @@ -165,25 +201,20 @@ elements: | '#options': Male: Male Female: Female - '#description': 'This is a description for the ''webform_composite'' element.' - '#description_display': tooltip - webform_contact: - '#type': webform_contact - '#title': Contact - '#description': 'This is a description for the ''webform_contact'' element.' + '#description': 'This is a description for the ''webform_custom_composite'' element.' '#description_display': tooltip webform_link: '#type': webform_link '#title': Link '#description': 'This is a description for the ''webform_link'' element.' '#description_display': tooltip - webform_location: - '#type': webform_location + webform_location_geocomplete: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true '#format': map - '#description': 'This is a description for the ''webform_location'' element.' + '#description': 'This is a description for the ''webform_location_geocomplete'' element.' '#description_display': tooltip webform_name: '#type': webform_name @@ -195,35 +226,6 @@ elements: | '#title': 'Telephone advanced' '#description': 'This is a description for the ''webform_telephone'' element.' '#description_display': tooltip - file_upload_elements: - '#type': details - '#title': 'File upload elements' - '#open': true - webform_audio_file: - '#type': webform_audio_file - '#title': 'Audio file' - '#description': 'This is a description for the ''webform_audio_file'' element.' - '#description_display': tooltip - webform_document_file: - '#type': webform_document_file - '#title': 'Document file' - '#description': 'This is a description for the ''webform_document_file'' element.' - '#description_display': tooltip - managed_file: - '#type': managed_file - '#title': File - '#description': 'This is a description for the ''managed_file'' element.' - '#description_display': tooltip - webform_image_file: - '#type': webform_image_file - '#title': 'Image file' - '#description': 'This is a description for the ''webform_image_file'' element.' - '#description_display': tooltip - webform_video_file: - '#type': webform_video_file - '#title': 'Video file' - '#description': 'This is a description for the ''webform_video_file'' element.' - '#description_display': tooltip options_elements: '#type': details '#title': 'Options elements' @@ -349,15 +351,15 @@ elements: | webform_computed_token: '#type': webform_computed_token '#title': 'Computed token' - '#value': 'This is a Computed token value.' '#description': 'This is a description for the ''webform_computed_token'' element.' '#description_display': tooltip + '#template': 'This is a Computed token value.' webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig' - '#value': 'This is a Computed Twig value.' '#description': 'This is a description for the ''webform_computed_twig'' element.' '#description_display': tooltip + '#template': 'This is a Computed Twig value.' date_time_elements: '#type': details '#title': 'Date/time elements' @@ -479,6 +481,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -486,6 +489,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -501,22 +505,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -527,6 +546,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -545,9 +565,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -600,4 +622,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_details.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_details.yml new file mode 100644 index 0000000000..0d71c8c39a --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_details.yml @@ -0,0 +1,202 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_details +title: 'Test: Element: Details' +description: 'Test the details element.' +category: 'Test: Element' +elements: | + details: + '#type': details + '#title': details + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#open': true + details_markup: + '#markup': '<p>This is some markup</p>' + details_title_invisible: + '#type': details + '#title': 'Details title invisible' + '#description': 'This is a description.' + '#title_display': invisible + details_title_invisible_markup: + '#markup': '<p>This is some markup</p>' + details_more: + '#type': details + '#title': 'details more' + '#more': 'This is more text' + '#open': true + details_more_markup: + '#markup': '<p>This is some markup</p>' + details_help: + '#type': details + '#title': 'details help' + '#help': 'This is help text.' + '#open': true + details_help_markup: + '#markup': '<p>This is some markup</p>' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_disabled.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_disabled.yml index a31788cf30..12cb8f4ce3 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_disabled.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_disabled.yml @@ -6,17 +6,15 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_disabled title: 'Test: Element disabled' description: 'Test disabling element.' category: 'Test: Element' elements: | - webform_address: - '#type': webform_address - '#title': Address - '#disabled': true webform_audio_file: '#type': webform_audio_file '#title': 'Audio file' @@ -25,6 +23,10 @@ elements: | '#type': webform_autocomplete '#title': Autocomplete '#disabled': true + webform_address: + '#type': webform_address + '#title': 'Basic address' + '#disabled': true webform_buttons: '#type': webform_buttons '#title': Buttons @@ -73,9 +75,13 @@ elements: | '#type': color '#title': Color '#disabled': true + webform_contact: + '#type': webform_contact + '#title': Contact + '#disabled': true webform_custom_composite: '#type': webform_custom_composite - '#title': 'Composite custom' + '#title': 'Custom composite' '#element': name: '#type': textfield @@ -89,10 +95,6 @@ elements: | Male: Male Female: Female '#disabled': true - webform_contact: - '#type': webform_contact - '#title': Contact - '#disabled': true date: '#type': date '#title': Date @@ -195,8 +197,8 @@ elements: | '#type': webform_link '#title': Link '#disabled': true - webform_location: - '#type': webform_location + webform_location_geocomplete: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true @@ -263,7 +265,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' '#disabled': true @@ -384,6 +386,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -391,6 +394,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -406,22 +410,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -432,6 +451,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -450,9 +470,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -505,4 +527,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_email.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_email.yml index e5696d95d6..701ef805dc 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_email.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_email.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_email title: 'Test: Element: Email' description: 'Tests email_confirm and email_multiple elements.' @@ -58,6 +60,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -65,6 +68,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -80,22 +84,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -106,6 +125,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -124,9 +144,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -179,6 +201,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_entity_reference.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_entity_reference.yml index 747041f8ad..9eb120f994 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_entity_reference.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_entity_reference.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_entity_reference title: 'Test: Element: Entity reference' description: 'Test entity reference elements.' @@ -122,6 +124,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -129,6 +132,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -144,22 +148,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -170,6 +189,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -188,9 +208,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -243,6 +265,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_fieldset.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_fieldset.yml new file mode 100644 index 0000000000..caec13c52e --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_fieldset.yml @@ -0,0 +1,211 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_fieldset +title: 'Test: Element: Fieldset' +description: 'Test the fieldset element.' +category: 'Test: Element' +elements: | + fieldset: + '#type': fieldset + '#title': fieldset + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + fieldset_markup: + '#markup': '<p>This is some markup</p>' + fieldset_title_invisible: + '#type': fieldset + '#title': fieldset_title_invisible + '#description': 'This is a description.' + '#title_display': invisible + fieldset_description_before: + '#type': fieldset + '#title': fieldset_description_before + '#description': 'This is a description before.' + '#description_display': before + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + fieldset_markup: + '#markup': '<p>This is some markup</p>' + fieldset_description_tooltip: + '#type': fieldset + '#title': fieldset_description_tooltip + '#description': 'This is a description tooltip.' + '#description_display': tooltip + '#help': 'This is help text.' + '#more': 'This is more text' + '#required': true + '#field_prefix': prefix + '#field_suffix': suffix + fieldset_markup: + '#markup': '<p>This is some markup</p>' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox.yml index e7ba4c0217..9f20ad856f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox.yml @@ -6,31 +6,15 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_flexbox title: 'Test: Element: Flexbox' description: 'Test flexbox layout.' category: 'Test: Element' elements: | - flexbox_processed_text: - '#type': webform_flexbox - flex_processed_text_left: - '#type': processed_text - '#title': 'Advanced HTML/Text 01' - flex_processed_text_right: - '#type': processed_text - '#title': 'Advanced HTML/Text 02' - flexbox_webform_markup: - '#type': webform_flexbox - flex_webform_markup_left: - '#type': webform_markup - '#title': 'Basic HTML 01' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' - flex_webform_markup_right: - '#type': webform_markup - '#title': 'Basic HTML 02' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' flexbox_container: '#type': webform_flexbox flex_container_left: @@ -57,44 +41,6 @@ elements: | flex_fieldset_right: '#type': fieldset '#title': 'Fieldset 02' - flexbox_item: - '#type': webform_flexbox - flex_item_left: - '#type': item - '#title': 'Item 01' - '#markup': '{markup}' - '#field_prefix': '{field_prefix}' - '#field_suffix': '{field_suffix}' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' - flex_item_right: - '#type': item - '#title': 'Item 02' - '#markup': '{markup}' - '#field_prefix': '{field_prefix}' - '#field_suffix': '{field_suffix}' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' - flexbox_label: - '#type': webform_flexbox - flex_label_left: - '#type': label - '#title': 'Label 01' - flex_label_right: - '#type': label - '#title': 'Label 02' - flexbox_webform_message: - '#type': webform_flexbox - flex_webform_message_left: - '#type': webform_message - '#title': 'Message 01' - '#message_type': warning - '#message_message': 'This is a <strong>warning</strong> message.' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' - flex_webform_message_right: - '#type': webform_message - '#title': 'Message 02' - '#message_type': warning - '#message_message': 'This is a <strong>warning</strong> message.' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' flexbox_webform_section: '#type': webform_flexbox flex_webform_section_left: @@ -111,14 +57,22 @@ elements: | flex_table_right: '#type': table '#title': 'Table 02' - flexbox_webform_address: + flexbox_address: + '#type': webform_flexbox + flex_address_left: + '#type': address + '#title': 'Advanced address 01' + flex_address_right: + '#type': address + '#title': 'Advanced address 02' + flexbox_processed_text: '#type': webform_flexbox - flex_webform_address_left: - '#type': webform_address - '#title': 'Address 01' - flex_webform_address_right: - '#type': webform_address - '#title': 'Address 02' + flex_processed_text_left: + '#type': processed_text + '#title': 'Advanced HTML/Text 01' + flex_processed_text_right: + '#type': processed_text + '#title': 'Advanced HTML/Text 02' flexbox_webform_audio_file: '#type': webform_flexbox flex_webform_audio_file_left: @@ -135,6 +89,24 @@ elements: | flex_webform_autocomplete_right: '#type': webform_autocomplete '#title': 'Autocomplete 02' + flexbox_webform_address: + '#type': webform_flexbox + flex_webform_address_left: + '#type': webform_address + '#title': 'Basic address 01' + flex_webform_address_right: + '#type': webform_address + '#title': 'Basic address 02' + flexbox_webform_markup: + '#type': webform_flexbox + flex_webform_markup_left: + '#type': webform_markup + '#title': 'Basic HTML 01' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' + flex_webform_markup_right: + '#type': webform_markup + '#title': 'Basic HTML 02' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' flexbox_webform_buttons: '#type': webform_flexbox flex_webform_buttons_left: @@ -243,11 +215,39 @@ elements: | flex_color_right: '#type': color '#title': 'Color 02' - flexbox_webform_composite: + flexbox_webform_computed_token: '#type': webform_flexbox - flex_webform_composite_left: + flex_webform_computed_token_left: + '#type': webform_computed_token + '#title': 'Computed token 01' + '#template': 'This is a Computed token value.' + flex_webform_computed_token_right: + '#type': webform_computed_token + '#title': 'Computed token 02' + '#template': 'This is a Computed token value.' + flexbox_webform_computed_twig: + '#type': webform_flexbox + flex_webform_computed_twig_left: + '#type': webform_computed_twig + '#title': 'Computed Twig 01' + '#template': 'This is a Computed Twig value.' + flex_webform_computed_twig_right: + '#type': webform_computed_twig + '#title': 'Computed Twig 02' + '#template': 'This is a Computed Twig value.' + flexbox_webform_contact: + '#type': webform_flexbox + flex_webform_contact_left: + '#type': webform_contact + '#title': 'Contact 01' + flex_webform_contact_right: + '#type': webform_contact + '#title': 'Contact 02' + flexbox_webform_custom_composite: + '#type': webform_flexbox + flex_webform_custom_composite_left: '#type': webform_custom_composite - '#title': 'Composite custom 01' + '#title': 'Custom composite 01' '#element': name: '#type': textfield @@ -260,9 +260,9 @@ elements: | '#options': Male: Male Female: Female - flex_webform_composite_right: + flex_webform_custom_composite_right: '#type': webform_custom_composite - '#title': 'Composite custom 02' + '#title': 'Custom composite 02' '#element': name: '#type': textfield @@ -275,34 +275,6 @@ elements: | '#options': Male: Male Female: Female - flexbox_webform_computed_token: - '#type': webform_flexbox - flex_webform_computed_token_left: - '#type': webform_computed_token - '#title': 'Computed token 01' - '#value': 'This is a Computed token value.' - flex_webform_computed_token_right: - '#type': webform_computed_token - '#title': 'Computed token 02' - '#value': 'This is a Computed token value.' - flexbox_webform_computed_twig: - '#type': webform_flexbox - flex_webform_computed_twig_left: - '#type': webform_computed_twig - '#title': 'Computed Twig 01' - '#value': 'This is a Computed Twig value.' - flex_webform_computed_twig_right: - '#type': webform_computed_twig - '#title': 'Computed Twig 02' - '#value': 'This is a Computed Twig value.' - flexbox_webform_contact: - '#type': webform_flexbox - flex_webform_contact_left: - '#type': webform_contact - '#title': 'Contact 01' - flex_webform_contact_right: - '#type': webform_contact - '#title': 'Contact 02' flexbox_date: '#type': webform_flexbox flex_date_left: @@ -467,14 +439,12 @@ elements: | class: - webform-horizontal-rule--dotted - webform-horizontal-rule--thick - '#title': 'webform_horizontal_rule 01' flex_webform_horizontal_rule_right: '#type': webform_horizontal_rule '#attributes': class: - webform-horizontal-rule--dotted - webform-horizontal-rule--thick - '#title': 'webform_horizontal_rule 02' flexbox_webform_image_file: '#type': webform_flexbox flex_webform_image_file_left: @@ -513,6 +483,30 @@ elements: | bear_3: text: 'Bear 3' src: 'https://www.placebear.com/120/100' + flexbox_item: + '#type': webform_flexbox + flex_item_left: + '#type': item + '#title': 'Item 01' + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + flex_item_right: + '#type': item + '#title': 'Item 02' + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + flexbox_label: + '#type': webform_flexbox + flex_label_left: + '#type': label + '#title': 'Label 01' + flex_label_right: + '#type': label + '#title': 'Label 02' flexbox_language_select: '#type': webform_flexbox flex_language_select_left: @@ -553,16 +547,16 @@ elements: | flex_webform_link_right: '#type': webform_link '#title': 'Link 02' - flexbox_webform_location: + flexbox_webform_location_places: '#type': webform_flexbox - flex_webform_location_left: - '#type': webform_location + flex_webform_location_places_left: + '#type': webform_location_places '#title': 'Location 01' '#map': true '#geolocation': true '#format': map - flex_webform_location_right: - '#type': webform_location + flex_webform_location_places_right: + '#type': webform_location_places '#title': 'Location 02' '#map': true '#geolocation': true @@ -599,6 +593,20 @@ elements: | four: Four five: Five six: Six + flexbox_webform_message: + '#type': webform_flexbox + flex_webform_message_left: + '#type': webform_message + '#title': 'Message 01' + '#message_type': warning + '#message_message': 'This is a <strong>warning</strong> message.' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + flex_webform_message_right: + '#type': webform_message + '#title': 'Message 02' + '#message_type': warning + '#message_message': 'This is a <strong>warning</strong> message.' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' flexbox_webform_name: '#type': webform_flexbox flex_webform_name_left: @@ -685,7 +693,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' flex_range_right: @@ -694,7 +702,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' flexbox_webform_rating: @@ -966,6 +974,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -973,6 +982,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -988,22 +998,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -1014,6 +1039,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -1032,9 +1058,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1087,4 +1115,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox_flex.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox_flex.yml index 4acd4eec77..6cf3a6ac0c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox_flex.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_flexbox_flex.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_flexbox_flex title: 'Test: Element: Flexbox flex ' description: 'Test flexbox flex containers.' @@ -811,6 +813,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -818,6 +821,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -833,22 +837,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -859,6 +878,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -877,9 +897,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -932,4 +954,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format.yml index 53e2055781..721c55f5a5 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_format title: 'Test: Element: Format' description: 'Test element formatting.' @@ -556,30 +558,26 @@ elements: | webform_computed_token_value: '#type': webform_computed_token '#title': 'Computed token (Value)' - '#value': 'This is a Computed token value.' - '#default_value': Loremipsum '#format': value + '#template': 'This is a Computed token value.' webform_computed_token_raw: '#type': webform_computed_token '#title': 'Computed token (Raw value)' - '#value': 'This is a Computed token value.' - '#default_value': Loremipsum '#format': raw + '#template': 'This is a Computed token value.' webform_computed_twig: '#type': details '#title': 'Computed Twig' webform_computed_twig_value: '#type': webform_computed_twig '#title': 'Computed Twig (Value)' - '#value': 'This is a Computed Twig value.' - '#default_value': Loremipsum '#format': value + '#template': 'This is a Computed Twig value.' webform_computed_twig_raw: '#type': webform_computed_twig '#title': 'Computed Twig (Raw value)' - '#value': 'This is a Computed Twig value.' - '#default_value': Loremipsum '#format': raw + '#template': 'This is a Computed Twig value.' date_time_elements: '#type': details '#title': 'Date/time elements' @@ -597,16 +595,21 @@ elements: | '#title': 'Date (Raw value)' '#default_value': '1942-06-18' '#format': raw - date_fallback: - '#type': date - '#title': 'Date (Fallback date format)' - '#default_value': '1942-06-18' - '#format': fallback date_html_date: '#type': date '#title': 'Date (HTML Date)' '#default_value': '1942-06-18' '#format': html_date + date_html_time: + '#type': date + '#title': 'Date (HTML Time)' + '#default_value': '1942-06-18' + '#format': html_time + date_fallback: + '#type': date + '#title': 'Date (Fallback date format)' + '#default_value': '1942-06-18' + '#format': fallback date_html_datetime: '#type': date '#title': 'Date (HTML Datetime)' @@ -617,11 +620,6 @@ elements: | '#title': 'Date (HTML Month)' '#default_value': '1942-06-18' '#format': html_month - date_html_time: - '#type': date - '#title': 'Date (HTML Time)' - '#default_value': '1942-06-18' - '#format': html_time date_html_week: '#type': date '#title': 'Date (HTML Week)' @@ -815,7 +813,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': value entity_autocomplete_raw: '#type': entity_autocomplete @@ -824,7 +822,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': raw entity_autocomplete_link: '#type': entity_autocomplete @@ -833,7 +831,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': link entity_autocomplete_id: '#type': entity_autocomplete @@ -842,7 +840,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': id entity_autocomplete_label: '#type': entity_autocomplete @@ -851,7 +849,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': label entity_autocomplete_text: '#type': entity_autocomplete @@ -860,7 +858,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': text entity_autocomplete_teaser: '#type': entity_autocomplete @@ -869,7 +867,7 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 + '#default_value': 1 '#format': teaser entity_autocomplete_default: '#type': entity_autocomplete @@ -878,8 +876,8 @@ elements: | '#selection_handler': 'default:user' '#selection_settings': include_anonymous: true - '#default_value': 5 - '#format': default + '#default_value': 1 + '#format': _default webform_entity_radios: '#type': details '#title': 'Entity radios' @@ -986,7 +984,7 @@ elements: | 1: Administrator 0: Anonymous '#default_value': 1 - '#format': default + '#format': _default webform_entity_select: '#type': details '#title': 'Entity select' @@ -1085,7 +1083,7 @@ elements: | 1: Administrator 0: Anonymous '#default_value': 1 - '#format': default + '#format': _default webform_term_select: '#type': details '#title': 'Term select' @@ -1093,55 +1091,55 @@ elements: | '#type': webform_term_select '#title': 'Term select (Value)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': value webform_term_select_raw: '#type': webform_term_select '#title': 'Term select (Raw value)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': raw webform_term_select_link: '#type': webform_term_select '#title': 'Term select (Link)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': link webform_term_select_id: '#type': webform_term_select '#title': 'Term select (Entity ID)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': id webform_term_select_label: '#type': webform_term_select '#title': 'Term select (Label)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': label webform_term_select_text: '#type': webform_term_select '#title': 'Term select (Label (ID))' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': text webform_term_select_teaser: '#type': webform_term_select '#title': 'Term select (Teaser)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': teaser webform_term_select_default: '#type': webform_term_select '#title': 'Term select (Default)' '#vocabulary': tags - '#default_value': 3 - '#format': default + '#default_value': Loremipsum + '#format': _default webform_term_select_breadcrumb: '#type': webform_term_select '#title': 'Term select (Breadcrumb)' '#vocabulary': tags - '#default_value': 3 + '#default_value': Loremipsum '#format': breadcrumb file_upload_elements: '#type': details @@ -1170,21 +1168,21 @@ elements: | '#title': 'File (Link)' '#file_extensions': txt '#format': link - managed_file_id: + managed_file_url: '#type': managed_file - '#title': 'File (File ID)' + '#title': 'File (URL)' '#file_extensions': txt - '#format': id + '#format': url managed_file_name: '#type': managed_file '#title': 'File (File name)' '#file_extensions': txt '#format': name - managed_file_url: + managed_file_id: '#type': managed_file - '#title': 'File (URL)' + '#title': 'File (File ID)' '#file_extensions': txt - '#format': url + '#format': id other_elements: '#type': details '#title': 'Other elements' @@ -1210,6 +1208,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -1217,6 +1216,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -1232,22 +1232,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -1258,6 +1273,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -1276,9 +1292,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1331,6 +1349,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_text: id: email @@ -1342,23 +1364,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -1374,23 +1398,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_custom.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_custom.yml index ea58e9b865..e87a516e06 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_custom.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_custom.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_format_custom title: 'Test: Element: Format custom' description: 'Test element custom formatting.' @@ -19,9 +21,33 @@ elements: | '#format': custom '#format_html': | <em>{{ value }}</em> + '#format_text': | /{{ value }}/ + '#default_value': '{textfield_custom}' + textfield_custom_token: + '#type': textfield + '#title': textfield_custom_token + '#format': custom + '#format_html': | + <em>[webform_submission:values:textfield_custom_token:raw]</em> + + '#format_text': | + /[webform_submission:values:textfield_custom_token:raw]/ + + '#default_value': '{textfield_custom_token}' + textfield_custom_token_exception: + '#type': textfield + '#title': textfield_custom_token_exception + '#format': custom + '#format_html': | + <em>EXCEPTION[webform_submission:values:textfield_custom_token_exception]</em> + + '#format_text': | + /EXCEPTION[webform_submission:values:textfield_custom_token_exception]/ + + '#default_value': '{textfield_custom_token_exception}' textfield_custom_value_multiple: '#type': textfield '#title': textfield_custom @@ -29,8 +55,10 @@ elements: | '#format': custom '#format_html': | <em>{{ value }}</em> + '#format_text': | /{{ value }}/ + '#format_items': custom '#format_items_html': | <table> @@ -38,10 +66,12 @@ elements: | <tr {% if loop.index is divisible by(2) %}style="background-color: #ffc"{% endif %}><td>{{ item }}</td></tr> {% endfor %} </table> + '#format_items_text': | {% for item in items %} ⦿ {{ item }} {% endfor %} + '#default_value': - One - Two @@ -62,6 +92,7 @@ elements: | item['original:image']: <div style="width: 100px">{{ item['original:image'] }}</div> item['original:link']: <div style="width: 100px">{{ item['original:link'] }}</div> item['original:modal']: <div style="width: 100px">{{ item['original:modal'] }}</div> + '#format_text': | value: {{ value }} item['value']: {{ item['value'] }} @@ -69,6 +100,7 @@ elements: | item['link']: {{ item['link'] }} item['id']: {{ item['id'] }} item['url']: {{ item['url'] }} + address_custom: '#type': webform_address '#title': address_custom @@ -80,6 +112,7 @@ elements: | element.state_province: {{ element.state_province }}<br/> element.postal_code: {{ element.postal_code }}<br/> element.country: {{ element.country }}<br/> + '#format_text': | element.address: {{ element.address }} element.address_2: {{ element.address_2 }} @@ -87,6 +120,7 @@ elements: | element.state_province: {{ element.state_province }} element.postal_code: {{ element.postal_code }} element.country: {{ element.country }} + '#state_province__type': webform_select_other '#country__type': webform_select_other '#default_value': @@ -96,14 +130,67 @@ elements: | state_province: '{state_province}' postal_code: '{postal_code}' country: '{country}' + address_multiple_custom: + '#type': webform_address + '#title': address_multiple_custom + '#multiple': true + '#default_value': + - address: '{01-address}' + address_2: '{01-address_2}' + city: '{01-city}' + state_province: '{01-state_province}' + postal_code: '{01-postal_code}' + country: '{01-country}' + - address: '{02-address}' + address_2: '{02-address_2}' + city: '{02-city}' + state_province: '{02-state_province}' + postal_code: '{02-postal_code}' + country: '{02-country}' + '#format': custom + '#format_html': | + element.address: {{ element.address }}<br/> + element.address_2: {{ element.address_2 }}<br/> + element.city: {{ element.city }}<br/> + element.state_province: {{ element.state_province }}<br/> + element.postal_code: {{ element.postal_code }}<br/> + element.country: {{ element.country }}<br/> + + '#format_text': | + element.address: {{ element.address }} + element.address_2: {{ element.address_2 }} + element.city: {{ element.city }} + element.state_province: {{ element.state_province }} + element.postal_code: {{ element.postal_code }} + element.country: {{ element.country }} + + '#format_items': custom + '#format_items_html': | + {% for item in items %} + <div>*****</div> + {{ item }} + <div>*****</div> + {% endfor %} + + '#format_items_text': | + {% for item in items %} + ***** + {{ item }} + ***** + {% endfor %} + + '#state_province__type': webform_select_other + '#country__type': webform_select_other fieldset_custom: '#type': fieldset '#title': fieldset_custom '#format': custom '#format_html': | {{ item.details }} + '#format_text': | {{ item.details }} + fieldset_custom_textfield: '#type': textfield '#title': fieldset_custom_textfield @@ -116,10 +203,12 @@ elements: | <h3>fieldset_custom_children</h3> <hr /> {{ children }} + '#format_text': | fieldset_custom_children ------------------------ {{ children }} + fieldset_custom_children_textfield: '#type': textfield '#title': fieldset_custom_children_textfield @@ -132,6 +221,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -139,6 +229,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -154,22 +245,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -180,6 +286,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -198,9 +305,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -253,4 +362,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_multiple.yml index a7b2eed674..0a88507cf9 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_multiple.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_format_multiple title: 'Test: Element: Format multiple values' description: 'Test element formatting with multiple value.' @@ -1476,9 +1478,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': comma webform_term_checkboxes_semicolon: '#type': webform_term_checkboxes @@ -1486,9 +1488,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': semicolon webform_term_checkboxes_and: '#type': webform_term_checkboxes @@ -1496,9 +1498,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': and webform_term_checkboxes_ol: '#type': webform_term_checkboxes @@ -1506,9 +1508,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': ol webform_term_checkboxes_ul: '#type': webform_term_checkboxes @@ -1516,9 +1518,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': ul webform_term_select: '#type': details @@ -1529,9 +1531,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': comma webform_term_select_semicolon: '#type': webform_term_select @@ -1539,9 +1541,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': semicolon webform_term_select_and: '#type': webform_term_select @@ -1549,9 +1551,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': and webform_term_select_ol: '#type': webform_term_select @@ -1559,9 +1561,9 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#format_items': ol webform_term_select_ul: '#type': webform_term_select @@ -1569,9 +1571,46 @@ elements: | '#vocabulary': tags '#multiple': true '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset + '#format_items': ul + file_upload_elements: + '#type': details + '#title': 'File upload elements' + '#open': true + managed_file: + '#type': details + '#title': File + managed_file_comma: + '#type': managed_file + '#title': 'File (Comma)' + '#multiple': true + '#file_extensions': txt + '#format_items': comma + managed_file_semicolon: + '#type': managed_file + '#title': 'File (Semicolon)' + '#multiple': true + '#file_extensions': txt + '#format_items': semicolon + managed_file_and: + '#type': managed_file + '#title': 'File (And)' + '#multiple': true + '#file_extensions': txt + '#format_items': and + managed_file_ol: + '#type': managed_file + '#title': 'File (Ordered list)' + '#multiple': true + '#file_extensions': txt + '#format_items': ol + managed_file_ul: + '#type': managed_file + '#title': 'File (Unordered list)' + '#multiple': true + '#file_extensions': txt '#format_items': ul css: '' javascript: '' @@ -1581,6 +1620,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -1588,6 +1628,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -1603,22 +1644,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -1629,6 +1685,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -1647,9 +1704,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1702,6 +1761,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_text: id: email @@ -1713,23 +1776,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -1745,23 +1810,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_token.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_token.yml index c70c8fea61..1cb58ade5b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_token.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_format_token.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_format_token title: 'Test: Element: Format: Token' description: 'Test element formatting using tokens.' @@ -32,6 +34,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -39,6 +42,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -54,22 +58,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -80,6 +99,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -98,9 +118,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -153,6 +175,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_html: id: email @@ -164,16 +190,16 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default + from_name: _default + subject: _default body: | <h3>default:</h3>[webform_submission:values:checkboxes]<hr /> <h3>comma:</h3>[webform_submission:values:checkboxes:value:comma]<hr /> @@ -182,12 +208,15 @@ handlers: <h3>ul:</h3>[webform_submission:values:checkboxes:value:ul]<hr /> <h3>ol:</h3>[webform_submission:values:checkboxes:value:ol]<hr /> <h3>raw:</h3>[webform_submission:values:checkboxes:raw]<hr /> + excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -203,16 +232,16 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default + from_name: _default + subject: _default body: | default: [webform_submission:values:checkboxes] @@ -234,12 +263,15 @@ handlers: raw: [webform_submission:values:checkboxes:raw] + excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_help.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_help.yml index 16de96fe5c..d5dad12f11 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_help.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_help.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_help title: 'Test: Element: Help' description: 'Test element #help property.' @@ -22,6 +24,11 @@ elements: | '#title': help_required '#required': true '#help': '{This is an example of help for a required element}' + help_title: + '#type': textfield + '#title': help_title + '#help': '{This is an example of help with a custom help title}' + '#help_title': '{Help custom title}' help_html: '#type': textfield '#title': help_html @@ -71,6 +78,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -78,6 +86,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -93,22 +102,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -119,6 +143,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -137,9 +162,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -192,4 +219,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_horizontal_rule.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_horizontal_rule.yml index b49794da34..6889d5d61f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_horizontal_rule.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_horizontal_rule.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_horizontal_rule title: 'Test: Element: Horizontal rule' description: 'Test the horizontal rule element.' @@ -107,6 +109,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -114,6 +117,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -129,22 +133,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -155,6 +174,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -173,9 +193,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -228,6 +250,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_editor.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_editor.yml index a96234f183..9f6dc72148 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_editor.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_editor.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_html_editor title: 'Test: Element: HTML editor' description: 'Test HTML editor element.' @@ -24,12 +26,15 @@ elements: | '#default_value': 'Hello <b>World!!!</b>' '#required': true '#disable': true + '#attributes': + class: + - custom-disabled webform_html_editor_format: '#type': webform_html_editor '#title': 'webform_html_editor (format)' '#required': true '#default_value': 'Hello <b>World!!!</b>' - '#format': 'basic_html' + '#format': basic_html webform_html_editor_codemirror: '#type': webform_html_editor '#title': 'webform_html_editor_codemirror (none)' @@ -44,6 +49,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -51,6 +57,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -66,22 +73,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -92,6 +114,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -110,9 +133,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -165,6 +190,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_escape.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_escape.yml index a7e5629f56..adee915d1b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_escape.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_escape.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_html_escape title: 'Test: Element: HTML escaping' description: 'Test element HTML escaping support' @@ -98,7 +100,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' webform_rating: @@ -158,9 +160,6 @@ elements: | '#message_type': warning '#message_message': 'This is a <strong>warning</strong> message.' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' - table: - '#type': table - '#title': 'Table | <script>alert(''This markup is not escaped properly!!!'') </script>' file_upload_elements: '#type': details '#title': 'File upload elements | <script>alert(''This markup is not escaped properly!!!'') </script>' @@ -350,11 +349,11 @@ elements: | webform_computed_token: '#type': webform_computed_token '#title': 'Computed token | <script>alert(''This markup is not escaped properly!!!'') </script>' - '#value': 'This is a Computed token value.' + '#template': 'This is a Computed token value.' webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig | <script>alert(''This markup is not escaped properly!!!'') </script>' - '#value': 'This is a Computed Twig value.' + '#template': 'This is a Computed Twig value.' containers: '#type': details '#title': 'Containers | <script>alert(''This markup is not escaped properly!!!'') </script>' @@ -376,9 +375,6 @@ elements: | '#field_prefix': '{field_prefix} | <script>alert(''This markup is not escaped properly!!!'') </script>' '#field_suffix': '{field_suffix} | <script>alert(''This markup is not escaped properly!!!'') </script>' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' - label: - '#type': label - '#title': 'Label | <script>alert(''This markup is not escaped properly!!!'') </script>' webform_section: '#type': webform_section '#title': 'Section | <script>alert(''This markup is not escaped properly!!!'') </script>' @@ -502,6 +498,13 @@ elements: | '#vocabulary': tags '#multiple': true '#select2': true + markup: + '#type': details + '#title': 'Markup | <script>alert(''This markup is not escaped properly!!!'') </script>' + '#open': true + label: + '#type': label + '#title': 'Label | <script>alert(''This markup is not escaped properly!!!'') </script>' other_elements: '#type': details '#title': 'Other elements | <script>alert(''This markup is not escaped properly!!!'') </script>' @@ -512,6 +515,9 @@ elements: | machine_name: '#type': machine_name '#title': 'Machine name | <script>alert(''This markup is not escaped properly!!!'') </script>' + table: + '#type': table + '#title': 'Table | <script>alert(''This markup is not escaped properly!!!'') </script>' css: '' javascript: '' settings: @@ -520,6 +526,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -527,6 +534,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -542,22 +550,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -568,6 +591,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -586,9 +610,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -641,4 +667,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_markup.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_markup.yml index 71599403d9..57d201ccf4 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_markup.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_html_markup.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_html_markup title: 'Test: Element: HTML Markup' description: 'Test element HTML markup support by underlining all titles, descriptions, prefixes, and suffixes.' @@ -98,7 +100,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' webform_rating: @@ -158,9 +160,6 @@ elements: | '#message_type': warning '#message_message': 'This is a <strong>warning</strong> message.' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' - table: - '#type': table - '#title': '<u>Table</u>' file_upload_elements: '#type': details '#title': '<u>File upload elements</u>' @@ -350,11 +349,11 @@ elements: | webform_computed_token: '#type': webform_computed_token '#title': '<u>Computed token</u>' - '#value': 'This is a Computed token value.' + '#template': 'This is a Computed token value.' webform_computed_twig: '#type': webform_computed_twig '#title': '<u>Computed Twig</u>' - '#value': 'This is a Computed Twig value.' + '#template': 'This is a Computed Twig value.' containers: '#type': details '#title': '<u>Containers</u>' @@ -376,9 +375,6 @@ elements: | '#field_prefix': '<u>{field_prefix}</u>' '#field_suffix': '<u>{field_suffix}</u>' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' - label: - '#type': label - '#title': '<u>Label</u>' webform_section: '#type': webform_section '#title': '<u>Section</u>' @@ -502,6 +498,13 @@ elements: | '#vocabulary': tags '#multiple': true '#select2': true + markup: + '#type': details + '#title': '<u>Markup</u>' + '#open': true + label: + '#type': label + '#title': '<u>Label</u>' other_elements: '#type': details '#title': '<u>Other elements</u>' @@ -512,6 +515,9 @@ elements: | machine_name: '#type': machine_name '#title': '<u>Machine name</u>' + table: + '#type': table + '#title': '<u>Table</u>' css: '' javascript: '' settings: @@ -520,6 +526,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -527,6 +534,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -542,22 +550,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -568,6 +591,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -586,9 +610,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -641,4 +667,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck.yml index 101daad88a..b91055d164 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_icheck title: 'Test: Element: iCheck' description: 'Test iCheck element.' @@ -63,12 +65,12 @@ elements: | 1: One 2: Two 3: Three - other: Other... + other: Other… '#icheck': flat radios_target: '#type': textfield '#attributes': - placeholder: 'Enter other...' + placeholder: 'Enter other…' '#states': visible: ':input[name="radios_trigger"]': @@ -84,8 +86,8 @@ elements: | '#type': textfield '#title': 'Please enter a value' checkbox_target: - '#type': 'checkboxes' - '#title': 'Checkboxes' + '#type': checkboxes + '#title': Checkboxes '#options': 1: One 2: Two @@ -103,6 +105,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -110,6 +113,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -125,22 +129,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -151,6 +170,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -169,9 +189,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -224,4 +246,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck_styles.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck_styles.yml index 099bfb2210..d1888e6653 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck_styles.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_icheck_styles.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_icheck_styles title: 'Test: Element: iCheck style' description: 'Test iCheck style.' @@ -27,7 +29,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal minimal_radios: '#type': radios @@ -49,7 +51,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-grey minimal_grey_radios: '#type': radios @@ -71,7 +73,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-yellow minimal_yellow_radios: '#type': radios @@ -93,7 +95,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-orange minimal_orange_radios: '#type': radios @@ -115,7 +117,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-red minimal_red_radios: '#type': radios @@ -137,7 +139,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-pink minimal_pink_radios: '#type': radios @@ -159,7 +161,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-purple minimal_purple_radios: '#type': radios @@ -181,7 +183,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-blue minimal_blue_radios: '#type': radios @@ -203,7 +205,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-green minimal_green_radios: '#type': radios @@ -225,7 +227,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': minimal-aero minimal_aero_radios: '#type': radios @@ -251,7 +253,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat flat_radios: '#type': radios @@ -273,7 +275,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-grey flat_grey_radios: '#type': radios @@ -295,7 +297,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-yellow flat_yellow_radios: '#type': radios @@ -317,7 +319,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-orange flat_orange_radios: '#type': radios @@ -339,7 +341,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-red flat_red_radios: '#type': radios @@ -361,7 +363,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-pink flat_pink_radios: '#type': radios @@ -383,7 +385,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-purple flat_purple_radios: '#type': radios @@ -405,7 +407,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-blue flat_blue_radios: '#type': radios @@ -427,7 +429,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-green flat_green_radios: '#type': radios @@ -449,7 +451,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': flat-aero flat_aero_radios: '#type': radios @@ -475,7 +477,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line line_radios: '#type': radios @@ -497,7 +499,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-grey line_grey_radios: '#type': radios @@ -519,7 +521,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-yellow line_yellow_radios: '#type': radios @@ -541,7 +543,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-orange line_orange_radios: '#type': radios @@ -563,7 +565,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-red line_red_radios: '#type': radios @@ -585,7 +587,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-pink line_pink_radios: '#type': radios @@ -607,7 +609,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-purple line_purple_radios: '#type': radios @@ -629,7 +631,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-blue line_blue_radios: '#type': radios @@ -651,7 +653,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-green line_green_radios: '#type': radios @@ -673,7 +675,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': line-aero line_aero_radios: '#type': radios @@ -699,7 +701,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square square_radios: '#type': radios @@ -721,7 +723,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-grey square_grey_radios: '#type': radios @@ -743,7 +745,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-yellow square_yellow_radios: '#type': radios @@ -765,7 +767,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-orange square_orange_radios: '#type': radios @@ -787,7 +789,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-red square_red_radios: '#type': radios @@ -809,7 +811,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-pink square_pink_radios: '#type': radios @@ -831,7 +833,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-purple square_purple_radios: '#type': radios @@ -853,7 +855,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-blue square_blue_radios: '#type': radios @@ -875,7 +877,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-green square_green_radios: '#type': radios @@ -897,7 +899,7 @@ elements: | three: Three '#default_value': one '#wrapper_attributes': - class: container-inline + - class: container-inline '#icheck': square-aero square_aero_radios: '#type': radios @@ -917,6 +919,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -924,6 +927,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -939,22 +943,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -965,6 +984,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -983,9 +1003,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1038,4 +1060,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_ignored_properties.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_ignored_properties.yml index df34449eef..7395ae4913 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_ignored_properties.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_ignored_properties.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_ignored_properties title: 'Test: Element: Ignore properties' description: 'Test ignored element properties and callbacks.' @@ -35,6 +37,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +45,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +61,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +102,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +121,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,4 +178,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_file.yml similarity index 71% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_file.yml index 60a8893d50..0334117aa3 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_file.yml @@ -6,38 +6,22 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_element_text -title: 'Test: Element: Text' -description: 'Tests text field and text area based elements.' +archive: false +id: test_element_image_file +title: 'Test: Element: Image file upload' +description: 'Tests image file upload element.' category: 'Test: Element' elements: | - '#attributes': - novalidate: novalidate - text_format_element: - '#type': details - '#title': 'Text format elements' - '#open': true - text_format: - '#type': text_format - '#title': text_format - '#hide_help': true - counter_elements: - '#type': details - '#title': 'Word and character counter' - '#open': true - counter_characters: - '#type': textfield - '#title': 'Character counter' - '#counter_type': character - '#counter_maximum': 10 - counter_words: - '#type': textarea - '#title': 'Word counter' - '#counter_type': word - '#counter_maximum': 3 - '#counter_message': 'word(s) left. This is a custom message' + webform_image_file: + '#type': webform_image_file + '#title': webform_image_file + webform_image_file_advanced: + '#type': webform_image_file + '#title': 'webform_image_file_advanced (max: 20x20)' + '#max_resolution': 20x20 css: '' javascript: '' settings: @@ -46,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -53,6 +38,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -68,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -94,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -112,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -167,6 +171,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_nested.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_resolution.yml similarity index 64% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_nested.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_resolution.yml index a54e957409..7624f99b69 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_nested.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_image_resolution.yml @@ -6,56 +6,25 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_nested -title: 'Test: Form API #states nested elements in a conditional hidden container' -description: 'Test Drupal''s #states for nested elements in a conditional hidden container.' -category: 'Test: Form API #states' +archive: false +id: test_element_image_resolution +title: 'Test: Element: Image resolution' +description: 'Tests image resolution element.' +category: 'Test: Element' elements: | - visible_trigger: - '#type': checkbox - '#title': visible_trigger - visible_fieldset: - '#type': fieldset - '#title': visible_fieldset - '#states': - visible: - ':input[name="visible_trigger"]': - checked: true - visible_textfield: - '#type': textfield - '#title': visible_textfield - '#required': true - visible_custom_textfield: - '#type': textfield - '#title': visible_custom_textfield - '#states': - required: - ':input[name="visible_trigger"]': - checked: true - ':input[name="visible_textfield"]': - filled: true - visible_slide_fieldset: - '#type': fieldset - '#title': visible_slide_fieldset - '#states': - 'visible-slide': - ':input[name="visible_trigger"]': - checked: true - visible_slide_textfield: - '#type': textfield - '#title': visible_slide_textfield - '#required': true - visible_slide_custom_textfield: - '#type': textfield - '#title': visible_slide_custom_textfield - '#states': - required: - ':input[name="visible_trigger"]': - checked: true - ':input[name="visible_slide_textfield"]': - filled: true + webform_image_resolution: + '#type': webform_image_resolution + '#title': webform_image_resolution + webform_image_resolution_advanced: + '#type': webform_image_resolution + '#title': webform_image_resolution_advanced + '#description': '{description}' + '#height_title': '{height_title}' + '#width_title': '{width_title}' + '#default_value': 300x400 css: '' javascript: '' settings: @@ -64,6 +33,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -71,6 +41,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -78,7 +49,7 @@ settings: form_prepopulate_source_entity_type: '' form_reset: false form_disable_autocomplete: false - form_novalidate: true + form_novalidate: false form_disable_inline_errors: false form_required: false form_unsaved: false @@ -86,22 +57,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -112,6 +98,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -130,9 +117,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -140,7 +129,7 @@ settings: purge: none purge_days: null results_disabled: true - results_disabled_ignore: true + results_disabled_ignore: false token_update: false access: create: @@ -185,4 +174,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_input_mask.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_input_mask.yml new file mode 100644 index 0000000000..34afc537fc --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_input_mask.yml @@ -0,0 +1,239 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_input_mask +title: 'Test: Element: Input Mask' +description: 'Tests input mask.' +category: 'Test: Element' +elements: | + currency: + '#type': textfield + '#title': currency + '#input_mask': '''alias'': ''currency''' + datetime: + '#type': textfield + '#title': datetime + '#input_mask': '''alias'': ''datetime''' + decimal: + '#type': textfield + '#title': decimal + '#input_mask': '''alias'': ''decimal''' + email: + '#type': textfield + '#title': email + '#input_mask': '''alias'': ''email''' + ip: + '#type': textfield + '#title': ip + '#input_mask': '''alias'': ''ip''' + license_plate: + '#type': textfield + '#title': license_plate + '#input_mask': '[9-]AAA-999' + mac: + '#type': textfield + '#title': mac + '#input_mask': '''alias'': ''mac''' + percentage: + '#type': textfield + '#title': percentage + '#input_mask': '''alias'': ''percentage''' + phone: + '#type': textfield + '#title': phone + '#input_mask': '(999) 999-9999' + ssn: + '#type': textfield + '#title': ssn + '#input_mask': 999-99-9999 + vin: + '#type': textfield + '#title': vin + '#input_mask': '''alias'': ''vin''' + zip: + '#type': textfield + '#title': zip + '#input_mask': '99999[-9999]' + uppercase: + '#type': textfield + '#title': uppercase + '#input_mask': '''casing'': ''upper''' + lowercase: + '#type': textfield + '#title': lowercase + '#input_mask': '''casing'': ''lower''' + custom: + '#type': textfield + '#title': custom + '#input_mask': '''alias'': ''numeric'', ''groupSeparator'': '','', ''autoGroup'': true, ''digits'': 2, ''digitsOptional'': false, ''prefix'': ''$ '', ''placeholder'': ''0''' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_invalid.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_invalid.yml index 90e9e2c727..b3f8f3f1c6 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_invalid.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_invalid.yml @@ -6,16 +6,15 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_invalid title: 'Test: Element: Invalid' description: 'Test invalid elements YAML.' category: 'Test: Element' -elements: | - not - valid - yaml +elements: '''not valid yaml''' css: '' javascript: '' settings: @@ -24,6 +23,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +31,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +47,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +88,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +107,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +164,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_likert.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_likert.yml index 049ebd4657..95358fe1f5 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_likert.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_likert.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_likert title: 'Test: Element: Likert' description: 'Test the likert element.' @@ -55,28 +57,27 @@ elements: | '#title': likert_help '#questions_description_display': help '#questions': - q1: 'Question 1 -- This is a help text' - q2: 'Question 2 -- This is a help text' - q3: 'Question 3 -- This is a help text' + q1: 'Question 1 -- This is help text' + q2: 'Question 2 -- This is help text' + q3: 'Question 3 -- This is help text' '#answers_description_display': help '#answers': - 1: 'Option 1 -- This is a help text' - 2: 'Option 2 -- This is a help text' - 3: 'Option 3 -- This is a help text' + 1: 'Option 1 -- This is help text' + 2: 'Option 2 -- This is help text' + 3: 'Option 3 -- This is help text' likert_values: '#type': webform_likert '#title': likert_values '#na_answer': true '#questions': - 0: 'Question 0' - 1: 'Question 1' - 2: 'Question 2' + - 'Question 0' + - 'Question 1' + - 'Question 2' '#answers': - 0: 'Option 0' - 1: 'Option 1' - 2: 'Option 2' - 3: 'Option 3' - + - 'Option 0' + - 'Option 1' + - 'Option 2' + - 'Option 3' css: '' javascript: '' settings: @@ -85,6 +86,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -92,6 +94,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -107,22 +110,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -133,6 +151,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -151,9 +170,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -206,6 +227,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_location.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_geocomplete.yml similarity index 79% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_location.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_geocomplete.yml index 544f0c70db..48fd4be963 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_location.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_geocomplete.yml @@ -6,11 +6,13 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_element_location -title: 'Test: Element: Location' -description: 'Test (geo)location elements.' +archive: false +id: test_element_loc_geocomplete +title: 'Test: Element: Location Geocomplete' +description: 'Test (geo)location elements with Geocomplete.' category: 'Test: Element' elements: | location_default_element: @@ -18,10 +20,10 @@ elements: | '#title': 'Location default element' '#open': true location_default: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'location default' location_map: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'location map' '#geolocation': true '#map': true @@ -30,7 +32,7 @@ elements: | '#title': 'Location with visible attributes element' '#open': true location_attributes: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'Location with visible attributes' '#lat__access': true '#lng__access': true @@ -50,7 +52,7 @@ elements: | '#title': 'Location with default value and required element' '#open': true location_default_value_required: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'Location with default value and required' '#default_value': value: '1600 Pennsylvania Ave NW, Washington, DC 20500' @@ -73,7 +75,7 @@ elements: | '#title': 'Location with geolocation element' '#open': true location_geolocation: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'Location with geolocation' '#geolocation': true '#location__access': true @@ -88,7 +90,7 @@ elements: | '#country__access': true '#country_short__access': true location_geolocation_hidden: - '#type': webform_location + '#type': webform_location_geocomplete '#title': 'Location with geolocation and hidden' '#geolocation': true '#hidden': true @@ -100,6 +102,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -107,6 +110,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -122,22 +126,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -148,6 +167,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -166,9 +186,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -221,4 +243,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_places.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_places.yml new file mode 100644 index 0000000000..3ef606eef2 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_loc_places.yml @@ -0,0 +1,212 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_loc_places +title: 'Test: Element: Location Places' +description: 'Test (geo)location elements with Algolia Places.' +category: 'Test: Element' +elements: | + location_default: + '#type': webform_location_places + '#title': location_default + '#placeholder': 'Enter a location' + location_attributes: + '#type': webform_location_places + '#title': location_attributes + '#lat__access': true + '#lng__access': true + '#name__access': true + '#postcode__access': true + '#locality__access': true + '#city__access': true + '#administrative__access': true + '#country__access': true + '#country_code__access': true + '#county__access': true + '#suburb__access': true + location_attributes_required: + '#type': webform_location_places + '#title': location_attributes_required + '#required': true + '#lat__access': true + '#lng__access': true + '#name__access': true + '#postcode__access': true + '#locality__access': true + '#city__access': true + '#administrative__access': true + '#country__access': true + '#country_code__access': true + '#county__access': true + '#suburb__access': true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file.yml index 61e556c212..df7253c15a 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_managed_file title: 'Test: Element: Managed file' description: 'Test file uploads using managed file element.' @@ -46,6 +48,17 @@ elements: | '#title': managed_file_sanitize '#file_extensions': txt '#sanitize': true + managed_file_single_placeholder: + '#type': managed_file + '#title': managed_file_single_placeholder + '#file_extensions': txt + '#file_placeholder': 'This is the single file upload placeholder' + managed_file_multiple_placeholder: + '#type': managed_file + '#title': managed_file_multiple_placeholder + '#file_extensions': txt + '#file_placeholder': 'This is the multiple file upload placeholder' + '#multiple': true css: '' javascript: '' settings: @@ -54,6 +67,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -61,6 +75,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -76,22 +91,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -102,6 +132,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -120,9 +151,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -175,4 +208,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_dis.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_dis.yml index 6d0a87cd04..8ee36bdbff 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_dis.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_dis.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_managed_file_dis title: 'Test: Element: Managed file results disabled' description: 'Test file uploads using managed file element with results disabled.' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_help.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_help.yml new file mode 100644 index 0000000000..2070b139c2 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_help.yml @@ -0,0 +1,228 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_managed_file_help +title: 'Test: File upload help' +description: 'Test file upload help.' +category: 'Test: Element' +elements: | + description: + '#type': details + '#title': description + '#open': true + managed_file_help_description: + '#type': managed_file + '#title': managed_file_help_description + managed_file_help_description_custom: + '#type': managed_file + '#title': managed_file_help_description_custom + '#description': 'This is a custom description' + '#description_display': before + '#multiple': true + help: + '#type': details + '#title': help + '#open': true + managed_file_help_help: + '#type': managed_file + '#title': managed_file_help_help + '#file_help': help + managed_file_help_help_custom: + '#type': managed_file + '#title': managed_file_help_help_custom + '#file_help': help + '#help': 'This is custom help' + '#multiple': true + more: + '#type': details + '#title': more + '#open': true + managed_file_help_more: + '#type': managed_file + '#title': managed_file_help_more + '#file_help': more + managed_file_help_more_custom: + '#type': managed_file + '#title': managed_file_help_more_custom + '#file_help': more + '#more': 'This is custom help' + '#multiple': true + none: + '#type': details + '#title': none + '#open': true + managed_file_help_none: + '#type': managed_file + '#title': managed_file_help_none + '#file_help': none +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_limit.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_limit.yml new file mode 100644 index 0000000000..416848c0c9 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_limit.yml @@ -0,0 +1,183 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_managed_file_limit +title: 'Test: Element: Managed file limit' +description: 'Test file upload limit per webform.' +category: 'Test: Element' +elements: | + managed_file_01: + '#type': managed_file + '#title': managed_file_01 + '#file_extensions': txt + managed_file_02: + '#type': managed_file + '#title': managed_file_02 + '#file_extensions': txt + managed_file_03: + '#type': managed_file + '#title': managed_file_03 + '#file_extensions': txt +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '1 MB' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_name.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_name.yml index 454137a9f6..757265c7cd 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_name.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_name.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_managed_file_name title: 'Test: File rename' description: 'Dummy form to test uploaded file renaming.' @@ -33,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -40,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -55,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -81,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -99,9 +119,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -154,6 +176,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_prev.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_prev.yml new file mode 100644 index 0000000000..7e28be31f4 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_managed_file_prev.yml @@ -0,0 +1,217 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_managed_file_prev +title: 'Test: Element: Managed file preview' +description: 'Test file upload previews using managed file element.' +category: 'Test: Element' +elements: | + webform_image_file: + '#type': webform_image_file + '#title': webform_image_file + '#file_preview': 'thumbnail:modal' + '#file_extensions': gif + webform_image_file_multiple: + '#type': webform_image_file + '#title': webform_image_file_multiple + '#file_preview': 'thumbnail:modal' + '#file_extensions': gif + '#multiple': true + webform_audio_file: + '#type': webform_audio_file + '#title': webform_audio_file + '#file_preview': file + '#file_extensions': mp3 + webform_audio_file_multiple: + '#type': webform_audio_file + '#title': webform_audio_file_multiple + '#file_preview': file + '#file_extensions': mp3 + '#multiple': true + webform_video_file: + '#type': webform_video_file + '#title': webform_video_file + '#file_preview': file + '#file_extensions': mp4 + webform_video_file_multiple: + '#type': webform_video_file + '#title': webform_video_file_multiple + '#file_preview': file + '#file_extensions': mp4 + '#multiple': true + webform_document_file: + '#type': webform_document_file + '#title': webform_document_file + '#file_preview': url + '#file_extensions': txt + webform_document_file_multiple: + '#type': webform_document_file + '#title': webform_document_file_multiple + '#file_preview': url + '#file_extensions': txt + '#multiple': true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_mapping.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_mapping.yml index e0fc82c083..612c2acce2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_mapping.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_mapping.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_mapping title: 'Test: Element: Mapping' description: 'Test the mapping element.' @@ -34,7 +36,7 @@ elements: | '#source__title': '{Custom source}' '#destination__title': '{Destination source}' '#destination__description': '{Destination description}' - '#arrow': '»' + '#arrow': » '#format': table '#source': days '#destination': @@ -113,6 +115,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -120,6 +123,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -135,22 +139,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -161,6 +180,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -179,9 +199,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -234,6 +256,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_markup.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_markup.yml index cdcac3dde4..4d1652e94c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_markup.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_markup.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_markup title: 'Test: Element: Markup' description: 'Test the markup element.' @@ -27,6 +29,10 @@ elements: | '#type': webform_markup '#markup': '<p>This is displayed on the both the form and submission view.</p>' '#display_on': both + markup_altered: + '#type': webform_markup + '#markup': '<p>Alter this markup.</p>' + '#display_on': both css: '' javascript: '' settings: @@ -35,6 +41,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +49,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +65,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +106,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +125,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,4 +182,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_media_file.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_media_file.yml index 36a391dd65..3b512287a9 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_media_file.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_media_file.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_media_file title: 'Test: Element: Media type file upload' description: 'Test file uploads using media accepts and capture file element.' @@ -77,6 +79,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -84,6 +87,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -99,22 +103,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -125,6 +144,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -143,9 +163,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -198,4 +220,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_message.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_message.yml index 7a24f4bf58..2a6244ee13 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_message.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_message.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_message title: 'Test: Element: Message' description: 'Test the message element.' @@ -97,6 +99,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -104,6 +107,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -119,22 +123,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -145,6 +164,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -163,9 +183,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -218,4 +240,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_more.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_more.yml index 8324d7df2f..be64567e15 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_more.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_more.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_more title: 'Test: Element: More' description: 'Test element #more and #more_title properties.' @@ -36,7 +38,7 @@ elements: | '#type': textfield '#title': more_title_description_hidden '#description': '{This is an example of a hidden description}' - '#description_display': 'invisible' + '#description_display': invisible '#more_title': '{Custom more title}' '#more': '{This is an example of more}' more_datetime: @@ -67,6 +69,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -74,6 +77,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -89,22 +93,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -115,6 +134,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -133,9 +153,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -188,4 +210,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple.yml index 893f38397a..af36c473f2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_multiple title: 'Test: Element: Multiple' description: 'Test multiple element.' @@ -36,6 +38,23 @@ elements: | - One - Two - Three + webform_multiple_no_add_more: + '#type': webform_multiple + '#title': webform_multiple_no_add_more + '#add_more': false + '#default_value': + - One + - Two + - Three + webform_multiple_custom_label: + '#type': webform_multiple + '#title': webform_multiple_custom_label + '#add_more_button_label': '{add_more_button_label}' + '#add_more_input_label': '{add_more_input_label}' + '#default_value': + - One + - Two + - Three webform_multiple_email_five: '#type': webform_multiple '#title': webform_multiple_email_five @@ -189,7 +208,7 @@ elements: | '#help': 'This is custom help text' option: '#type': container - '#title': 'text/description' + '#title': text/description '#title_display': invisible '#help': 'This is custom help text' text: @@ -205,10 +224,15 @@ elements: | '#default_value': - value: one text: One - description: This is the number 1. + description: 'This is the number 1.' - value: two text: Two - description: This is the number 2. + description: 'This is the number 2.' + webform_multiple_no_items: + '#type': webform_multiple + '#title': webform_multiple_no_items + '#min_items': 0 + '#empty_items': 0 css: '' javascript: '' settings: @@ -217,6 +241,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -224,6 +249,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -239,22 +265,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -265,6 +306,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -283,9 +325,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -338,6 +382,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_date.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_date.yml index c756272be6..d7f5a9103c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_date.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_date.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_multiple_date title: 'Test: Element: Multiple: Date' description: 'Test multiple text Date elements.' @@ -27,15 +29,15 @@ elements: | '#open': true date_basic: '#type': date - '#title': 'date_basic' + '#title': date_basic '#multiple': true datetime_basic: '#type': datetime - '#title': 'datetime_basic' + '#title': datetime_basic '#multiple': true datelist_basic: '#type': datelist - '#title': 'datelist_basic' + '#title': datelist_basic '#multiple': true css: '' javascript: '' @@ -45,6 +47,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -52,6 +55,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -67,22 +71,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -93,6 +112,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -111,9 +131,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -166,6 +188,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_property.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_property.yml index 17ba0d916a..36a1b99891 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_property.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_property.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_multiple_property title: 'Test: Element: Multiple property' description: 'Test #multiple element property.' @@ -19,11 +21,11 @@ elements: | webform_element_multiple_true: '#type': webform_element_multiple '#title': webform_element_multiple_true - '#default_value': TRUE + '#default_value': true webform_element_multiple_false: '#type': webform_element_multiple '#title': webform_element_multiple_false - '#default_value': FALSE + '#default_value': false webform_element_multiple_custom: '#type': webform_element_multiple '#title': webform_element_multiple_custom @@ -41,6 +43,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -48,6 +51,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -63,22 +67,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -89,6 +108,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -107,9 +127,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -162,6 +184,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_text.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_text.yml index 6052cf5564..17a71aabf3 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_text.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_multiple_text.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_multiple_text title: 'Test: Element: Multiple: Text Based' description: 'Test multiple text based elements.' @@ -88,6 +90,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -95,6 +98,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -110,22 +114,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -136,6 +155,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -154,9 +174,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -209,6 +231,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_options.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_options.yml index 7069ade79b..471c67c7c4 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_options.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_options.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_options title: 'Test: Element: Options' description: 'Test options element.' @@ -22,14 +24,14 @@ elements: | '#title': webform_options webform_options_default_value: '#type': webform_options - '#title': 'webform_options_default_value' + '#title': webform_options_default_value '#default_value': one: One two: Two three: Three webform_options_maxlength: '#type': webform_options - '#title': 'webform_options_maxlength' + '#title': webform_options_maxlength '#options_text_maxlength': 20 '#options_value_maxlength': 20 '#default_value': @@ -38,7 +40,7 @@ elements: | three: Three webform_options_optgroup: '#type': webform_options - '#title': 'webform_options_optgroup' + '#title': webform_options_optgroup '#required': true '#default_value': 'Group One': @@ -53,12 +55,12 @@ elements: | '#open': true webform_element_options_entity: '#type': webform_element_options - '#title': 'webform_element_options_entity' + '#title': webform_element_options_entity '#required': true '#default_value': yes_no webform_element_options_custom: '#type': webform_element_options - '#title': 'webform_element_options_custom' + '#title': webform_element_options_custom '#required': true '#default_value': one: One @@ -72,6 +74,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -79,6 +82,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -94,22 +98,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -120,6 +139,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -138,9 +158,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -193,6 +215,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_other.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_other.yml index 45186d922d..3033c02fc7 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_other.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_other.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_other title: 'Test: Element: Other' description: 'Test the webform other elements, includes select_other, checkboxes_other, radios_other, and buttons_other.' @@ -35,13 +37,16 @@ elements: | 'Option Group': Two: Two Three: Three - '#empty_option': Select... + '#empty_option': Select… '#other__option_label': 'Is there another option you wish to enter?' '#other__title': Other '#other__placeholder': 'What is this other option' '#other__description': 'Other select description' '#other__size': 20 '#other__maxlength': 20 + '#other__counter_type': character + '#other__counter_minimum': '4' + '#other__counter_maximum': '10' '#required': true select_other_multiple: '#type': webform_select_other @@ -75,15 +80,16 @@ elements: | '#type': webform_checkboxes_other '#title': 'Checkboxes other advanced' '#options': - One: One - Two: Two - Three: Three + One: 'One -- Description' + Two: 'Two -- Description' + Three: 'Three -- Description' '#default_value': - One - Two - Four '#required': true '#options_display': two_columns + '#options_description_display': help '#other__option_label': 'Is there another option you wish to enter?' '#other__placeholder': 'What is this other option' '#other__description': 'Other checkbox description' @@ -103,12 +109,13 @@ elements: | '#type': webform_radios_other '#title': 'Radios other advanced' '#options': - One: One - Two: Two - Three: Three + One: 'One -- Description' + Two: 'Two -- Description' + Three: 'Three -- Description' '#default_value': Four '#required': true '#options_display': two_columns + '#options_description_display': help '#other__option_label': 'Is there another option you wish to enter?' '#other__placeholder': 'What is this other option' '#other__description': 'Other radio description' @@ -136,6 +143,34 @@ elements: | '#other__option_label': Other '#other__placeholder': 'What is this other option' '#other__description': 'Other button description' + wrapper_other: + '#type': details + '#title': 'Wrapper other (#wrapper_type)' + '#open': true + wrapper_other_fieldset: + '#type': webform_select_other + '#title': 'Wrapper other (fieldset)' + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': fieldset + wrapper_other_form_element: + '#type': webform_select_other + '#title': 'Wrapper other (form_element)' + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': form_element + wrapper_other_container: + '#type': webform_select_other + '#title': 'Wrapper other (container)' + '#options': + One: One + Two: Two + Three: Three + '#wrapper_type': container css: '' javascript: '' settings: @@ -144,6 +179,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -151,6 +187,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -166,22 +203,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -192,6 +244,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -210,9 +263,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -265,6 +320,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_pattern.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_pattern.yml new file mode 100644 index 0000000000..87ebfc81d5 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_pattern.yml @@ -0,0 +1,195 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_pattern +title: 'Test: Element: Pattern' +description: 'Test element pattern validation.' +category: 'Test: Element' +elements: | + pattern: + '#type': textfield + '#title': pattern + '#pattern': Hello + '#description': 'Enter ''Hello''' + pattern_error: + '#type': textfield + '#title': pattern_error + '#pattern': Hello + '#pattern_error': 'You did not enter ''Hello''' + '#description': 'Enter ''Hello''' + pattern_unicode: + '#type': textfield + '#title': pattern_unicode + '#pattern': \u2E8F + '#description': 'Enter unicode CJK characters ''⺏''' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_prepopulate.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_prepopulate.yml index eb2b331d64..88d35ed25c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_prepopulate.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_prepopulate.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_prepopulate title: 'Test: Element: Prepopulate' description: 'Test prepopulating individual elements using query string parameters.' @@ -19,11 +21,11 @@ elements: | textfield_prepopulate: '#type': textfield '#title': textfield_prepopulate - '#prepopulate': true + '#prepopulate': true managed_file_prepopulate: '#type': managed_file '#title': managed_file_prepopulate - '#prepopulate': true + '#prepopulate': true prepopulate_test: '#markup': '<a href="?textfield=value&textfield_prepopulate=value">?textfield=value&textfield_prepopulate=value</a>' css: '' @@ -34,6 +36,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -41,6 +44,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -56,22 +60,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -82,6 +101,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -100,9 +120,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -155,4 +177,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_private.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_private.yml index 06f6194953..8860f52ca1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_private.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_private.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_private title: 'Test: Element: Private' description: 'Test element private properties' @@ -29,6 +31,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -36,6 +39,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -51,22 +55,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -77,6 +96,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -95,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -150,4 +172,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml index 0d25675469..d8437d7f2d 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_radios.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_radios title: 'Test: Element: Radios' description: 'Test the radios.' @@ -20,6 +22,7 @@ elements: | radios_required: '#type': radios '#title': radios_required + '#description': 'This is a description' '#required': true '#options': yes_no radios_required_conditional_example: @@ -106,6 +109,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -113,6 +117,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -128,22 +133,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -154,6 +174,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -172,9 +193,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -227,6 +250,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_range.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_range.yml index 7f110007f1..f56719bcc2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_range.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_range.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_range title: 'Test: Element: Range' description: 'Test the range element.' @@ -37,7 +39,7 @@ elements: | '#attributes': style: 'width: 500px' '#output': below - '#output__field_prefix': '$' + '#output__field_prefix': $ '#output__field_suffix': '.00' '#output__attributes': style: 'background-color: yellow' @@ -51,10 +53,10 @@ elements: | '#min': 0 '#max': 10000 '#step': 100 - '#field_prefix': '$0.00' + '#field_prefix': $0.00 '#field_suffix': '$10,000' '#output': left - '#output__field_prefix': '$' + '#output__field_prefix': $ '#output__field_suffix': '.00' '#output__attributes': style: 'background-color: yellow' @@ -76,6 +78,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -83,6 +86,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -98,22 +102,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -124,6 +143,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -142,9 +162,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -197,6 +219,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_rating.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_rating.yml index 2da2ddac0b..6963963492 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_rating.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_rating.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_rating title: 'Test: Element: Rating' description: 'Test the rating element.' @@ -19,14 +21,18 @@ elements: | '#open': true rating_basic: '#type': webform_rating - '#title': 'Rating basic' + '#title': rating_basic rating_advanced: '#type': webform_rating - '#title': 'Rating advanced' + '#title': rating_advanced '#star_size': large '#reset': true '#max': '10' '#step': '0.1' + rating_required: + '#type': webform_rating + '#title': rating_required + '#required': true css: '' javascript: '' settings: @@ -35,6 +41,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +49,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +65,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +106,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +125,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,6 +182,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_readonly.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_readonly.yml index b9fb9ab280..81077f627f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_readonly.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_readonly.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_readonly title: 'Test: Element: Read-only' description: 'Test read-only elements.' @@ -29,6 +31,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -36,6 +39,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -51,22 +55,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -77,6 +96,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -95,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -150,6 +172,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_section.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_section.yml index f49779b993..6bc5bcaa0d 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_section.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_section.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_section title: 'Test: Element: Section' description: 'Test the section element.' @@ -16,27 +18,22 @@ elements: | webform_section: '#type': webform_section '#title': webform_section - '#help': '{This is help text}' - '#description': '{This is a description}' - webform_section_textfield: - '#type': textfield - '#title': webform_section_textfield - webform_section_required: - '#type': webform_section - '#title': webform_section_required + '#description': 'This is a description.' + '#help': 'This is help text.' + '#more': 'This is more text' '#required': true - webform_section_required_textfield: - '#type': textfield - '#title': webform_section_required_textfield + webform_section_markup: + '#markup': '<p>This is some markup</p>' + webform_section_title_invisible: + '#type': webform_section + '#title': webform_section_title_invisible + '#title_display': invisible webform_section_title_custom: '#type': webform_section '#title': webform_section_title_custom '#title_tag': h5 '#title_attributes': - 'style': 'color: red' - webform_section_h5_textfield: - '#type': textfield - '#title': webform_section_h5_textfield + style: 'color: red' css: '' javascript: '' settings: @@ -45,6 +42,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -52,6 +50,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -67,22 +66,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -93,6 +107,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -111,9 +126,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -166,4 +183,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_select.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_select.yml index 821d3373f4..f0f1612946 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_select.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_select.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_select title: 'Test: Element: Select' description: 'Tests select menus.' @@ -15,7 +17,7 @@ category: 'Test: Element' elements: | select_examples: '#type': details - '#title': 'Select' + '#title': Select '#open': true select: '#type': select @@ -50,6 +52,17 @@ elements: | three: Three four: Four five: Five + select_select2_placeholder: + '#type': select + '#title': select_select2_placeholder + '#select2': true + '#placeholder': 'This is a placeholder' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five select_select2_multiple: '#type': select '#title': select_select2_multiple @@ -61,6 +74,66 @@ elements: | three: Three four: Four five: Five + select_select2_multiple_placeholder: + '#type': select + '#title': select_select2_multiple_placeholder + '#select2': true + '#placeholder': 'This is a placeholder' + '#multiple': true + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select_chosen_examples: + '#type': details + '#title': 'Select chosen' + '#open': true + select_chosen: + '#type': select + '#title': select_chosen + '#chosen': true + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select_chosen_placeholder: + '#type': select + '#title': select_chosen_placeholder + '#chosen': true + '#placeholder': 'This is a placeholder' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select_chosen_multiple: + '#type': select + '#title': select_chosen_multiple + '#chosen': true + '#multiple': true + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select_chosen_multiple_placeholder: + '#type': select + '#title': select_chosen_multiple_placeholder + '#chosen': true + '#placeholder': 'This is a placeholder' + '#multiple': true + '#options': + one: One + two: Two + three: Three + four: Four + five: Five select_empty_option_examples: '#type': details '#title': 'Select empty option' @@ -138,6 +211,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -145,6 +219,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -160,22 +235,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -186,6 +276,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -204,9 +295,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -259,6 +352,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_signature.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_signature.yml index 8f42ac289a..f9d9f08785 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_signature.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_signature.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_signature title: 'Test: Element: Signature' description: 'Test the signature element.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,6 +167,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_states.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_states.yml index e253544498..e3fd7a6322 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_states.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_states.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_states title: 'Test: Element: #States API' description: 'Test Drupal''s #states API states (visible, required, disabled, checked, and expanded).' @@ -20,6 +22,16 @@ elements: | selector_01: 'Selector 01 (selector_01)' selector_02: 'Selector 02 (selector_02)' selector_03: 'Selector 03 (selector_03)' + '#selector_sources': + selector_01: + option_a: 'Option A' + option_b: 'Option B' + selector_02: + option_c: 'Option C' + option_d: 'Option D' + selector_03: + option_e: 'Option E' + option_f: 'Option F' '#default_value': enabled: selector_01: @@ -119,6 +131,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -126,6 +139,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -141,22 +155,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -167,6 +196,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -185,9 +215,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -240,6 +272,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views.yml new file mode 100644 index 0000000000..b28fe93852 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views.yml @@ -0,0 +1,210 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_submission_views +title: 'Test: Element: Webform Submission Views' +description: 'Test the webform submission views.' +category: 'Test: Element' +elements: | + global: + '#type': details + '#title': global + '#open': true + webform_submission_views_global: + '#type': webform_submission_views + '#global': true + '#default_value': + admin: + view: 'webform_submissions:embed_administer' + title: Admin + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions + webform: + '#type': details + '#title': webform + '#open': true + webform_submission_views: + '#type': webform_submission_views + '#default_value': + admin: + view: 'webform_submissions:embed_administer' + title: Admin + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views_r.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views_r.yml new file mode 100644 index 0000000000..af95ca7357 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submission_views_r.yml @@ -0,0 +1,204 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_submission_views_r +title: 'Test: Element: Webform Submission Views replace' +description: 'Test the webform submission views replace.' +category: 'Test: Element' +elements: | + global: + '#type': details + '#title': global + '#open': true + webform_submission_views_replace_global: + '#type': webform_submission_views_replace + '#global': true + '#default_value': + global_routes: + - entity.webform_submission.collection + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions + webform: + '#type': details + '#title': webform + '#open': true + webform_submission_views_replace: + '#type': webform_submission_views_replace + '#default_value': + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submitted_value.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submitted_value.yml index 523a521554..a2ff6f9aa6 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submitted_value.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_submitted_value.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_submitted_value title: 'Test: Element: Submitted value' description: 'Test submitted value handling for options element.' @@ -60,6 +62,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -67,6 +70,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -82,22 +86,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -108,6 +127,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -126,9 +146,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -181,6 +203,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_table.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_table.yml index 8514ed6ea4..aedd2c51dc 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_table.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_table.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_table title: 'Test: Element: Table' description: 'Test table elements.' @@ -189,6 +191,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -196,6 +199,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -211,22 +215,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -237,6 +256,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -255,9 +275,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -310,6 +332,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_telephone.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_telephone.yml index 0ef3ccd204..df38050aeb 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_telephone.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_telephone.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_telephone title: 'Test: Element: Telephone' description: 'Test telephone element.' @@ -20,6 +22,16 @@ elements: | '#type': tel '#title': 'tel international' '#international': true + tel_validation_e164: + '#type': tel + '#title': tel_validation_e164 + '#telephone_validation_format': '0' + tel_validation_national: + '#type': tel + '#title': tel_validation_national + '#description': 'United States - +1' + '#telephone_validation_format': '2' + '#telephone_validation_country': US css: '' javascript: '' settings: @@ -28,6 +40,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,6 +48,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -50,22 +64,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +105,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -94,9 +124,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,6 +181,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_term_reference.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_term_reference.yml index cec6b9a2a4..01d6229b8b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_term_reference.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_term_reference.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_term_reference title: 'Test: Element: Term reference' description: 'Test term reference element.' @@ -30,7 +32,7 @@ elements: | '#title': webform_term_checkboxes_tree_advanced '#vocabulary': tags '#multiple': true - '#tree_delimiter': '..' + '#tree_delimiter': .. '#scroll': false '#format': breadcrumb '#format_items': ul @@ -71,7 +73,7 @@ elements: | '#title': webform_term_select_tree_advanced '#vocabulary': tags '#multiple': true - '#tree_delimiter': '..' + '#tree_delimiter': .. '#format': breadcrumb '#format_items': ul webform_term_select_breadcrumb: @@ -102,6 +104,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -109,6 +112,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -124,22 +128,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -150,6 +169,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -168,9 +188,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -223,4 +245,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_terms_of_service.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_terms_of_service.yml index c0f6da83b0..3e2d84a223 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_terms_of_service.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_terms_of_service.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_terms_of_service title: 'Test: Element: Terms of Service' description: 'Test terms of service element.' @@ -15,19 +17,19 @@ category: 'Test: Element' elements: | terms_of_service_default: '#type': webform_terms_of_service - '#title': I agree to the {terms of service}. (default) + '#title': 'I agree to the {terms of service}. (default)' '#terms_title': terms_of_service_default '#terms_content': 'These are the terms of service.' '#required': true terms_of_service_modal: '#type': webform_terms_of_service - '#title': I agree to the {terms of service}. (modal) + '#title': 'I agree to the {terms of service}. (modal)' '#terms_type': modal '#terms_title': terms_of_service_modal '#terms_content': 'These are the terms of service.' terms_of_service_slideout: '#type': webform_terms_of_service - '#title': I agree to the {terms of service}. (slideout) + '#title': 'I agree to the {terms of service}. (slideout)' '#terms_type': slideout '#terms_title': terms_of_service_slideout '#terms_content': 'These are the terms of service.' @@ -39,6 +41,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -46,6 +49,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -61,22 +65,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -87,6 +106,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -105,9 +125,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -160,4 +182,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text_format.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text_format.yml index b5dfe3f073..c9386c2e65 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text_format.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_text_format.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_text_format title: 'Test: Element: Text format' description: 'Test text webform element support.' @@ -18,6 +20,7 @@ elements: | '#title': text_format '#format': plain_text '#default_value': 'The quick brown fox jumped over the lazy dog.' + '#hide_help': true css: '' javascript: '' settings: @@ -26,6 +29,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +37,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +53,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +94,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +113,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +170,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_time.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_time.yml index 20343a7470..086c148609 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_time.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_time.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_time title: 'Test: Element: Time' description: 'Test time element.' @@ -68,6 +70,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -75,6 +78,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -90,22 +94,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -116,6 +135,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -134,9 +154,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -189,4 +211,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_toggle.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_toggle.yml index e5c2d64e48..58438812e0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_toggle.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_toggle.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_toggle title: 'Test: Element: Toggle' description: 'Test the toggle element.' @@ -55,6 +57,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -62,6 +65,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -77,22 +81,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -103,6 +122,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -121,9 +141,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -176,6 +198,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_users_roles.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_users_roles.yml index 105ee0b1e5..459eb1d9de 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_users_roles.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_users_roles.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_users_roles title: 'Test: Element: Users & Roles' description: 'Test users and roles element.' @@ -38,6 +40,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -45,6 +48,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -60,22 +64,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -86,6 +105,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -104,9 +124,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -159,6 +181,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_minlength.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_minlength.yml index 0806249837..d96b01a914 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_minlength.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_minlength.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_validate_minlength title: 'Test: Element: Validate Minlength' description: 'Test #minlength element validation support.' @@ -18,6 +20,12 @@ elements: | '#title': minlength_textfield '#minlength': 5 '#default_value': value + minlength_textfield_required: + '#type': textfield + '#title': minlength_textfield_required + '#minlength': 5 + '#required': true + '#default_value': value css: '' javascript: '' settings: @@ -26,6 +34,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +42,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +58,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +99,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +118,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +175,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_multiple.yml index 3751a3d1e5..ee613047b1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_multiple.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_validate_multiple title: 'Test: Element: Validate Multiple' description: 'Test #multiple element validation support.' @@ -20,7 +22,7 @@ elements: | webform_element_multiple_textfield_unlimited: '#type': textfield '#title': webform_element_multiple_textfield_unlimited - '#multiple': TRUE + '#multiple': true webform_element_multiple_textfield_three: '#type': textfield '#title': webform_element_multiple_textfield_three @@ -37,7 +39,7 @@ elements: | webform_element_multiple_link_unlimited: '#type': webform_link '#title': webform_element_multiple_link_unlimited - '#multiple': TRUE + '#multiple': true '#multiple__header': true checkboxes: '#type': details @@ -84,6 +86,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -91,6 +94,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -106,22 +110,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -132,6 +151,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -150,9 +170,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -205,6 +227,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_required.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_required.yml index d676d17312..317dda81dc 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_required.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_required.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_validate_required title: 'Test: Element: Validate Required' description: 'Test #required_error element validation support.' @@ -35,6 +37,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +45,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +61,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +102,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +121,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,4 +178,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_unique.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_unique.yml index 5d45926935..495a2b1d4d 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_unique.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_element_validate_unique.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_validate_unique title: 'Test: Element: Validate Unique' description: 'Test #unique element validation support.' @@ -40,17 +42,14 @@ elements: | '#title': unique_error '#unique': true '#unique_error': 'unique_error error message.' - unique_ignored: + unique_multiple: '#type': checkboxes - '#title': unique_ignored + '#title': unique_multiple '#unique': true - '#unique_error': 'unique_ignored error message.' + '#unique_error': 'unique_multiple error message.' '#options': 1: one 2: two - '#default_value': - - 1 - - 2 css: '' javascript: '' settings: @@ -59,6 +58,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -66,6 +66,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: true @@ -81,22 +82,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -107,6 +123,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -125,9 +142,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -180,4 +199,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements.yml index c468a0efae..978f1e986e 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_example_elements title: 'Test: Example: Elements' description: 'Examples of every supported <a href="https://api.drupal.org/api/drupal/elements">webform element</a>.' @@ -98,7 +100,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' webform_rating: @@ -158,9 +160,6 @@ elements: | '#message_type': warning '#message_message': 'This is a <strong>warning</strong> message.' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' - table: - '#type': table - '#title': Table file_upload_elements: '#type': details '#title': 'File upload elements' @@ -350,11 +349,11 @@ elements: | webform_computed_token: '#type': webform_computed_token '#title': 'Computed token' - '#value': 'This is a Computed token value.' + '#template': 'This is a Computed token value.' webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig' - '#value': 'This is a Computed Twig value.' + '#template': 'This is a Computed Twig value.' containers: '#type': details '#title': Containers @@ -376,9 +375,6 @@ elements: | '#field_prefix': '{field_prefix}' '#field_suffix': '{field_suffix}' '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' - label: - '#type': label - '#title': Label webform_section: '#type': webform_section '#title': Section @@ -502,6 +498,13 @@ elements: | '#vocabulary': tags '#multiple': true '#select2': true + markup: + '#type': details + '#title': Markup + '#open': true + label: + '#type': label + '#title': Label other_elements: '#type': details '#title': 'Other elements' @@ -512,6 +515,9 @@ elements: | machine_name: '#type': machine_name '#title': 'Machine name' + table: + '#type': table + '#title': Table css: '' javascript: '' settings: @@ -520,6 +526,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -527,6 +534,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -542,22 +550,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -568,6 +591,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -586,9 +610,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -641,4 +667,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements_composite.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements_composite.yml index ca5328aa3a..e8e5938e49 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements_composite.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_example_elements_composite.yml @@ -6,31 +6,56 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_example_elements_composite title: 'Test: Example: Elements: Composite' description: 'Examples of composite elements, includes address, and contact.' category: 'Test: Example' elements: | + address_example: + '#type': details + '#title': 'Advanced address' + '#open': true + address: + '#type': address + '#title': 'Advanced address' + address_multiple: + '#type': address + '#title': 'Advanced address multiple' + '#multiple': true webform_address_example: '#type': details - '#title': Address + '#title': 'Basic address' '#open': true webform_address: '#type': webform_address - '#title': Address + '#title': 'Basic address' webform_address_multiple: '#type': webform_address - '#title': 'Address multiple' + '#title': 'Basic address multiple' + '#multiple': true + '#multiple__header': true + webform_contact_example: + '#type': details + '#title': Contact + '#open': true + webform_contact: + '#type': webform_contact + '#title': Contact + webform_contact_multiple: + '#type': webform_contact + '#title': 'Contact multiple' '#multiple': true webform_custom_composite_example: '#type': details - '#title': 'Composite custom' + '#title': 'Custom composite' '#open': true webform_custom_composite: '#type': webform_custom_composite - '#title': 'Composite custom' + '#title': 'Custom composite' '#element': name: '#type': textfield @@ -45,7 +70,7 @@ elements: | Female: Female webform_custom_composite_multiple: '#type': webform_custom_composite - '#title': 'Composite custom multiple' + '#title': 'Custom composite multiple' '#element': name: '#type': textfield @@ -60,17 +85,6 @@ elements: | Female: Female '#multiple': true '#multiple__header': true - webform_contact_example: - '#type': details - '#title': Contact - '#open': true - webform_contact: - '#type': webform_contact - '#title': Contact - webform_contact_multiple: - '#type': webform_contact - '#title': 'Contact multiple' - '#multiple': true webform_likert_example: '#type': details '#title': Likert @@ -97,18 +111,19 @@ elements: | '#type': webform_link '#title': 'Link multiple' '#multiple': true - webform_location_example: + '#multiple__header': true + webform_location_geocomplete_example: '#type': details '#title': Location '#open': true - webform_location: - '#type': webform_location + webform_location_geocomplete: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true '#format': map - webform_location_multiple: - '#type': webform_location + webform_location_geocomplete_multiple: + '#type': webform_location_places '#title': 'Location multiple' '#map': true '#geolocation': true @@ -140,6 +155,7 @@ elements: | '#type': webform_name '#title': 'Name multiple' '#multiple': true + '#multiple__header': true webform_telephone_example: '#type': details '#title': 'Telephone advanced' @@ -151,6 +167,7 @@ elements: | '#type': webform_telephone '#title': 'Telephone advanced multiple' '#multiple': true + '#multiple__header': true text_format_example: '#type': details '#title': 'Text format' @@ -167,6 +184,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -174,6 +192,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -189,22 +208,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -215,6 +249,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -233,9 +268,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -288,4 +325,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_entity_reference.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_entity_reference.yml index 03dc5aa631..f08dfb78b8 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_entity_reference.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_entity_reference.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_exporter_entity_reference title: 'Test: Exporter: Entity Reference' description: 'Test exporting entity references.' @@ -39,6 +41,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -46,6 +49,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -61,22 +65,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -87,6 +106,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -105,9 +125,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -160,4 +182,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_options.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_options.yml index 3841ba8d6b..3a811b56fb 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_options.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_exporter_options.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_exporter_options title: 'Test: Exporter: Options' description: 'Test exporting options element.' @@ -49,6 +51,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -56,6 +59,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -71,22 +75,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -97,6 +116,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -115,9 +135,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -170,4 +192,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_access_denied.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_access_denied.yml new file mode 100644 index 0000000000..bc4d272031 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_access_denied.yml @@ -0,0 +1,173 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_access_denied +title: 'Test: Webform: Access Denied' +description: 'Test access denied to form or submission.' +category: 'Test: Webform' +elements: | + textfield: + '#type': textfield + '#title': textfield +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: login + form_access_denied_title: 'Webform: Access denied' + form_access_denied_message: 'Please login to access <b>[webform:title]</b>.' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: login + submission_access_denied_title: 'Webform submission: Access Denied' + submission_access_denied_message: 'Please login to access <b>[webform_submission:label]</b>.' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_api.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_api.yml index f421288c7e..b1ef23e857 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_api.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_api.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_api title: 'Test: Form API' description: 'Test Form API validation' @@ -124,6 +126,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -131,6 +134,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -146,22 +150,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -172,6 +191,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -190,9 +210,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -245,4 +267,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_login.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_archived.yml similarity index 74% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_login.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_archived.yml index 2bf1e93343..29e3e72e40 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_login.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_archived.yml @@ -6,16 +6,17 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_login -title: 'Test: Webform: Login' -description: 'Test login access denied to form or submission.' +archive: true +id: test_form_archived +title: 'Test: Webform: Archive' +description: 'Test archiving a webform.' category: 'Test: Webform' elements: | - textfield: - '#type': textfield - '#title': textfield + description: + '#markup': 'This webform is archived.' css: '' javascript: '' settings: @@ -24,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: true - form_login_message: 'Please login to access <b>[webform:title]</b>.' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: true - submission_login_message: 'Please login to access <b>[webform_submission:label]</b>.' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_assets.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_assets.yml index ecb1d94284..f65fdad01c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_assets.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_assets.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_assets title: 'Test: Webform: Assets (CSS/JS)' description: 'Test custom CSS and JS webform assets.' @@ -22,18 +24,21 @@ css: | display: none; padding: 20px; } + javascript: | (function ($) { $(function() { $('.webform-submission-form').fadeIn(3000); }); })(jQuery); + settings: ajax: false ajax_scroll_top: form page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -41,6 +46,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -56,22 +62,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -82,6 +103,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -100,9 +122,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -155,4 +179,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofill.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofill.yml index ada1c6ff12..dbc1d7f324 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofill.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofill.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_autofill title: 'Test: Webform: Autofill' description: 'Test autofill with previous submission data.' @@ -27,6 +29,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -34,6 +37,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -49,15 +53,28 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: true autofill_message: '' autofill_excluded_elements: @@ -65,7 +82,9 @@ settings: wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofocus.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofocus.yml index 62140a777d..ca1ec6b636 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofocus.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_autofocus.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_autofocus title: 'Test: Webform: Autofocus' description: 'Test autofocusing the first element.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: true form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +167,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_closed.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_closed.yml index 962f61a226..6136e0710c 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_closed.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_closed.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_closed title: 'Test: Webform: Closed' description: 'Test warning messages being displayed when a webform is closed.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_confidential.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_confidential.yml index 23244206e5..4dc64c243a 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_confidential.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_confidential.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_confidential title: 'Test: Webform: Confidential' description: 'Test confidential webforms.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: true form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +167,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_details_toggle.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_details_toggle.yml index 1350ce83e3..d8f3eea412 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_details_toggle.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_details_toggle.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_details_toggle title: 'Test: Webform: Details toggle' description: 'Test details expand/collapse all toggle link.' @@ -31,6 +33,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -38,6 +41,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -53,22 +57,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: true - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -79,6 +98,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -97,9 +117,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -152,4 +174,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_autocomplete.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_autocomplete.yml index 7659061193..373e9751fe 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_autocomplete.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_autocomplete.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_disable_autocomplete title: 'Test: Webform: Disable autocomplete' description: 'Test disabling autocompletion for all elements.' @@ -26,6 +28,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +36,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +52,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +93,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -92,9 +112,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -147,4 +169,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_back.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_back.yml index e8f5eecbc9..c455bdf929 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_back.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_back.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_disable_back title: 'Test: Webform: Disabled back button' description: 'Test disabling the ability to navigate back using the back button.' @@ -351,6 +353,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -358,6 +361,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -373,22 +377,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -399,6 +418,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -417,9 +437,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -472,4 +494,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_inline_errors.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_inline_errors.yml index f5e8d5385d..8be662a991 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_inline_errors.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_disable_inline_errors.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_disable_inline_errors title: 'Test: Webform: Disable inline form errors' description: 'Test disabling inline form errors.' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_anonymous.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_anonymous.yml index 82ad2e414b..a7bc5fd846 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_anonymous.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_anonymous.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_draft_anonymous title: 'Test: Webform: Draft anonymous' description: 'Test saving a draft and previewing a submission for anonymous (and authenticated users).' @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,12 +38,13 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: true form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false form_prepopulate_source_entity_type: '' - form_reset: false + form_reset: true form_disable_autocomplete: false form_novalidate: false form_disable_inline_errors: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: true @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_authenticated.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_authenticated.yml index dcdec66792..71d3caaf85 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_authenticated.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_authenticated.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_draft_authenticated title: 'Test: Webform: Draft authenticated' description: 'Test saving a draft and previewing a submission for authenticated users.' @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,12 +38,13 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false form_prepopulate_source_entity_type: '' - form_reset: false + form_reset: true form_disable_autocomplete: false form_novalidate: false form_disable_inline_errors: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: true @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_multiple.yml index 2e839d9dfc..b18c8140f6 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_draft_multiple.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_draft_multiple title: 'Test: Webform: Draft multiple' description: 'Test saving multiple drafts.' @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,12 +38,13 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false form_prepopulate_source_entity_required: false form_prepopulate_source_entity_type: '' - form_reset: false + form_reset: true form_disable_autocomplete: false form_novalidate: false form_disable_inline_errors: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: true draft_auto_save: true @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_inline_errors.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_inline_errors.yml index ecf441fb21..d0f52342d0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_inline_errors.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_inline_errors.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_inline_errors title: 'Test: Webform: Inline Form Errors' description: 'Test inline form errors.' @@ -132,6 +134,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -139,6 +142,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -154,22 +158,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -180,6 +199,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -198,9 +218,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -253,4 +275,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit.yml index 869fb3be84..3ca896ed07 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_limit title: 'Test: Form: Submission limit' description: 'Test submission and user limits.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: 4 limit_total_interval: 60 limit_total_message: 'Only 4 submissions are allowed.' + limit_total_unique: false limit_user: 1 limit_user_interval: 60 limit_user_message: 'You are only allowed to have 1 submission for this webform.' + limit_user_unique: false entity_limit_total: 2 entity_limit_total_interval: 60 entity_limit_user: 1 @@ -145,4 +167,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_total_unique.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_total_unique.yml new file mode 100644 index 0000000000..1062f8f5e8 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_total_unique.yml @@ -0,0 +1,174 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_limit_total_unique +title: 'Test: Form: Submission limit total unique' +description: 'Test submission limit total unique.' +category: 'Test: Form' +elements: | + name: + '#type': textfield + '#title': Name +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: both + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: true + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: authenticated + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: true + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_user_unique.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_user_unique.yml new file mode 100644 index 0000000000..7e42cc8da0 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_limit_user_unique.yml @@ -0,0 +1,174 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_limit_user_unique +title: 'Test: Form: Submission limit user unique' +description: 'Test submission limit user unique.' +category: 'Test: Form' +elements: | + name: + '#type': textfield + '#title': Name +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: both + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: true + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: authenticated + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: true + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_100.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_100.yml index 4e293d5260..c48ae3b4e2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_100.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_100.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_long_100 title: 'Test: Webform: Long - 100 elements' description: 'Test webform with 100 elements' @@ -321,6 +323,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -328,6 +331,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -343,22 +347,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -369,6 +388,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -387,9 +407,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -442,4 +464,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_200.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_200.yml index 7adb8d58be..b940c852c0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_200.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_200.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_long_200 title: 'Test: Webform: Long - 200 elements' description: 'Test webform with 200 elements' @@ -621,6 +623,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -628,6 +631,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -643,22 +647,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -669,6 +688,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -687,9 +707,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -742,4 +764,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_300.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_300.yml index 7866475ed2..bc77082abc 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_300.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_long_300.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_long_300 title: 'Test: Webform: Long - 300 elements' description: 'Test webform with 300 elements' @@ -921,6 +923,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -928,6 +931,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -943,22 +947,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -969,6 +988,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -987,9 +1007,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1042,4 +1064,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_novalidate.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_novalidate.yml index 8df5fc7f3d..ba31ca3803 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_novalidate.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_novalidate.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_novalidate title: 'Test: Webform: Novalidate' description: 'Test disabling client-side validation.' @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,6 +38,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +171,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_opening.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_opening.yml index b9dc93f376..e235d6e573 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_opening.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_opening.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: '2020-01-01T00:00:00-05:00' close: null +weight: 0 uid: null template: false +archive: false id: test_form_opening title: 'Test: Webform: Opening' description: 'Test message being displayed when a webform is scheduled to open.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: 'This form is opening soon.' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_prepopulate.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_prepopulate.yml index 6684109223..1bd7e936f4 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_prepopulate.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_prepopulate.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_prepopulate title: 'Test: Webform: Prepopulate' description: 'Test prepopulating all form elements using query string parameters.' @@ -33,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -40,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: true @@ -55,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -81,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -99,9 +119,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -154,4 +176,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_preview.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_preview.yml index e5a3c758ea..f8f70625ae 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_preview.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_preview.yml @@ -6,20 +6,57 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_preview title: 'Test: Webform: Preview' description: 'Test submission preview.' category: 'Test: Webform' elements: | - name: - '#type': textfield - '#title': Name - email: - '#type': email - '#title': Email -css: '' + fieldset: + '#type': fieldset + '#title': fieldset + '#format_attributes': + class: + - format-attributes-class + name: + '#type': textfield + '#title': Name + '#format_attributes': + class: + - format-attributes-class + container: + '#type': container + '#title': container + '#format_attributes': + class: + - format-attributes-class + email: + '#type': email + '#title': Email + '#format_attributes': + class: + - format-attributes-class + section: + '#type': webform_section + '#title': section + '#format_attributes': + class: + - format-attributes-class + checkbox: + '#type': checkbox + '#title': Checkbox + '#format_attributes': + class: + - format-attributes-class +css: | + .format-attributes-class { + border: 1px dashed #ccc; + padding: 10px; + } + javascript: '' settings: ajax: false @@ -27,6 +64,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -34,6 +72,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -49,22 +88,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: true + submission_exclude_empty_checkbox: true + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -75,6 +129,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: true draft: none draft_multiple: false draft_auto_save: false @@ -93,9 +148,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -148,4 +205,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_properties.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_properties.yml index 641c87c190..7f73e06ead 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_properties.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_properties.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_properties title: 'Test: Webform: Properties' description: 'Test custom webform properties.' @@ -36,6 +38,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -43,6 +46,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -58,22 +62,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -84,6 +103,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -102,9 +122,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -157,4 +179,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_remote_addr.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_remote_addr.yml new file mode 100644 index 0000000000..566c2f6a85 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_remote_addr.yml @@ -0,0 +1,174 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_remote_addr +title: 'Test: Webform: Remote IP Address' +description: 'Test disabling the tracking of remote IP address.' +category: 'Test: Webform' +elements: | + name: + '#type': textfield + '#title': Name +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: false + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_required.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_required.yml index e5a9e96575..e450acd3b2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_required.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_required.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_required title: 'Test: Webform: Required indicator' description: 'Test required element indicator.' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_reset.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_reset.yml index 63145dffc7..e6a329fa9d 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_reset.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_reset.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_reset title: 'Test: Webform: Reset' description: 'Test form reset button.' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_results_disabled.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_results_disabled.yml index 0e10e2b19e..79bf87a297 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_results_disabled.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_results_disabled.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_results_disabled title: 'Test: Form: Results Disabled' description: 'Test the disabling of saving of results to the database.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_back.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_back.yml index a6529dea6c..a5e2438ac1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_back.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_back.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_submit_back title: 'Test: Webform: Back Button submit' description: 'Browser Back Button to submit to previous page.' @@ -35,6 +37,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +45,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +61,37 @@ settings: form_submit_back: true form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +102,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +121,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,4 +178,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_once.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_once.yml index d1c3e1a1fd..acc0cf335e 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_once.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_once.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_submit_once title: 'Test: Webform: Submit once' description: 'Test submit once.' @@ -32,6 +34,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: true form_exception_message: '' form_open_message: '' @@ -39,6 +42,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -54,22 +58,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: true - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -80,6 +99,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: false @@ -98,9 +118,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -153,4 +175,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_text.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_text.yml index e2f303dd6c..e37e26362f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_text.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_submit_text.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_submit_text title: 'Test: Webform: Submit text' description: 'Test customizing the webform''s submit button label.' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_template.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_template.yml index 4b76816bb4..de685d68e0 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_template.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_template.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: true +archive: false id: test_form_template title: 'Test: Webform: Template' description: 'Test using a webform as a template.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,4 +166,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved.yml index b629078480..5acbb9a1ad 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved.yml @@ -6,17 +6,23 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_unsaved title: 'Test: Webform: Unsaved' description: 'Test displaying unsaved data warning.' category: 'Test: Webform' elements: | + markup: + '#markup': 'Enter some data and hit reload' test: '#type': textfield - '#title': 'Text field' - '#description': 'Enter some data and hit reload' + '#title': textfield + text_format: + '#type': text_format + '#title': text_format css: '' javascript: '' settings: @@ -25,6 +31,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +39,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +55,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +96,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +172,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved_wizard.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved_wizard.yml new file mode 100644 index 0000000000..a8d685756a --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_unsaved_wizard.yml @@ -0,0 +1,183 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_unsaved_wizard +title: 'Test: Webform: Unsaved wizard' +description: 'Test displaying unsaved data warning on wizard.' +category: 'Test: Webform' +elements: | + page_01: + '#type': webform_wizard_page + '#title': page_1 + testfield_1: + '#type': textfield + '#title': textfield_1 + page_02: + '#type': webform_wizard_page + '#title': page_2 + testfield_1: + '#type': textfield + '#title': textfield_2 +css: '' +javascript: '' +settings: + ajax: true + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: true + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 1 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_validate.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_validate.yml index aa3a7cbe7a..45d8111329 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_validate.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_validate.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_validate title: 'Test: Webform: Validate' description: 'Test executing $form["#validate"] handlers.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +167,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_access.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_access.yml index 0341a00540..5379026fc2 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_access.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_access.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_access title: 'Test: Webform: Wizard access' description: 'Test wizard with page specific access rules.' @@ -62,6 +64,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -69,6 +72,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -84,22 +88,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -110,6 +129,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -128,9 +148,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -187,4 +209,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_advanced.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_advanced.yml index b926c2710a..523c659931 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_advanced.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_advanced.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_advanced title: 'Test: Webform: Wizard advanced' description: 'Test a multiple step ''wizard'' webform with save draft, auto save, and preview.' @@ -66,6 +68,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -73,6 +76,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -88,22 +92,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -114,6 +133,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: true @@ -132,9 +152,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -187,4 +209,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_basic.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_basic.yml index 9f80133d13..549c60e261 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_basic.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_basic.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_basic title: 'Test: Webform: Wizard basic' description: 'Test a basic multiple step ''wizard'' webform.' @@ -35,6 +37,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -42,6 +45,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -57,22 +61,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -83,6 +102,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -101,9 +121,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -156,4 +178,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_conditional.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_conditional.yml index 03bbc17eb8..6f932176fd 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_conditional.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_conditional.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_conditional title: 'Test: Webform: Wizard conditional' description: 'Test a multiple step wizard with conditional pages' @@ -16,7 +18,7 @@ elements: | start: '#title': Start '#type': webform_wizard_page - 'trigger_pages': + trigger_pages: '#type': checkboxes '#title': trigger_pages '#options': @@ -38,7 +40,7 @@ elements: | disabled: ':input[name="trigger_none"]': checked: true - 'trigger_none': + trigger_none: '#type': checkbox '#title': trigger_none page_1: @@ -50,7 +52,7 @@ elements: | checked: true ':input[name="trigger_none"]': unchecked: true - 'page_1_markup': + page_1_markup: '#markup': 'This is page 1.' page_2: '#title': 'Page 2' @@ -61,7 +63,7 @@ elements: | checked: true ':input[name="trigger_none"]': unchecked: true - 'page_2_markup': + page_2_markup: '#markup': 'This is page 2.' page_3: '#title': 'Page 3' @@ -72,7 +74,7 @@ elements: | checked: true ':input[name="trigger_none"]': unchecked: true - 'page_3_markup': + page_3_markup: '#markup': 'This is page 3.' page_4: '#title': 'Page 4' @@ -83,7 +85,7 @@ elements: | checked: true ':input[name="trigger_none"]': unchecked: true - 'page_4_markup': + page_4_markup: '#markup': 'This is page 4.' page_5: '#title': 'Page 5' @@ -94,7 +96,7 @@ elements: | checked: true ':input[name="trigger_none"]': unchecked: true - 'page_5_markup': + page_5_markup: '#markup': 'This is page 5.' css: '' javascript: '' @@ -104,6 +106,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -111,6 +114,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -126,22 +130,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -152,6 +171,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -170,9 +190,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -225,4 +247,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_custom.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_custom.yml index 88cc26107d..b35a7dcf3b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_custom.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_custom.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_custom title: 'Test: Webform: Wizard custom' description: 'Test a multiple step ''wizard'' webform with custom hide/show pages logic.' @@ -67,6 +69,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -74,6 +77,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -89,22 +93,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -115,6 +134,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -133,9 +153,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -188,4 +210,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_links.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_links.yml new file mode 100644 index 0000000000..5d6408c6a0 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_links.yml @@ -0,0 +1,185 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_form_wizard_links +title: 'Test: Webform: Wizard previous links' +description: 'Test a multiple step ''wizard'' webform with previous page links.' +category: 'Test: Webform' +elements: | + page_1: + '#title': 'Page 1' + '#type': webform_wizard_page + element_1: + '#title': 'Element 1' + '#type': textfield + '#default_value': '{element_1}' + page_2: + '#title': 'Page 2' + '#type': webform_wizard_page + element_2: + '#title': 'Element 2' + '#type': textfield + '#default_value': '{element_2}' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: true + wizard_start_label: '' + wizard_preview_link: true + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 1 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: all + draft_multiple: false + draft_auto_save: true + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_100.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_100.yml index 477ffdbf31..f40b85c579 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_100.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_100.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_long_100 title: 'Test: Wizard: Long - 100 elements' description: 'Test wizard with 100 elements' @@ -351,6 +353,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -358,6 +361,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -373,22 +377,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -399,6 +418,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -417,9 +437,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -472,4 +494,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_200.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_200.yml index d0861cf9cb..f19678c472 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_200.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_200.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_long_200 title: 'Test: Wizard: Long - 200 elements' description: 'Test wizard with 200 elements' @@ -681,6 +683,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -688,6 +691,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -703,22 +707,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -729,6 +748,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -747,9 +767,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -802,4 +824,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_300.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_300.yml index 41cc08c3a7..3757818a24 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_300.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_long_300.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_long_300 title: 'Test: Wizard: Long - 300 elements' description: 'Test wizard with 300 elements' @@ -1011,6 +1013,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -1018,6 +1021,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -1033,22 +1037,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -1059,6 +1078,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -1077,9 +1097,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -1132,4 +1154,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate.yml index 96723c33c6..5364771ccc 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_validate title: 'Test: Webform: Wizard element validation' description: 'Test a multiple step ''wizard'' webform element validation.' @@ -64,6 +66,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -71,6 +74,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -86,22 +90,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -112,6 +131,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -130,9 +150,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -185,6 +207,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate_comp.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate_comp.yml index 6e4adf504e..ce89a6f57b 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate_comp.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_wizard_validate_comp.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_form_wizard_validate_comp title: 'Test: Webform: Wizard composite element validation' description: 'Test a multiple step ''wizard'' webform composite element validation.' @@ -112,6 +114,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -119,6 +122,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: true form_prepopulate_source_entity: false @@ -134,22 +138,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: true wizard_progress_percentage: true + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -160,6 +179,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -178,9 +198,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -233,6 +255,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_action.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_action.yml index 9c894d936f..1a19991c41 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_action.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_action.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_action title: 'Test: Handler: Action' description: 'Test action handler.' @@ -53,6 +55,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -60,6 +63,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -75,22 +79,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: 'This is submission was automatically locked.' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -101,6 +120,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: authenticated draft_multiple: false draft_auto_save: false @@ -119,9 +139,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -174,6 +196,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: notes: id: action @@ -196,6 +222,7 @@ handlers: data: | notes_add: '' notes_last: '[webform_submission:values:notes_add]' + message: 'Submission notes have been updated.' message_type: status debug: true diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email.yml index fd9978da16..d2d87772a6 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email title: 'Test: Handler: Email' description: 'Test base plain text email handler.' @@ -46,6 +48,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -53,6 +56,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -68,22 +72,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -94,6 +113,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -112,9 +132,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -167,6 +189,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -188,13 +214,15 @@ handlers: from_options: { } from_name: '[webform_submission:values:first_name:raw] [webform_submission:values:last_name:raw]' subject: '[webform_submission:values:subject:raw]' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_advanced.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_advanced.yml index b44348e50b..dae7577f74 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_advanced.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_advanced.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email_advanced title: 'Test: Handler: Email advanced' description: 'Test email HTML handler.' @@ -41,6 +43,9 @@ elements: | optional: '#title': Optional '#type': textfield + checkbox: + '#title': Checkbox + '#type': checkbox file: '#type': managed_file '#title': File @@ -58,6 +63,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -65,6 +71,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -80,22 +87,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -106,6 +128,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -124,9 +147,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -179,6 +204,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -207,12 +236,15 @@ handlers: [webform_submission:values] <hr /> <p style="color:yellow"><em>Custom styled HTML markup</em></p> + excluded_elements: { } ignore_access: true exclude_empty: true + exclude_empty_checkbox: true html: true attachments: true twig: false + theme_name: '' debug: true reply_to: reply_to@example.com return_path: return_path@example.com diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_mapping.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_mapping.yml index 139e162612..64f6528ac3 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_mapping.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_mapping.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email_mapping title: 'Test: Handler: Email mapping' description: 'Test email mapping handler.' @@ -36,6 +38,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -43,6 +46,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -58,22 +62,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -84,6 +103,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -102,9 +122,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -157,6 +179,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: select: id: email @@ -176,17 +202,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Email mapping handler: Select yes option' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -210,17 +238,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Email mapping handler: Select empty' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -244,17 +274,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Email mapping handler: Select default' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -280,17 +312,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Email mapping handler: Checkboxes' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -314,17 +348,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Email mapping handler: Radios other' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_roles.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_roles.yml index ee27d8735e..cb9dcbb2a5 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_roles.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_roles.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email_roles title: 'Test: Handler: Email roles' description: 'Test email roles.' @@ -19,7 +21,7 @@ elements: | '#options': authenticated: 'Authenticated (authenticated)' administrator: 'Administrator (administrator)' - other: 'Other' + other: Other css: '' javascript: '' settings: @@ -28,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,6 +38,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -50,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -94,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,6 +171,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -172,17 +198,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_states.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_states.yml index b00fe296ca..1df0a5db40 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_states.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_states.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email_states title: 'Test: Handler: Email states' description: 'Test sending email during each submission state.' @@ -23,6 +25,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -30,6 +33,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: false form_prepopulate_source_entity: false @@ -45,22 +49,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -71,6 +90,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -89,9 +109,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -144,6 +166,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_draft: id: email @@ -161,17 +187,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Draft saved' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -193,17 +221,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Submission converted' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -225,17 +255,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Submission completed' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -257,17 +289,53 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Submission updated' - body: default + body: _default + excluded_elements: { } + ignore_access: false + exclude_empty: true + exclude_empty_checkbox: false + html: true + attachments: false + twig: false + theme_name: '' + debug: true + reply_to: '' + return_path: '' + sender_mail: '' + sender_name: '' + email_locked: + id: email + label: 'Submission locked' + handler_id: email_locked + status: true + conditions: { } + weight: 3 + settings: + states: + - locked + to_mail: locked@example.com + to_options: { } + cc_mail: '' + cc_options: { } + bcc_mail: '' + bcc_options: { } + from_mail: _default + from_options: { } + from_name: _default + subject: 'Submission locked' + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -289,17 +357,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Submission deleted' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -320,17 +390,19 @@ handlers: cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: 'Submission custom' - body: default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_twig.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_twig.yml index 9db8dbc11e..5c867753aa 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_twig.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_email_twig.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_email_twig title: 'Test: Handler: Email Twig' description: 'Test Twig email handler.' @@ -46,6 +48,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -53,6 +56,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -68,22 +72,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -94,6 +113,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -112,9 +132,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -167,6 +189,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -191,12 +217,15 @@ handlers: body: | <p>Submitted values are:</p> {{ webform_token('[webform_submission:values]', webform_submission) }} + excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: true + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_settings.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_settings.yml index 9ea1cf8c17..4087323aa1 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_settings.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_handler_settings.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_settings title: 'Test: Handler: Settings' description: 'Test settings handler.' @@ -66,6 +68,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -73,6 +76,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -88,22 +92,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -114,6 +133,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -132,9 +152,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -187,6 +209,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: preview: id: settings @@ -204,7 +230,7 @@ handlers: confirmation_url: '' confirmation_title: '' confirmation_message: '' - debug: true + debug: '1' confirmation: id: settings label: 'Confirmation Settings' @@ -221,7 +247,7 @@ handlers: confirmation_url: '' confirmation_title: '[webform_submission:values:confirmation_title]' confirmation_message: '[webform_submission:values:confirmation_message]' - debug: true + debug: '1' custom: id: settings label: 'Custom settings' @@ -238,6 +264,6 @@ handlers: confirmation_url: '' confirmation_title: '' confirmation_message: '' - debug: true + debug: '1' draft_saved_message: '[webform_submission:values:draft_saved_message]' draft_loaded_message: '[webform_submission:values:draft_loaded_message]' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_libraries_optional.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_libraries_optional.yml index 5e7411cc53..91d4379770 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_libraries_optional.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_libraries_optional.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: 1 template: false +archive: false id: test_libraries_optional title: 'Test: Libraries: Optional' description: 'Test optional libraries.' @@ -73,7 +75,7 @@ elements: | text: 'Cute Kitten 3' src: 'http://placekitten.com/130/200' location: - '#type': webform_location + '#type': webform_location_places '#title': Location rating: '#type': webform_rating @@ -99,6 +101,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -106,6 +109,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -121,22 +125,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -147,6 +166,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -165,9 +185,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -220,4 +242,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_rendering.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_rendering.yml index dfbc6cfb9c..4f22ff7a90 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_rendering.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_rendering.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_rendering title: 'Test: Rendering' description: 'Test Webform and Webform Submission rendering' @@ -55,6 +57,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -62,6 +65,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -77,22 +81,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '[webform_submission:values:submission_label]' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -103,6 +122,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -121,9 +141,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -176,6 +198,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_html: id: email @@ -187,23 +213,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: '[webform_submission:values:submission_label:raw]' body: '[webform_submission:values]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' @@ -219,23 +247,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default + from_name: _default subject: '[webform_submission:values:submission_label:raw]' body: '[webform_submission:values]' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: false attachments: false twig: false + theme_name: '' debug: true reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states.yml similarity index 93% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states.yml index d25e5a4637..654e784690 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states +archive: false +id: test_states title: 'Test: Form API #states' description: 'Test Drupal''s #states API states (visible, required, disabled, checked, and expanded).' category: 'Test: Form API #states' @@ -19,27 +21,6 @@ elements: | visible_trigger: '#type': checkbox '#title': 'Displays and require elements' - visible_processed_text: - '#type': processed_text - '#title': 'Advanced HTML/Text' - '#states': - visible: - ':input[name="visible_trigger"]': - checked: true - required: - ':input[name="visible_trigger"]': - checked: true - visible_webform_markup: - '#type': webform_markup - '#title': 'Basic HTML' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' - '#states': - visible: - ':input[name="visible_trigger"]': - checked: true - required: - ':input[name="visible_trigger"]': - checked: true visible_container: '#type': container '#title': Container @@ -71,13 +52,9 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_item: - '#type': item - '#title': Item - '#markup': '{markup}' - '#field_prefix': '{field_prefix}' - '#field_suffix': '{field_suffix}' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + visible_webform_section: + '#type': webform_section + '#title': Section '#states': visible: ':input[name="visible_trigger"]': @@ -85,9 +62,9 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_label: - '#type': label - '#title': Label + visible_table: + '#type': table + '#title': Table '#states': visible: ':input[name="visible_trigger"]': @@ -95,12 +72,19 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_webform_message: - '#type': webform_message - '#title': Message - '#message_type': warning - '#message_message': 'This is a <strong>warning</strong> message.' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + visible_address: + '#type': address + '#title': 'Advanced address' + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en '#states': visible: ':input[name="visible_trigger"]': @@ -108,9 +92,9 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_webform_section: - '#type': webform_section - '#title': Section + visible_processed_text: + '#type': processed_text + '#title': 'Advanced HTML/Text' '#states': visible: ':input[name="visible_trigger"]': @@ -118,9 +102,20 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_table: - '#type': table - '#title': Table + visible_webform_audio_file: + '#type': webform_audio_file + '#title': 'Audio file' + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + required: + ':input[name="visible_trigger"]': + checked: true + visible_webform_autocomplete: + '#type': webform_autocomplete + '#title': Autocomplete + '#default_value': Loremipsum '#states': visible: ':input[name="visible_trigger"]': @@ -130,13 +125,13 @@ elements: | checked: true visible_webform_address: '#type': webform_address - '#title': Address + '#title': 'Basic address' '#default_value': address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#states': visible: @@ -145,20 +140,10 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_webform_audio_file: - '#type': webform_audio_file - '#title': 'Audio file' - '#states': - visible: - ':input[name="visible_trigger"]': - checked: true - required: - ':input[name="visible_trigger"]': - checked: true - visible_webform_autocomplete: - '#type': webform_autocomplete - '#title': Autocomplete - '#default_value': Loremipsum + visible_webform_markup: + '#type': webform_markup + '#title': 'Basic HTML' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' '#states': visible: ':input[name="visible_trigger"]': @@ -281,40 +266,9 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_webform_composite: - '#type': webform_custom_composite - '#title': 'Composite custom' - '#element': - name: - '#type': textfield - '#title': Name - '#title_display': invisible - gender: - '#type': select - '#title': Gender - '#title_display': invisible - '#options': - Male: Male - Female: Female - '#default_value': - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - '#states': - visible: - ':input[name="visible_trigger"]': - checked: true - required: - ':input[name="visible_trigger"]': - checked: true visible_webform_computed_token: '#type': webform_computed_token '#title': 'Computed token' - '#value': 'This is a Computed token value.' - '#default_value': Loremipsum '#states': visible: ':input[name="visible_trigger"]': @@ -322,11 +276,10 @@ elements: | required: ':input[name="visible_trigger"]': checked: true + '#template': 'This is a Computed token value.' visible_webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig' - '#value': 'This is a Computed Twig value.' - '#default_value': Loremipsum '#states': visible: ':input[name="visible_trigger"]': @@ -334,6 +287,7 @@ elements: | required: ':input[name="visible_trigger"]': checked: true + '#template': 'This is a Computed Twig value.' visible_webform_contact: '#type': webform_contact '#title': Contact @@ -346,7 +300,7 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#states': visible: @@ -355,10 +309,39 @@ elements: | required: ':input[name="visible_trigger"]': checked: true + visible_webform_custom_composite: + '#type': webform_custom_composite + '#title': 'Custom composite' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + required: + ':input[name="visible_trigger"]': + checked: true visible_date: '#type': date '#title': Date - '#default_value': '1942-06-18' + '#default_value': '2025-12-07T23:03:39-0500' '#states': visible: ':input[name="visible_trigger"]': @@ -370,7 +353,7 @@ elements: | '#type': datetime '#title': Date/time '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2419131">Issue #2419131: #states attribute does not work on #type datetime</a>' - '#default_value': '1942-06-18' + '#default_value': '2031-05-08T00:29:17-0400' '#states': visible: ':input[name="visible_trigger"]': @@ -381,7 +364,7 @@ elements: | visible_datelist: '#type': datelist '#title': 'Date list' - '#default_value': '1942-06-18' + '#default_value': '2036-12-05T02:05:53-0500' '#states': visible: ':input[name="visible_trigger"]': @@ -561,6 +544,30 @@ elements: | required: ':input[name="visible_trigger"]': checked: true + visible_item: + '#type': item + '#title': Item + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + required: + ':input[name="visible_trigger"]': + checked: true + visible_label: + '#type': label + '#title': Label + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + required: + ':input[name="visible_trigger"]': + checked: true visible_language_select: '#type': language_select '#title': 'Language select' @@ -607,8 +614,8 @@ elements: | required: ':input[name="visible_trigger"]': checked: true - visible_webform_location: - '#type': webform_location + visible_webform_location_places: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true @@ -655,6 +662,19 @@ elements: | required: ':input[name="visible_trigger"]': checked: true + visible_webform_message: + '#type': webform_message + '#title': Message + '#message_type': warning + '#message_message': 'This is a <strong>warning</strong> message.' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + required: + ':input[name="visible_trigger"]': + checked: true visible_webform_name: '#type': webform_name '#title': Name @@ -747,7 +767,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' '#states': @@ -760,7 +780,6 @@ elements: | visible_webform_rating: '#type': webform_rating '#title': Rating - '#default_value': 1 '#states': visible: ':input[name="visible_trigger"]': @@ -905,9 +924,9 @@ elements: | '#title': 'Term checkboxes' '#vocabulary': tags '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#states': visible: ':input[name="visible_trigger"]': @@ -919,7 +938,7 @@ elements: | '#type': webform_term_select '#title': 'Term select' '#vocabulary': tags - '#default_value': 41 + '#default_value': Loremipsum '#states': visible: ':input[name="visible_trigger"]': @@ -1045,21 +1064,6 @@ elements: | invisible_trigger: '#type': checkbox '#title': 'Hide and empty elements' - invisible_processed_text: - '#type': processed_text - '#title': 'Advanced HTML/Text' - '#states': - invisible: - ':input[name="invisible_trigger"]': - checked: true - invisible_webform_markup: - '#type': webform_markup - '#title': 'Basic HTML' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' - '#states': - invisible: - ':input[name="invisible_trigger"]': - checked: true invisible_container: '#type': container '#title': Container @@ -1082,73 +1086,77 @@ elements: | invisible: ':input[name="invisible_trigger"]': checked: true - invisible_item: - '#type': item - '#title': Item - '#markup': '{markup}' - '#field_prefix': '{field_prefix}' - '#field_suffix': '{field_suffix}' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + invisible_webform_section: + '#type': webform_section + '#title': Section '#states': invisible: ':input[name="invisible_trigger"]': checked: true - invisible_label: - '#type': label - '#title': Label + invisible_table: + '#type': table + '#title': Table '#states': invisible: ':input[name="invisible_trigger"]': checked: true - invisible_webform_message: - '#type': webform_message - '#title': Message - '#message_type': warning - '#message_message': 'This is a <strong>warning</strong> message.' - '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + invisible_address: + '#type': address + '#title': 'Advanced address' + '#default_value': + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en '#states': invisible: ':input[name="invisible_trigger"]': checked: true - invisible_webform_section: - '#type': webform_section - '#title': Section + invisible_processed_text: + '#type': processed_text + '#title': 'Advanced HTML/Text' '#states': invisible: ':input[name="invisible_trigger"]': checked: true - invisible_table: - '#type': table - '#title': Table + invisible_webform_audio_file: + '#type': webform_audio_file + '#title': 'Audio file' + '#states': + invisible: + ':input[name="invisible_trigger"]': + checked: true + invisible_webform_autocomplete: + '#type': webform_autocomplete + '#title': Autocomplete + '#default_value': Loremipsum '#states': invisible: ':input[name="invisible_trigger"]': checked: true invisible_webform_address: '#type': webform_address - '#title': Address + '#title': 'Basic address' '#default_value': address: '10 Main Street' address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#states': invisible: ':input[name="invisible_trigger"]': checked: true - invisible_webform_audio_file: - '#type': webform_audio_file - '#title': 'Audio file' - '#states': - invisible: - ':input[name="invisible_trigger"]': - checked: true - invisible_webform_autocomplete: - '#type': webform_autocomplete - '#title': Autocomplete - '#default_value': Loremipsum + invisible_webform_markup: + '#type': webform_markup + '#title': 'Basic HTML' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' '#states': invisible: ':input[name="invisible_trigger"]': @@ -1244,50 +1252,22 @@ elements: | invisible: ':input[name="invisible_trigger"]': checked: true - invisible_webform_composite: - '#type': webform_custom_composite - '#title': 'Composite custom' - '#element': - name: - '#type': textfield - '#title': Name - '#title_display': invisible - gender: - '#type': select - '#title': Gender - '#title_display': invisible - '#options': - Male: Male - Female: Female - '#default_value': - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - '#states': - invisible: - ':input[name="invisible_trigger"]': - checked: true invisible_webform_computed_token: '#type': webform_computed_token '#title': 'Computed token' - '#value': 'This is a Computed token value.' - '#default_value': Loremipsum '#states': invisible: ':input[name="invisible_trigger"]': checked: true + '#template': 'This is a Computed token value.' invisible_webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig' - '#value': 'This is a Computed Twig value.' - '#default_value': Loremipsum '#states': invisible: ':input[name="invisible_trigger"]': checked: true + '#template': 'This is a Computed Twig value.' invisible_webform_contact: '#type': webform_contact '#title': Contact @@ -1300,16 +1280,42 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#states': invisible: ':input[name="invisible_trigger"]': checked: true + invisible_webform_custom_composite: + '#type': webform_custom_composite + '#title': 'Custom composite' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#states': + invisible: + ':input[name="invisible_trigger"]': + checked: true invisible_date: '#type': date '#title': Date - '#default_value': '1942-06-18' + '#default_value': '2029-09-22T22:31:32-0400' '#states': invisible: ':input[name="invisible_trigger"]': @@ -1318,7 +1324,7 @@ elements: | '#type': datetime '#title': Date/time '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2419131">Issue #2419131: #states attribute does not work on #type datetime</a>' - '#default_value': '1942-06-18' + '#default_value': '2012-11-08T22:22:22-0500' '#states': invisible: ':input[name="invisible_trigger"]': @@ -1326,7 +1332,7 @@ elements: | invisible_datelist: '#type': datelist '#title': 'Date list' - '#default_value': '1942-06-18' + '#default_value': '2030-03-15T14:17:44-0400' '#states': invisible: ':input[name="invisible_trigger"]': @@ -1467,6 +1473,24 @@ elements: | invisible: ':input[name="invisible_trigger"]': checked: true + invisible_item: + '#type': item + '#title': Item + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + '#states': + invisible: + ':input[name="invisible_trigger"]': + checked: true + invisible_label: + '#type': label + '#title': Label + '#states': + invisible: + ':input[name="invisible_trigger"]': + checked: true invisible_language_select: '#type': language_select '#title': 'Language select' @@ -1504,8 +1528,8 @@ elements: | invisible: ':input[name="invisible_trigger"]': checked: true - invisible_webform_location: - '#type': webform_location + invisible_webform_location_places: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true @@ -1543,6 +1567,16 @@ elements: | invisible: ':input[name="invisible_trigger"]': checked: true + invisible_webform_message: + '#type': webform_message + '#title': Message + '#message_type': warning + '#message_message': 'This is a <strong>warning</strong> message.' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + '#states': + invisible: + ':input[name="invisible_trigger"]': + checked: true invisible_webform_name: '#type': webform_name '#title': Name @@ -1617,7 +1651,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' '#states': @@ -1627,7 +1661,6 @@ elements: | invisible_webform_rating: '#type': webform_rating '#title': Rating - '#default_value': 1 '#states': invisible: ':input[name="invisible_trigger"]': @@ -1742,9 +1775,9 @@ elements: | '#title': 'Term checkboxes' '#vocabulary': tags '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#states': invisible: ':input[name="invisible_trigger"]': @@ -1753,7 +1786,7 @@ elements: | '#type': webform_term_select '#title': 'Term select' '#vocabulary': tags - '#default_value': 41 + '#default_value': Loremipsum '#states': invisible: ':input[name="invisible_trigger"]': @@ -1849,16 +1882,26 @@ elements: | disabled_trigger: '#type': checkbox '#title': 'Disable elements' - disabled_webform_address: - '#type': webform_address - '#title': Address + disabled_address: + '#type': address + '#title': 'Advanced address' '#default_value': - address: '10 Main Street' - address_2: '10 Main Street' - city: Springfield - state_province: Alabama - postal_code: Loremipsum - country: Afghanistan + given_name: John + family_name: Smith + organization: 'Google Inc.' + address_line1: '1098 Alta Ave' + postal_code: '94043' + locality: 'Mountain View' + administrative_area: CA + country_code: US + langcode: en + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true + disabled_processed_text: + '#type': processed_text + '#title': 'Advanced HTML/Text' '#states': disabled: ':input[name="disabled_trigger"]': @@ -1878,6 +1921,28 @@ elements: | disabled: ':input[name="disabled_trigger"]': checked: true + disabled_webform_address: + '#type': webform_address + '#title': 'Basic address' + '#default_value': + address: '10 Main Street' + address_2: '10 Main Street' + city: Springfield + state_province: Alabama + postal_code: '11111' + country: Afghanistan + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true + disabled_webform_markup: + '#type': webform_markup + '#title': 'Basic HTML' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2700667">Issue #2700667: Notice: Undefined index: #type in drupal_process_states()</a>' + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true disabled_webform_buttons: '#type': webform_buttons '#title': Buttons @@ -1969,50 +2034,22 @@ elements: | disabled: ':input[name="disabled_trigger"]': checked: true - disabled_webform_composite: - '#type': webform_custom_composite - '#title': 'Composite custom' - '#element': - name: - '#type': textfield - '#title': Name - '#title_display': invisible - gender: - '#type': select - '#title': Gender - '#title_display': invisible - '#options': - Male: Male - Female: Female - '#default_value': - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - - name: Loremipsum - gender: Male - '#states': - disabled: - ':input[name="disabled_trigger"]': - checked: true disabled_webform_computed_token: '#type': webform_computed_token '#title': 'Computed token' - '#value': 'This is a Computed token value.' - '#default_value': Loremipsum '#states': disabled: ':input[name="disabled_trigger"]': checked: true + '#template': 'This is a Computed token value.' disabled_webform_computed_twig: '#type': webform_computed_twig '#title': 'Computed Twig' - '#value': 'This is a Computed Twig value.' - '#default_value': Loremipsum '#states': disabled: ':input[name="disabled_trigger"]': checked: true + '#template': 'This is a Computed Twig value.' disabled_webform_contact: '#type': webform_contact '#title': Contact @@ -2025,16 +2062,42 @@ elements: | address_2: '10 Main Street' city: Springfield state_province: Alabama - postal_code: Loremipsum + postal_code: '11111' country: Afghanistan '#states': disabled: ':input[name="disabled_trigger"]': checked: true + disabled_webform_custom_composite: + '#type': webform_custom_composite + '#title': 'Custom composite' + '#element': + name: + '#type': textfield + '#title': Name + '#title_display': invisible + gender: + '#type': select + '#title': Gender + '#title_display': invisible + '#options': + Male: Male + Female: Female + '#default_value': + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + - name: Loremipsum + gender: Male + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true disabled_date: '#type': date '#title': Date - '#default_value': '1942-06-18' + '#default_value': '2015-04-28T17:21:41-0400' '#states': disabled: ':input[name="disabled_trigger"]': @@ -2043,7 +2106,7 @@ elements: | '#type': datetime '#title': Date/time '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/2419131">Issue #2419131: #states attribute does not work on #type datetime</a>' - '#default_value': '1942-06-18' + '#default_value': '2009-06-05T06:32:08-0400' '#states': disabled: ':input[name="disabled_trigger"]': @@ -2051,7 +2114,7 @@ elements: | disabled_datelist: '#type': datelist '#title': 'Date list' - '#default_value': '1942-06-18' + '#default_value': '2012-04-05T08:52:12-0400' '#states': disabled: ':input[name="disabled_trigger"]': @@ -2192,6 +2255,24 @@ elements: | disabled: ':input[name="disabled_trigger"]': checked: true + disabled_item: + '#type': item + '#title': Item + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/783438">Issue #783438: #states doesn''t work for #type item</a>' + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true + disabled_label: + '#type': label + '#title': Label + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true disabled_language_select: '#type': language_select '#title': 'Language select' @@ -2229,8 +2310,8 @@ elements: | disabled: ':input[name="disabled_trigger"]': checked: true - disabled_webform_location: - '#type': webform_location + disabled_webform_location_places: + '#type': webform_location_places '#title': Location '#map': true '#geolocation': true @@ -2268,6 +2349,16 @@ elements: | disabled: ':input[name="disabled_trigger"]': checked: true + disabled_webform_message: + '#type': webform_message + '#title': Message + '#message_type': warning + '#message_message': 'This is a <strong>warning</strong> message.' + '#description': '<b>Known Issues:</b><br /><a href="https://www.drupal.org/node/77245">Issue #77245: A place for JavaScript status messages</a>' + '#states': + disabled: + ':input[name="disabled_trigger"]': + checked: true disabled_webform_name: '#type': webform_name '#title': Name @@ -2342,7 +2433,7 @@ elements: | '#min': 0 '#max': 100 '#step': 1 - '#output': right + '#output': below '#output__field_prefix': $ '#output__field_suffix': '.00' '#states': @@ -2352,7 +2443,6 @@ elements: | disabled_webform_rating: '#type': webform_rating '#title': Rating - '#default_value': 1 '#states': disabled: ':input[name="disabled_trigger"]': @@ -2467,9 +2557,9 @@ elements: | '#title': 'Term checkboxes' '#vocabulary': tags '#default_value': - - 41 - - 48 - - 43 + - Loremipsum + - Oratione + - Dixisset '#states': disabled: ':input[name="disabled_trigger"]': @@ -2478,7 +2568,7 @@ elements: | '#type': webform_term_select '#title': 'Term select' '#vocabulary': tags - '#default_value': 41 + '#default_value': Loremipsum '#states': disabled: ':input[name="disabled_trigger"]': @@ -2576,6 +2666,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -2583,6 +2674,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -2598,22 +2690,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -2624,6 +2731,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -2642,9 +2750,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -2697,4 +2807,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_autocomplete.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_autocomplete.yml new file mode 100644 index 0000000000..193079f38b --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_autocomplete.yml @@ -0,0 +1,315 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_autocomplete +title: 'Test: Form API #states autocomplete' +description: 'Test Drupal''s #states autocomplete.' +category: 'Test: Form API #states' +elements: | + select: + '#type': select + '#title': select + '#options': + One: One + Two: Two + Three: Three + select_multiple: + '#type': select + '#title': select_multiple + '#multiple': true + '#options': + One: One + Two: Two + Three: Three + webform_select_other: + '#type': webform_select_other + '#title': webform_select_other + '#options': + One: One + Two: Two + Three: Three + radios: + '#type': radios + '#title': radios + '#options': + One: One + Two: Two + Three: Three + webform_radios_other: + '#type': webform_radios_other + '#title': webform_radios_other + '#options': + One: One + Two: Two + Three: Three + checkboxes: + '#type': checkboxes + '#title': checkboxes + '#options': + One: One + Two: Two + Three: Three + webform_checkboxes_other: + '#type': webform_checkboxes_other + '#title': webform_checkboxes_other + '#options': + One: One + Two: Two + Three: Three + webform_buttons: + '#type': webform_buttons + '#title': webform_buttons + '#options': + One: One + Two: Two + Three: Three + webform_buttons_other: + '#type': webform_buttons_other + '#title': webform_buttons_other + '#options': + One: One + Two: Two + Three: Three + tableselect_single: + '#type': tableselect + '#title': tableselect_single + '#multiple': false + '#options': + One: One + Two: Two + Three: Three + tableselect_multiple: + '#type': tableselect + '#title': tableselect_multiple + '#options': + One: One + Two: Two + Three: Three + webform_entity_select: + '#type': webform_entity_select + '#title': webform_entity_select + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + webform_likert: + '#type': webform_likert + '#title': webform_likert + '#questions': + q1: 'Question 1' + q2: 'Question 2' + q3: 'Question 3' + '#answers': + 1: 'Option 1' + 2: 'Option 2' + 3: 'Option 3' + webform_address: + '#type': webform_address + '#title': webform_address + '#state_province__type': webform_select_other + webform_custom_composite: + '#type': webform_custom_composite + '#title': webform_custom_composite + '#element': + textfield: + '#type': textfield + '#title': textfield + select: + '#type': select + '#title': select + '#options': + One: One + Two: Two + Three: Three + webform_element_composite: + '#type': webform_element_composite + '#title': webform_element_composite + '#default_value': + first_name: + '#type': textfield + '#title': 'First name' + last_name: + '#type': textfield + '#title': 'Last name' + gender: + '#type': select + '#options': + Male: Male + Female: Female + '#title': Gender + webform_image_select: + '#type': webform_image_select + '#title': webform_image_select + '#images': + kitten_1: + text: 'Cute Kitten 1' + src: 'http://placekitten.com/220/200' + kitten_2: + text: 'Cute Kitten 2' + src: 'http://placekitten.com/180/200' + kitten_3: + text: 'Cute Kitten 3' + src: 'http://placekitten.com/130/200' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_crosspage.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_crosspage.yml new file mode 100644 index 0000000000..04e2c52054 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_crosspage.yml @@ -0,0 +1,201 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_crosspage +title: 'Test: Form API #states cross page conditions' +description: 'Test Drupal''s #states cross page conditions.' +category: 'Test: Form API #states' +elements: | + page_1: + '#type': webform_wizard_page + '#title': page_1 + trigger_1: + '#type': checkbox + '#title': trigger_1 + dependent_1: + '#type': textfield + '#title': dependent_1 + '#states': + visible: + ':input[name="trigger_1"]': + checked: true + ':input[name="trigger_2"]': + checked: true + page_2: + '#type': webform_wizard_page + '#title': page_2 + trigger_2: + '#type': checkbox + '#title': trigger_2 + dependent_2: + '#type': textfield + '#title': dependent_2 + '#states': + visible: + ':input[name="trigger_1"]': + checked: true + ':input[name="trigger_2"]': + checked: true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_clear.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_clear.yml new file mode 100644 index 0000000000..eed6d97e3d --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_clear.yml @@ -0,0 +1,266 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_server_clear +title: 'Test: Form API #states save hide/show element without clearing default value' +description: 'Test Drupal's #states API hide/show element without clearing default value.' +category: 'Test: Form API #states' +elements: | + trigger_checkbox: + '#type': checkbox + '#title': trigger_checkbox + dependent_hidden: + '#type': hidden + '#title': dependent_hidden + '#default_value': '{dependent_hidden}' + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_checkbox: + '#type': checkbox + '#title': dependent_checkbox + '#default_value': true + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_radios: + '#type': radios + '#title': dependent_radios + '#default_value': One + '#options': + One: One + Two: Two + Three: Three + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_value: + '#type': value + '#title': dependent_value + '#value': '{value}' + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_textfield: + '#type': textfield + '#title': dependent_textfield + '#default_value': '{dependent_textfield}' + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_textfield_multiple: + '#type': textfield + '#title': dependent_textfield + '#multiple': true + '#default_value': + - '{dependent_textfield}' + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_webform_name: + '#type': webform_name + '#title': webform_name + '#multiple': true + '#default_value': + - first: John + last: Smith + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_details: + '#type': details + '#title': dependent_details + '#states_clear': false + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_details_textfield: + '#type': textfield + '#title': dependent_details_textfield + '#default_value': '{dependent_details_textfield}' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_comp.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_comp.yml similarity index 74% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_comp.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_comp.yml index 5e6d5b681c..11fd1f842a 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_comp.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_comp.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_comp +archive: false +id: test_states_server_comp title: 'Test: Form API #states server-side composite elements' description: 'Test Drupal''s #states API for composite elements via server-side.' category: 'Test: Form API #states' @@ -56,6 +58,24 @@ elements: | visible: ':input[name="webform_name_multiple_header_trigger"]': checked: true + webform_name_nested_trigger: + '#type': checkbox + '#title': webform_name_nested_trigger + '#default_value': false + webform_name_nested_trigger_fieldset: + '#type': fieldset + '#title': webform_name_nested_fieldset + '#states': + visible: + ':input[name="webform_name_nested_trigger"]': + checked: true + webform_name_nested: + '#type': webform_name + '#title': webform_name_nested + '#first__required': true + '#first__title': webform_name_nested_first + '#last__required': true + '#last__title': webform_name_nested_last css: '' javascript: '' settings: @@ -64,6 +84,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -71,6 +92,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -86,22 +108,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -112,6 +149,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -130,9 +168,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -185,4 +225,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_containers.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_containers.yml new file mode 100644 index 0000000000..3d8c17a297 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_containers.yml @@ -0,0 +1,229 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_server_containers +title: 'Test: Form API #states nested elements in a conditional hidden container' +description: 'Test Drupal''s #states for nested elements in a conditional hidden container.' +category: 'Test: Form API #states' +elements: | + visible_trigger: + '#type': checkbox + '#title': visible_trigger + visible_fieldset: + '#type': fieldset + '#title': visible_fieldset + '#states': + visible: + ':input[name="visible_trigger"]': + checked: true + visible_textfield: + '#type': textfield + '#title': visible_textfield + '#required': true + visible_composite: + '#type': webform_custom_composite + '#title': visible_composite + '#element': + textfield: + '#type': textfield + '#title': textfield + '#required': true + select_other: + '#type': webform_select_other + '#title': select_other + '#options': + one: One + two: Two + '#required': true + visible_custom_textfield: + '#type': textfield + '#title': visible_custom_textfield + '#states': + required: + ':input[name="visible_trigger"]': + checked: true + ':input[name="visible_textfield"]': + filled: true + visible_slide_fieldset: + '#type': fieldset + '#title': visible_slide_fieldset + '#states': + visible-slide: + ':input[name="visible_trigger"]': + checked: true + visible_slide_textfield: + '#type': textfield + '#title': visible_slide_textfield + '#required': true + visible_slide_custom_textfield: + '#type': textfield + '#title': visible_slide_custom_textfield + '#states': + required: + ':input[name="visible_trigger"]': + checked: true + ':input[name="visible_slide_textfield"]': + filled: true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_custom.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_custom.yml similarity index 83% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_custom.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_custom.yml index 30a8db412e..c3ea741a0d 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_custom.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_custom.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_custom +archive: false +id: test_states_server_custom title: 'Test: Form API #states custom pattern, less, and greater condition validation' description: 'Test Drupal''s #states API custom pattern, less, and greater condition validation.' category: 'Test: Form API #states' @@ -40,11 +42,11 @@ elements: | visible: ':input[name="trigger_not_pattern"]': value: - '!pattern': '^$' + '!pattern': ^$ required: ':input[name="trigger_not_pattern"]': value: - '!pattern': '^$' + '!pattern': ^$ trigger_less: '#type': number '#title': trigger_less @@ -95,6 +97,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -102,6 +105,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -117,22 +121,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -143,6 +162,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -161,9 +181,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -216,4 +238,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_multiple.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_multiple.yml similarity index 78% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_multiple.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_multiple.yml index fc188b16f2..0e4bcc3caa 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_multiple.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_multiple.yml @@ -6,16 +6,18 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_multiple +archive: false +id: test_states_server_multiple title: 'Test: Form API #states server-side multiple elements' description: 'Test Drupal''s #states API for multiple elements via server-side.' category: 'Test: Form API #states' elements: | trigger_required: '#type': checkbox - '#title': trigger_required + '#title': trigger_required '#default_value': false textfield_multiple: '#type': textfield @@ -33,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -40,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -55,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -81,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -99,9 +119,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -154,4 +176,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_nested.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_nested.yml new file mode 100644 index 0000000000..212b383060 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_nested.yml @@ -0,0 +1,214 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_server_nested +title: 'Test: Form API #states nested conditions' +description: 'Test Drupal''s #states nested conditions support.' +category: 'Test: Form API #states' +elements: | + page_1: + '#type': webform_wizard_page + '#title': page_1 + a: + '#type': checkbox + '#title': a + '#default_value': true + b: + '#type': checkbox + '#title': b + '#default_value': true + c: + '#type': checkbox + '#title': c + page_1_target: + '#type': textfield + '#title': 'page_1_target: [a and b] or c = required' + '#states': + required: + - ':input[name="c"]': + checked: true + - or + - + - ':input[name="a"]': + checked: true + ':input[name="b"]': + checked: true + page_2: + '#type': webform_wizard_page + '#title': page_2 + page_2_target: + '#type': textfield + '#title': 'page_2_target: [a and b] or c = required' + '#states': + required: + - ':input[name="c"]': + checked: true + - or + - + - ':input[name="a"]': + checked: true + ':input[name="b"]': + checked: true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: true + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_preview.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_preview.yml similarity index 81% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_preview.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_preview.yml index 59fae3a4fe..1d9a651ee7 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_preview.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_preview.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_preview +archive: false +id: test_states_server_preview title: 'Test: Form API #states preview hide/show condition validation' description: 'Test Drupal''s #states API hide/show condition validation.' category: 'Test: Form API #states' @@ -58,6 +60,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -65,6 +68,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -80,22 +84,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -106,6 +125,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -124,9 +144,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -179,4 +201,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_required.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_required.yml similarity index 90% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_required.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_required.yml index ba2cb3d5b8..b4d3f59494 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_required.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_required.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_required +archive: false +id: test_states_server_required title: 'Test: Form API #states server-side required validation' description: 'Test Drupal''s #states API via server-side required validation.' category: 'Test: Form API #states' @@ -97,7 +99,7 @@ elements: | minlength_hidden_dependent: '#type': textfield '#title': minlength_hidden_dependent - '#minlength': 1 + '#minlength': 5 '#states': visible: ':input[name="minlength_hidden_trigger"]': @@ -121,6 +123,28 @@ elements: | required: ':input[name="checkboxes_trigger[one]"]': checked: true + checkboxes_other_trigger_details: + '#type': details + '#title': checkboxes_other_trigger + '#open': true + checkboxes_other_trigger: + '#type': webform_checkboxes_other + '#title': checkboxes_other_trigger + '#options': + one: One + two: Two + three: Three + checkboxes_other_dependent_required: + '#type': textfield + '#title': checkboxes_other_dependent_required + '#description': '<b>Required:</b> checkboxes_other[one]:checked=true OR checkboxes_other[other]:filled=true' + '#states': + required: + - ':input[name="checkboxes_other_trigger[checkboxes][one]"]': + checked: true + - or + - ':input[name="checkboxes_other_trigger[other]"]': + filled: true text_format_trigger_details: '#type': details '#title': text_format_trigger @@ -332,7 +356,7 @@ elements: | '#states': required: ':input[name="composite_required_trigger"]': - 'checked': true + checked: true composite_sub_elements_required_details: '#type': details '#title': composite_sub_elements_required @@ -414,6 +438,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -421,6 +446,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -436,22 +462,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -462,6 +503,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -480,9 +522,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -535,4 +579,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_save.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_save.yml new file mode 100644 index 0000000000..e01f6cd5b2 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_save.yml @@ -0,0 +1,234 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_states_server_save +title: 'Test: Form API #states save hide/show element' +description: 'Test Drupal's #states API hide/show element.' +category: 'Test: Form API #states' +elements: | + trigger_checkbox: + '#type': checkbox + '#title': trigger_checkbox + dependent_hidden: + '#type': hidden + '#title': dependent_hidden + '#default_value': '{dependent_hidden}' + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_checkbox: + '#type': checkbox + '#title': dependent_checkbox + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_value: + '#type': value + '#title': dependent_value + '#value': '{value}' + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_textfield: + '#type': textfield + '#title': dependent_textfield + '#default_value': '{dependent_textfield}' + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_textfield_multiple: + '#type': textfield + '#title': dependent_textfield + '#multiple': true + '#default_value': + - '{dependent_textfield}' + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_details: + '#type': details + '#title': dependent_details + '#states': + visible: + ':input[name="trigger_checkbox"]': + checked: true + dependent_details_textfield: + '#type': textfield + '#title': dependent_details_textfield + '#default_value': '{dependent_details_textfield}' +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: true + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: message + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: true + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_wizard.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_wizard.yml similarity index 87% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_wizard.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_wizard.yml index 91b28dbb9e..2d8ee71e27 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_server_wizard.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_server_wizard.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_server_wizard +archive: false +id: test_states_server_wizard title: 'Test: Form API #states server-side wizard pages' description: 'Test Drupal''s #states API for wizard pages via server-side.' category: 'Test: Form API #states' @@ -111,7 +113,7 @@ elements: | page_01_trigger_checkbox_computed: '#type': webform_computed_twig '#title': trigger_checkbox - '#value': '{{ data.page_01_trigger_checkbox ? ''Yes'' : ''No'' }}' + '#template': '{{ data.page_01_trigger_checkbox ? ''Yes'' : ''No'' }}' page_02_textfield_required: '#type': textfield '#title': page_02_textfield_required @@ -150,6 +152,7 @@ elements: | '#type': textfield '#title': page_02_textfield_visible '#description': '<b>Visible:</b> page_01_trigger_checkbox:checked' + '#default_value': '{default_value}' '#states': visible: ':input[name="page_01_trigger_checkbox"]': @@ -158,24 +161,27 @@ elements: | '#type': textfield '#title': page_02_textfield_visible_slide '#description': '<b>Visible (Slide):</b> page_01_trigger_checkbox:checked' + '#default_value': '{default_value}' '#states': - 'visible-slide': + visible-slide: ':input[name="page_01_trigger_checkbox"]': checked: true page_02_textfield_invisible: '#type': textfield - '#title': 'page_02_textfield_invisible' + '#title': page_02_textfield_invisible '#description': '<b>Invisible:</b> page_01_trigger_checkbox:checked' + '#default_value': '{default_value}' '#states': invisible: ':input[name="page_01_trigger_checkbox"]': checked: true page_02_textfield_invisible_slide: '#type': textfield - '#title': 'page_02_textfield_invisible_slide' + '#title': page_02_textfield_invisible_slide '#description': '<b>Invisible (Slide):</b> page_01_trigger_checkbox:checked' + '#default_value': '{default_value}' '#states': - 'invisible-slide': + invisible-slide: ':input[name="page_01_trigger_checkbox"]': checked: true page_02_checkbox_checked: @@ -222,6 +228,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -229,6 +236,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -244,22 +252,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -270,6 +293,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -288,9 +312,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -343,4 +369,16 @@ access: roles: { } users: { } permissions: { } -handlers: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 1 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_triggers.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_triggers.yml similarity index 87% rename from web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_triggers.yml rename to web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_triggers.yml index a55bd34c10..3e3998c97f 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_form_states_triggers.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_states_triggers.yml @@ -6,9 +6,11 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false -id: test_form_states_triggers +archive: false +id: test_states_triggers title: 'Test: Form API #states: Triggers and Operators' description: 'Test Drupal''s #states API triggers (empty, checked, value, and collapsed).' category: 'Test: Form API #states' @@ -136,6 +138,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -143,6 +146,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -158,22 +162,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -184,6 +203,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -202,9 +222,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -257,4 +279,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_label.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_label.yml index 3b3a901363..959085eacb 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_label.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_label.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: 1 template: false +archive: false id: test_submission_label title: 'Test: Submission: Label' description: 'Test custom submission label' @@ -25,6 +27,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -32,6 +35,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -47,22 +51,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: 'Submitted by [webform_submission:values:name]' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -73,6 +92,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -91,9 +111,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -146,4 +168,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_log.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_log.yml index e91d75fb15..8c399e8768 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_log.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_log.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_submission_log title: 'Test: Submission: Logging' description: 'Test submission event logging.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: true + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,4 +167,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_views.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_views.yml new file mode 100644 index 0000000000..5c12a3b432 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_submission_views.yml @@ -0,0 +1,204 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test +open: null +close: null +weight: 0 +uid: 1 +template: false +archive: false +id: test_submission_views +title: 'Test: Webform submission views' +description: '' +category: '' +elements: | + textfield: + '#type': textfield + '#title': textfield +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: + admin: + view: 'webform_submissions:embed_administer' + title: 'Administer submissions' + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions + manage: + view: 'webform_submissions:embed_manage' + title: 'Manage submissions' + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions + review: + view: 'webform_submissions:embed_review' + title: 'Review submissions' + webform_routes: + - entity.webform.results_submissions + node_routes: + - entity.node.webform.results_submissions + user: + view: 'webform_submissions:embed_default' + title: 'User submissions' + webform_routes: + - entity.webform.user.drafts + - entity.webform.user.submissions + node_routes: + - entity.node.webform.user.drafts + - entity.node.webform.user.submissions + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: all + draft_multiple: true + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token.yml index 3c7126a462..d12c61d868 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: '2000-01-01T00:00:00-05:00' close: '2020-01-01T00:00:00-05:00' +weight: 0 uid: null template: false +archive: false id: test_token title: 'Test: Token' description: 'Test Webform and Webform Submission tokens' @@ -29,6 +31,7 @@ elements: | Open long: [webform:open:long] Close: [webform:close] Close long: [webform:close:long] + webform_submission_tokens: '#type': webform_codemirror '#mode': text @@ -59,12 +62,14 @@ elements: | URL: [webform_submission:url] URL Edit Webform: [webform_submission:url:edit-form] UUID: [webform_submission:uuid] + webform_submission_data_tokens: '#type': webform_codemirror '#mode': text '#title': 'Webform Submission Data Tokens' '#default_value': | Submission values: [webform_submission:values] + webform_submission_source_entity_tokens: '#type': webform_codemirror '#mode': text @@ -93,6 +98,7 @@ elements: | Summary: [webform_submission:source-entity:summary] Title: [webform_submission:source-entity:title] URL: [webform_submission:source-entity:url] + webform_submission_node_tokens: '#type': webform_codemirror '#mode': text @@ -129,6 +135,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -136,6 +143,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -151,22 +159,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -177,6 +200,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -195,9 +219,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -250,6 +276,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -261,23 +291,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default + from_name: _default + subject: _default body: '<a href="[webform_submission:update-url]">[webform_submission:update-url]</a>' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_submission_value.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_submission_value.yml index 64eeb9aabf..71aee70508 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_submission_value.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_submission_value.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_token_submission_value title: 'Test: Token submission value' description: 'Test Webform submission values tokens' @@ -16,18 +18,18 @@ elements: | email: '#type': email '#title': email - '#default_value': 'example@example.com' + '#default_value': example@example.com emails: '#type': email '#title': emails '#multiple': true '#default_value': - - 'one@example.com' - - 'two@example.com' - - 'three@example.com' + - one@example.com + - two@example.com + - three@example.com user: '#type': webform_entity_select - '#title': 'user' + '#title': user '#target_type': user '#selection_handler': 'default:user' '#selection_settings': @@ -35,7 +37,7 @@ elements: | '#default_value': 1 users: '#type': webform_entity_select - '#title': 'user' + '#title': user '#multiple': true '#target_type': user '#selection_handler': 'default:user' @@ -76,8 +78,8 @@ elements: | '#type': webform_contact '#title': contact '#default_value': - name: John Smith - email: 'john@example.com' + name: 'John Smith' + email: john@example.com address: '10 Main Street' city: Springfield state_province: Alabama @@ -89,14 +91,14 @@ elements: | '#multiple': true '#default_value': - name: 'John Smith' - email: 'john@example.com' + email: john@example.com address: '10 Main Street' city: Springfield state_province: Alabama postal_code: 12345 country: 'United States' - name: 'Jane Doe' - email: 'jane@example.com' + email: jane@example.com address: '10 Main Street' city: Springfield state_province: Alabama @@ -113,6 +115,18 @@ elements: | '#type': textfield '#title': last_name '#default_value': Smith + url: + '#type': url + '#title': url + '#default_value': 'http://example.com?query=param' + markup: + '#type': textfield + '#title': markup + '#default_value': '<b>Bold</b> & UPPERCASE' + script: + '#type': textfield + '#title': script + '#default_value': '<script>alert(''hi'');</script>' css: '' javascript: '' settings: @@ -121,6 +135,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -128,6 +143,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -143,22 +159,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -169,6 +200,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -201,6 +233,12 @@ settings: <tr><th width="50%">webform_submission:values:users:99:entity:account-name</th><td width="50%">[webform_submission:values:users:99:entity:account-name]</td></tr> </table> + <h3>current-user</h3> + <table class="table"> + <tr><th width="50%">current-user:display-name</th><td width="50%">[current-user:display-name]</td></tr> + <tr><th width="50%">current-user:missing</th><td width="50%">[current-user:missing]</td></tr> + </table> + <h3>terms</h3> <table class="table"> <tr><th width="50%">webform_submission:values:term</th><td width="50%">[webform_submission:values:term]</td></tr> @@ -256,7 +294,25 @@ settings: <table class="table"> <tr><th width="50%">webform_submission:values:missing</th><td width="50%">[webform_submission:values:missing]</td></tr> <tr><th width="50%">webform_submission:values:missing:clear</th><td width="50%">[webform_submission:values:missing:clear]</td></tr> + <tr><th width="50%">webform:random:missing</th><td width="50%">[webform:random:missing]</td></tr> + <tr><th width="50%">webform:random:missing:clear</th><td width="50%">[webform:random:missing:clear]</td></tr> </table> + + <h3>urlencode</h3> + <table class="table"> + <tr><th width="50%">webform_submission:values:url</th><td width="50%">[webform_submission:values:url]</td></tr> + <tr><th width="50%">webform_submission:values:url:urlencode</th><td width="50%">[webform_submission:values:url:urlencode]</td></tr> + </table> + + <h3>htmldecode</h3> + <table class="table"> + <tr><th width="50%">webform_submission:values:markup</th><td width="50%">[webform_submission:values:markup]</td></tr> + <tr><th width="50%">webform_submission:values:markup:htmldecode</th><td width="50%">[webform_submission:values:markup:htmldecode]</td></tr> + <tr><th width="50%">webform_submission:values:markup:htmldecode:striptags</th><td width="50%">[webform_submission:values:markup:htmldecode:striptags]</td></tr> + <tr><th width="50%">webform_submission:values:script</th><td width="50%">[webform_submission:values:script]</td></tr> + <tr><th width="50%">webform_submission:values:script:htmldecode</th><td width="50%">[webform_submission:values:script:htmldecode]</td></tr> + </table> + confirmation_url: '' confirmation_attributes: { } confirmation_back: true @@ -267,9 +323,11 @@ settings: limit_total: 100 limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: 10 limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: 50 entity_limit_total_interval: null entity_limit_user: 5 @@ -322,4 +380,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_update.yml b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_update.yml index afb597d3c1..53a644eb87 100644 --- a/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_update.yml +++ b/web/modules/webform/tests/modules/webform_test/config/install/webform.webform.test_token_update.yml @@ -6,8 +6,10 @@ dependencies: - webform_test open: null close: null +weight: 0 uid: null template: false +archive: false id: test_token_update title: 'Test: Token: Update' description: 'Test updating submission using secure token.' @@ -24,6 +26,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -31,6 +34,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -46,22 +50,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -72,6 +91,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -90,9 +110,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -145,6 +167,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email: id: email @@ -156,23 +182,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default + from_name: _default + subject: _default body: '<a href="[webform_submission:update-url]">[webform_submission:update-url]</a>' excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_description_tooltip.inc b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_description_tooltip.inc index aa780e18fa..e0537f1e8d 100644 --- a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_description_tooltip.inc +++ b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_description_tooltip.inc @@ -5,8 +5,6 @@ * Generate test elements with with description tooltips. */ -use Drupal\Component\Utility\Unicode; - /** * Generate test element description tooltip. * @@ -35,7 +33,7 @@ function webform_test_test_element_description_tooltip() { } $category_name = (string) $webform_element->getPluginDefinition()['category'] ?: 'Other elements'; - $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', Unicode::strtolower($category_name)); + $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', mb_strtolower($category_name)); if (empty($data[$category_id])) { $data[$category_id] = [ '#type' => 'details', diff --git a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_format.inc b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_format.inc index a1b92ddbbf..512c0816a2 100644 --- a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_format.inc +++ b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_format.inc @@ -5,7 +5,6 @@ * Generate test elements with formatting. */ -use Drupal\Component\Utility\Unicode; use Drupal\webform\WebformInterface; /** @@ -93,7 +92,7 @@ function webform_test_test_element_format(WebformInterface $webform, $composite // Set default (test) value. if (strpos($element_type, 'date') === 0) { - $element['#default_value'] = '1942-06-18'; + $element['#default_value'] = ($multiple) ? ['1942-06-18', '1940-07-07', '1943-02-25'] : '1942-06-18'; } elseif ($default_value = $submission_generate->getTestValue($webform, $element_type, $element, ['random' => FALSE])) { $element['#default_value'] = $default_value; @@ -106,7 +105,7 @@ function webform_test_test_element_format(WebformInterface $webform, $composite // Set element category. $category_name = (string) $webform_element->getPluginDefinition()['category'] ?: 'Other elements'; - $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', Unicode::strtolower($category_name)); + $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', mb_strtolower($category_name)); if (empty($data[$category_id])) { $data[$category_id] = [ '#type' => 'details', diff --git a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_icheck_styles.inc b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_icheck_styles.inc index cd5bc1ec41..3b4cbde0eb 100644 --- a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_icheck_styles.inc +++ b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_element_icheck_styles.inc @@ -61,7 +61,7 @@ function webform_test_test_element_icheck_styles() { 'three' => 'Three', ], '#default_value' => 'one', - '#wrapper_attributes' => ['class' => 'container-inline'], + '#wrapper_attributes' => ['class' => ['container-inline']], '#icheck' => $style, ]; $elements[$skin_name][$name . '_radios'] = [ diff --git a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_example_elements.inc b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_example_elements.inc index 835c3f4948..d9a823285a 100644 --- a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_example_elements.inc +++ b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_example_elements.inc @@ -5,8 +5,6 @@ * Generate examples of all elements. */ -use Drupal\Component\Utility\Unicode; - /** * Generate examples of all elements. * @@ -38,7 +36,7 @@ function webform_test_test_example_elements() { } $category_name = (string) $webform_element->getPluginDefinition()['category'] ?: 'Other elements'; - $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', Unicode::strtolower($category_name)); + $category_id = preg_replace('/[^a-zA-Z0-9]+/', '_', mb_strtolower($category_name)); if (empty($data[$category_id])) { $data[$category_id] = [ '#type' => 'details', diff --git a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_form_states.inc b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_form_states.inc index 18a2841a3d..eb86179d37 100644 --- a/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_form_states.inc +++ b/web/modules/webform/tests/modules/webform_test/includes/webform_test.test_form_states.inc @@ -13,7 +13,7 @@ * @return array * An array containing test elements with #states. */ -function webform_test_test_form_states() { +function webform_test_test_states() { $elements = []; // Visible. @@ -25,7 +25,7 @@ function webform_test_test_form_states() { '#type' => 'checkbox', '#title' => 'Displays and require elements', ]; - $elements['visible'] += _webform_test_form_states('visible', [ + $elements['visible'] += _webform_test_states('visible', [ '#states' => [ 'visible' => [ ':input[name="visible_trigger"]' => [ @@ -49,7 +49,7 @@ function webform_test_test_form_states() { '#type' => 'checkbox', '#title' => 'Hide and empty elements', ]; - $elements['invisible'] += _webform_test_form_states('invisible', [ + $elements['invisible'] += _webform_test_states('invisible', [ '#states' => [ 'invisible' => [ ':input[name="invisible_trigger"]' => [ @@ -68,7 +68,7 @@ function webform_test_test_form_states() { '#type' => 'checkbox', '#title' => 'Disable elements', ]; - $elements['disabled'] += _webform_test_form_states('disabled', [ + $elements['disabled'] += _webform_test_states('disabled', [ '#states' => [ 'disabled' => [ ':input[name="disabled_trigger"]' => [ @@ -92,7 +92,7 @@ function webform_test_test_form_states() { * @return array * A render array of example elements */ -function _webform_test_form_states($type, array $default_properties = []) { +function _webform_test_states($type, array $default_properties = []) { $data = [ 'containers' => [], 'elements' => [], diff --git a/web/modules/webform/tests/modules/webform_test/webform_test.info.yml b/web/modules/webform/tests/modules/webform_test/webform_test.info.yml index 25972c866f..ab9a74e2b3 100644 --- a/web/modules/webform/tests/modules/webform_test/webform_test.info.yml +++ b/web/modules/webform/tests/modules/webform_test/webform_test.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test/webform_test.module b/web/modules/webform/tests/modules/webform_test/webform_test.module index 0bb5af4137..a475549d0c 100644 --- a/web/modules/webform/tests/modules/webform_test/webform_test.module +++ b/web/modules/webform/tests/modules/webform_test/webform_test.module @@ -5,53 +5,10 @@ * Support module for webform related testing. */ -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Url; use Drupal\webform\Utility\WebformElementHelper; -/** - * Implements hook_webform_submission_form_alter(). - * - * Adds button to disable/enable HTML client-side validation without have - * to change any webform settings. - * - * The link is only applicable to webform ids that are prefix with test_*. - */ -function webform_test_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) { - if (!\Drupal::currentUser()->hasPermission('access devel information')) { - return; - } - - if (strpos($form['#webform_id'], 'test_') !== 0) { - return; - } - - if (\Drupal::request()->query->get('novalidate') == '1') { - $form['#attributes']['novalidate'] = TRUE; - } - elseif (\Drupal::request()->query->get('novalidate') == '0') { - unset($form['#attributes']['novalidate']); - } - - if (!empty($form['#attributes']['novalidate'])) { - $form['webform_test_novalidate'] = [ - '#type' => 'link', - '#title' => t('Enable client-side validation'), - '#url' => Url::fromRoute('<current>', [], ['query' => ['novalidate' => '0']]), - '#weight' => 1000, - ]; - } - else { - $form['webform_test_novalidate'] = [ - '#type' => 'link', - '#title' => t('Disable client-side validation'), - '#url' => Url::fromRoute('<current>', [], ['query' => ['novalidate' => '1']]), - '#weight' => 1000, - ]; - } -} - /******************************************************************************/ // Generate elements. /******************************************************************************/ @@ -65,7 +22,7 @@ function webform_test_preprocess_page(&$variables) { && _webform_test_load_include(\Drupal::routeMatch()->getRawParameter('webform')) ) { $t_args = [':href' => Url::fromRouteMatch(\Drupal::routeMatch())->toString() . '?generate']; - drupal_set_message(t('The below webform\'s elements are automatically generated and exported. You can regenerate the below elements by appending <a href=":href">?generate</a> to this page\'s URL.', $t_args), 'warning'); + \Drupal::messenger()->addWarning(t('The below webform\'s elements are automatically generated and exported. You can regenerate the below elements by appending <a href=":href">?generate</a> to this page\'s URL.', $t_args)); } } @@ -94,7 +51,7 @@ function webform_test_webform_load(array $entities) { ->save(); // Display message. - drupal_set_message(t('Generated elements for %title webform', ['%title' => $entity->label()])); + \Drupal::messenger()->addStatus(t('Generated elements for %title webform', ['%title' => $entity->label()])); } } } diff --git a/web/modules/webform/tests/modules/webform_test_ajax/config/optional/block.block.bartik_webform_test_ajax.yml b/web/modules/webform/tests/modules/webform_test_ajax/config/optional/block.block.bartik_webform_test_ajax.yml index f3fc5b4ecd..ad72fbc2db 100644 --- a/web/modules/webform/tests/modules/webform_test_ajax/config/optional/block.block.bartik_webform_test_ajax.yml +++ b/web/modules/webform/tests/modules/webform_test_ajax/config/optional/block.block.bartik_webform_test_ajax.yml @@ -17,9 +17,4 @@ settings: label: 'Test Webform Ajax Modal Dialogs' provider: webform_test_ajax label_display: visible -visibility: - request_path: - id: request_path - pages: '<front>' - negate: false - context_mapping: { } +visibility: { } diff --git a/web/modules/webform/tests/modules/webform_test_ajax/src/Plugin/Block/WebformTestAjaxBlock.php b/web/modules/webform/tests/modules/webform_test_ajax/src/Plugin/Block/WebformTestAjaxBlock.php index 0c32dc7f11..0352439bc2 100644 --- a/web/modules/webform/tests/modules/webform_test_ajax/src/Plugin/Block/WebformTestAjaxBlock.php +++ b/web/modules/webform/tests/modules/webform_test_ajax/src/Plugin/Block/WebformTestAjaxBlock.php @@ -28,7 +28,7 @@ class WebformTestAjaxBlock extends BlockBase implements ContainerFactoryPluginIn protected $redirectDestination; /** - * Creates a WebformBlock instance. + * Creates a WebformTestAjaxBlock instance. * * @param array $configuration * A configuration array containing information about the plugin instance. @@ -62,7 +62,7 @@ public static function create(ContainerInterface $container, array $configuratio public function build() { $webforms = Webform::loadMultiple(); - $links = []; + $ajax_links = []; foreach ($webforms as $webform_id => $webform) { if (strpos($webform_id, 'test_ajax') !== 0 && $webform_id != 'test_form_wizard_long_100') { continue; @@ -77,7 +77,7 @@ public function build() { $route_options = []; } - $links[$webform_id] = [ + $ajax_links[$webform_id] = [ 'title' => $this->t('Open @webform_id', ['@webform_id' => $webform_id]), 'url' => $webform->toUrl('canonical', $route_options), 'attributes' => [ @@ -92,9 +92,35 @@ public function build() { ]; } + $webform = Webform::load('contact'); + $inline_links = [ + 'webform' => [ + 'title' => $this->t('Open Contact'), + 'url' => $webform->toUrl('canonical'), + 'attributes' => [ + 'class' => ['webform-dialog', 'webform-dialog-normal'], + ], + ], + 'source_entity' => [ + 'title' => $this->t('Open Contact with Source Entity'), + 'url' => $webform->toUrl('canonical', ['query' => ['source_entity_type' => 'ENTITY_TYPE', 'source_entity_id' => 'ENTITY_ID']]), + 'attributes' => [ + 'class' => ['webform-dialog', 'webform-dialog-normal'], + ], + ], + ]; + return [ - '#theme' => 'links', - '#links' => $links, + 'ajax' => [ + '#prefix' => '<h3>' . $this->t('Ajax links') . '</h3>', + '#theme' => 'links', + '#links' => $ajax_links, + ], + 'inline' => [ + '#prefix' => '<h3>' . $this->t('Inline (Global) links') . '</h3>', + '#theme' => 'links', + '#links' => $inline_links, + ], '#attached' => ['library' => ['core/drupal.ajax']], ]; } diff --git a/web/modules/webform/tests/modules/webform_test_ajax/webform_test_ajax.info.yml b/web/modules/webform/tests/modules/webform_test_ajax/webform_test_ajax.info.yml index 7db72a4a01..4871326949 100644 --- a/web/modules/webform/tests/modules/webform_test_ajax/webform_test_ajax.info.yml +++ b/web/modules/webform/tests/modules/webform_test_ajax/webform_test_ajax.info.yml @@ -7,8 +7,8 @@ dependencies: - 'drupal:block' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.info.yml b/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.info.yml index 687771dc00..5b12b96fcc 100644 --- a/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.info.yml +++ b/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.module b/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.module index 4ffb9187fd..159a905527 100644 --- a/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.module +++ b/web/modules/webform/tests/modules/webform_test_alter_hooks/webform_test_alter_hooks.module @@ -16,7 +16,7 @@ */ function webform_test_alter_hooks_form_alter(&$form, FormStateInterface $form_state, $form_id) { if (strpos($form_id, 'webform_') === 0) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_alter()', '@form_id' => $form_id]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_alter()', '@form_id' => $form_id]), TRUE); } } @@ -24,7 +24,7 @@ function webform_test_alter_hooks_form_alter(&$form, FormStateInterface $form_st * Implements hook_form_webform_submission_form_alter(). */ function webform_test_alter_hooks_form_webform_submission_form_alter(array $form, FormStateInterface $form_state, $form_id) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_form_alter()', '@form_id' => $form_id]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_form_alter()', '@form_id' => $form_id]), TRUE); } /** @@ -35,7 +35,7 @@ function webform_test_alter_hooks_form_webform_submission_form_alter(array $form * @see \Drupal\Core\Form\FormBuilder::prepareForm */ function webform_test_alter_hooks_form_webform_submission_contact_form_alter(array $form, FormStateInterface $form_state, $form_id) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_BASE_FORM_ID_form_alter()', '@form_id' => $form_id]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_BASE_FORM_ID_form_alter()', '@form_id' => $form_id]), TRUE); } /** @@ -46,7 +46,7 @@ function webform_test_alter_hooks_form_webform_submission_contact_form_alter(arr * @see \Drupal\Core\Form\FormBuilder::prepareForm */ function webform_test_alter_hooks_form_webform_submission_contact_add_form_alter(array $form, FormStateInterface $form_state, $form_id) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_FORM_ID_form_alter()', '@form_id' => $form_id]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_FORM_ID_form_alter()', '@form_id' => $form_id]), TRUE); } /** @@ -57,7 +57,7 @@ function webform_test_alter_hooks_form_webform_submission_contact_add_form_alter * @see \Drupal\Core\Form\FormBuilder::prepareForm */ function webform_test_alter_hooks_form_webform_submission_contact_node_1_add_form_alter(array $form, FormStateInterface $form_state, $form_id) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_FORM_ID_form_alter()', '@form_id' => $form_id]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_form_webform_submission_FORM_ID_form_alter()', '@form_id' => $form_id]), TRUE); } /** @@ -65,8 +65,8 @@ function webform_test_alter_hooks_form_webform_submission_contact_node_1_add_for * * @see \Drupal\webform\WebformSubmissionForm::buildForm */ -function webform_test_alter_hooks_webform_submission_form_alter(array &$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { - drupal_set_message(t("@hook: '@form_id' executed.", ['@hook' => 'hook_webform_submission_form_alter()', '@form_id' => $form_id]), 'status', TRUE); +function webform_test_alter_hooks_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) { + \Drupal::messenger()->addStatus(t("@hook: '@form_id' executed.", ['@hook' => 'hook_webform_submission_form_alter()', '@form_id' => $form_id]), TRUE); } /******************************************************************************/ @@ -80,7 +80,7 @@ function webform_test_alter_hooks_webform_submission_form_alter(array &$form, \D * @see \Drupal\webform\WebformSubmissionForm::prepareElements */ function webform_test_alter_hooks_webform_element_alter(array &$element, FormStateInterface $form_state, array $context) { - drupal_set_message(t("@hook: '@webform_key' executed.", ['@hook' => 'hook_webform_element_alter()', '@webform_key' => $element['#webform_key']]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@webform_key' executed.", ['@hook' => 'hook_webform_element_alter()', '@webform_key' => $element['#webform_key']]), TRUE); } @@ -91,5 +91,5 @@ function webform_test_alter_hooks_webform_element_alter(array &$element, FormSta * @see \Drupal\webform\WebformSubmissionForm::prepareElements */ function webform_test_alter_hooks_webform_element_email_alter(array &$element, FormStateInterface $form_state, array $context) { - drupal_set_message(t("@hook: '@webform_key' executed.", ['@hook' => 'hook_webform_element_ELEMENT_TYPE_alter()', '@webform_key' => $element['#webform_key']]), 'status', TRUE); + \Drupal::messenger()->addStatus(t("@hook: '@webform_key' executed.", ['@hook' => 'hook_webform_element_ELEMENT_TYPE_alter()', '@webform_key' => $element['#webform_key']]), TRUE); } diff --git a/web/modules/webform/tests/modules/webform_test_block_context/webform_test_block_context.info.yml b/web/modules/webform/tests/modules/webform_test_block_context/webform_test_block_context.info.yml index 93c27bf201..14673cfa8f 100644 --- a/web/modules/webform/tests/modules/webform_test_block_context/webform_test_block_context.info.yml +++ b/web/modules/webform/tests/modules/webform_test_block_context/webform_test_block_context.info.yml @@ -7,8 +7,8 @@ dependencies: - 'drupal:block' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_block_custom/webform_test_block_custom.info.yml b/web/modules/webform/tests/modules/webform_test_block_custom/webform_test_block_custom.info.yml index 71f9955c4d..65fc12c841 100644 --- a/web/modules/webform/tests/modules/webform_test_block_custom/webform_test_block_custom.info.yml +++ b/web/modules/webform/tests/modules/webform_test_block_custom/webform_test_block_custom.info.yml @@ -8,8 +8,8 @@ dependencies: - 'drupal:block_content' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_block_submission_limit/webform_test_block_submission_limit.info.yml b/web/modules/webform/tests/modules/webform_test_block_submission_limit/webform_test_block_submission_limit.info.yml index cfa7e96cde..7d7a268506 100644 --- a/web/modules/webform/tests/modules/webform_test_block_submission_limit/webform_test_block_submission_limit.info.yml +++ b/web/modules/webform/tests/modules/webform_test_block_submission_limit/webform_test_block_submission_limit.info.yml @@ -7,8 +7,8 @@ dependencies: - 'drupal:block' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_config_performance/webform_test_config_performance.info.yml b/web/modules/webform/tests/modules/webform_test_config_performance/webform_test_config_performance.info.yml index f48f1e1c6c..da55a630d7 100644 --- a/web/modules/webform/tests/modules/webform_test_config_performance/webform_test_config_performance.info.yml +++ b/web/modules/webform/tests/modules/webform_test_config_performance/webform_test_config_performance.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_custom_properties/webform_test_custom_properties.info.yml b/web/modules/webform/tests/modules/webform_test_custom_properties/webform_test_custom_properties.info.yml index f781244543..993734001e 100644 --- a/web/modules/webform/tests/modules/webform_test_custom_properties/webform_test_custom_properties.info.yml +++ b/web/modules/webform/tests/modules/webform_test_custom_properties/webform_test_custom_properties.info.yml @@ -7,8 +7,8 @@ dependencies: - 'webform:webform' - 'webform:webform_ui' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_comp_file_plugin.yml b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_comp_file_plugin.yml new file mode 100644 index 0000000000..6cbf785a83 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_comp_file_plugin.yml @@ -0,0 +1,191 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test_element +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_comp_file_plugin +title: 'Test: Element: Composite custom file upload plugin' +description: 'Test custom webform composite element with file upload.' +category: 'Test: Element' +elements: | + webform_test_composite_file: + '#type': webform_test_composite_file + '#title': webform_test_composite_file + webform_test_composite_file_multiple: + '#type': webform_test_composite_file + '#title': webform_test_composite_file_multiple + '#multiple': true + webform_test_composite_file_multiple_header: + '#type': webform_test_composite_file + '#title': webform_test_composite_file_multiple_header + '#multiple': true + '#multiple__header': true +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_composite_plugin.yml b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_composite_plugin.yml index 650259d2e6..3af15449b0 100644 --- a/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_composite_plugin.yml +++ b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_composite_plugin.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_element open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_composite_plugin title: 'Test: Element: Composite custom plugin' description: 'Test custom webform composite element.' @@ -33,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -40,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -55,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -81,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -99,9 +119,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -154,6 +176,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: debug: id: debug diff --git a/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_plugin.yml b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_plugin.yml index 775f734d19..3c7dd25bd9 100644 --- a/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_plugin.yml +++ b/web/modules/webform/tests/modules/webform_test_element/config/install/webform.webform.test_element_plugin.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_element open: null close: null +weight: 0 uid: null template: false +archive: false id: test_element_plugin title: 'Test: Element: Test (plugin)' description: 'Test the webform element plugin integration and method invoking.' @@ -16,6 +18,7 @@ elements: | description: '#markup': | <p>This webform includes a #test elememt which will trigger all methods associated with a WebformElement plugin.</p> + test: '#type': webform_test_element '#title': 'This is a test element' @@ -28,6 +31,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -35,6 +39,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -50,22 +55,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -76,6 +96,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -94,9 +115,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -149,4 +172,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestComposite.php b/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestComposite.php index 56f61e0f06..1036f67ce2 100644 --- a/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestComposite.php +++ b/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestComposite.php @@ -110,7 +110,6 @@ public static function getCompositeElements(array $element) { // @see \Drupal\webform\Element\WebformCompositeBase::processWebformComposite // $elements['checkboxes'] = ['#type' => 'checkboxes']; // $elements['likert'] = ['#type' => 'webform_likert']; - // $elements['likert'] = ['#type' => 'managed_file']; // $elements['datetime'] = ['#type' => 'datetime']; return $elements; } diff --git a/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestCompositeFile.php b/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestCompositeFile.php new file mode 100644 index 0000000000..9b467e94fc --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_element/src/Element/WebformTestCompositeFile.php @@ -0,0 +1,37 @@ +<?php + +namespace Drupal\webform_test_element\Element; + +use Drupal\webform\Element\WebformCompositeBase; + +/** + * Provides a webform composite element file for testing. + * + * @FormElement("webform_test_composite_file") + */ +class WebformTestCompositeFile extends WebformCompositeBase { + + /** + * {@inheritdoc} + */ + public static function getCompositeElements(array $element) { + $elements = []; + $elements['textfield'] = [ + '#type' => 'textfield', + '#title' => t('textfield'), + ]; + $elements['managed_file'] = [ + '#type' => 'managed_file', + '#title' => 'managed_file', + ]; + return $elements; + } + + /** + * {@inheritdoc} + */ + public function preview() { + return []; + } + +} diff --git a/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestCompositeFile.php b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestCompositeFile.php new file mode 100644 index 0000000000..70878518dc --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestCompositeFile.php @@ -0,0 +1,28 @@ +<?php + +namespace Drupal\webform_test_element\Plugin\WebformElement; + +use Drupal\webform\Plugin\WebformElement\WebformCompositeBase; + +/** + * Provides a 'webform_test_composite_file' element. + * + * @WebformElement( + * id = "webform_test_composite_file", + * label = @Translation("Test composite element file"), + * description = @Translation("Provides a Webform composite file element for testing."), + * multiline = TRUE, + * composite = TRUE, + * states_wrapper = TRUE, + * ) + */ +class WebformTestCompositeFile extends WebformCompositeBase { + + /** + * {@inheritdoc} + */ + public function preview() { + return []; + } + +} diff --git a/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElement.php b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElement.php index 5229257bd3..e4709b39af 100644 --- a/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElement.php +++ b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElement.php @@ -123,7 +123,7 @@ public function postSave(array &$element, WebformSubmissionInterface $webform_su protected function displayMessage($method_name, $context1 = NULL) { if (PHP_SAPI != 'cli') { $t_args = ['@class_name' => get_class($this), '@method_name' => $method_name, '@context1' => $context1]; - drupal_set_message($this->t('Invoked: @class_name:@method_name @context1', $t_args)); + $this->messenger()->addStatus($this->t('Invoked: @class_name:@method_name @context1', $t_args)); } } @@ -131,7 +131,7 @@ protected function displayMessage($method_name, $context1 = NULL) { * Form API callback. Convert password confirm array to single value. */ public static function validate(array &$element, FormStateInterface $form_state) { - drupal_set_message(t('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement::validate')); + \Drupal::messenger()->addStatus(t('Invoked: Drupal\webform_test_element\Plugin\WebformElement\WebformTestElement::validate')); } } diff --git a/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElementProperties.php b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElementProperties.php index 54023fc036..3d4fe3d3d4 100644 --- a/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElementProperties.php +++ b/web/modules/webform/tests/modules/webform_test_element/src/Plugin/WebformElement/WebformTestElementProperties.php @@ -106,12 +106,12 @@ public function form(array $form, FormStateInterface $form_state) { 'wrapper' => $ajax_wrapper_id, 'progress' => ['type' => 'fullscreen'], ], - // Hide button, add submit button trigger class, and disable validation. + // Disable validation, hide button, add submit button trigger class. '#attributes' => [ + 'formnovalidate' => 'formnovalidate', 'class' => [ 'js-hide', 'js-webform-test-properties-submit', - 'js-webform-novalidate', ], ], ]; diff --git a/web/modules/webform/tests/modules/webform_test_element/webform_test_element.info.yml b/web/modules/webform/tests/modules/webform_test_element/webform_test_element.info.yml index 7ea6526329..4f87a139c4 100644 --- a/web/modules/webform/tests/modules/webform_test_element/webform_test_element.info.yml +++ b/web/modules/webform/tests/modules/webform_test_element/webform_test_element.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_entity_reference.yml b/web/modules/webform/tests/modules/webform_test_entity_reference/config/install/views.view.webform_test_entity_reference_vs.yml similarity index 56% rename from web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_entity_reference.yml rename to web/modules/webform/tests/modules/webform_test_entity_reference/config/install/views.view.webform_test_entity_reference_vs.yml index 7a1f8f1a6c..670fafac68 100644 --- a/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_entity_reference.yml +++ b/web/modules/webform/tests/modules/webform_test_entity_reference/config/install/views.view.webform_test_entity_reference_vs.yml @@ -1,14 +1,16 @@ langcode: en status: true dependencies: - enforced: - module: - - webform_test_views + config: + - field.storage.node.field_image + - image.style.thumbnail + - node.type.article module: + - image - node - user -id: webform_test_entity_reference -label: 'Webform test entity reference' +id: webform_test_entity_reference_vs +label: 'Webform test entity reference views' module: views description: '' tag: '' @@ -80,6 +82,70 @@ display: hide_empty: false default_field_elements: true fields: + field_image: + id: field_image + table: node__field_image + field: field_image + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: image + settings: + image_style: thumbnail + image_link: '' + group_column: '' + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + plugin_id: field title: id: title table: node_field_data @@ -139,6 +205,46 @@ display: expose: operator: '' group: 1 + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: + article: article + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: type + plugin_id: bundle sorts: created: id: created @@ -169,10 +275,13 @@ display: - url.query_args - 'user.node_grants:view' - user.permissions - tags: { } - entity_reference: + tags: + - 'config:field.storage.node.field_image' + - env + - extensions + entity_reference_1: display_plugin: entity_reference - id: entity_reference + id: entity_reference_1 display_title: 'Entity Reference' position: 1 display_options: @@ -182,19 +291,27 @@ display: options: search_fields: title: title + field_image: '0' + pager: + type: some + options: + items_per_page: 50 + offset: 0 row: type: entity_reference options: default_field_elements: true - inline: - title: title - separator: '-' + inline: { } + separator: '' hide_empty: false + rendering_language: '***LANGUAGE_language_interface***' cache_metadata: max-age: -1 contexts: - - 'languages:language_content' - 'languages:language_interface' - 'user.node_grants:view' - user.permissions - tags: { } + tags: + - 'config:field.storage.node.field_image' + - env + - extensions diff --git a/web/modules/webform/tests/modules/webform_test_entity_reference/config/install/webform.webform.test_element_entity_reference_vs.yml b/web/modules/webform/tests/modules/webform_test_entity_reference/config/install/webform.webform.test_element_entity_reference_vs.yml new file mode 100644 index 0000000000..629f357909 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_entity_reference/config/install/webform.webform.test_element_entity_reference_vs.yml @@ -0,0 +1,229 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test_entity_reference_views +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_element_entity_reference_vs +title: 'Test: Element: Entity reference views' +description: 'Test entity reference views elements.' +category: 'Test: Element' +elements: | + entity_autocomplete: + '#type': entity_autocomplete + '#title': entity_autocomplete + '#multiple': true + '#target_type': node + '#selection_handler': views + '#selection_settings': + view: + view_name: webform_test_entity_reference_vs + display_name: entity_reference_1 + arguments: { } + entity_select: + '#type': webform_entity_select + '#title': webform_entity_select + '#multiple': true + '#target_type': node + '#selection_handler': views + '#selection_settings': + view: + view_name: webform_test_entity_reference_vs + display_name: entity_reference_1 + arguments: { } + entity_radios: + '#type': webform_entity_radios + '#title': webform_entity_radios + '#multiple': true + '#target_type': node + '#selection_handler': views + '#selection_settings': + view: + view_name: webform_test_entity_reference_vs + display_name: entity_reference_1 + arguments: { } + '#wrapper_attributes': + class: + - webform-entity-reference-options + entity_checkboxes: + '#type': webform_entity_checkboxes + '#title': entity_checkboxes + '#multiple': true + '#target_type': node + '#selection_handler': views + '#selection_settings': + view: + view_name: webform_test_entity_reference_vs + display_name: entity_reference_1 + arguments: { } + '#wrapper_attributes': + class: + - webform-entity-reference-options +css: "/* Autocomplete */\r\n\r\n.ui-autocomplete {\r\n display: flex;\r\n flex-wrap: wrap !important;\r\n align-self: flex-start !important;\r\n max-width: 540px !important;\r\n}\r\n\r\n.ui-autocomplete .ui-menu-item-wrapper {\r\n display: block !important;\r\n border: 1px solid #ccc !important;\r\n background-color: #eee !important;\r\n width: 100px !important;\r\n margin: 10px 0 0 10px !important;\r\n padding: 10px !important;\r\n}\r\n\r\n.ui-autocomplete .ui-menu-item-wrapper.ui-state-active {\r\n background-color: blue !important;\r\n}\r\n\r\n.ui-autocomplete .views-field views-field-field-image {\r\n display: block;\r\n margin: 0 0 5px 0;\r\n}\r\n\r\n/* Checkboex and radios */\r\n\r\n.webform-entity-reference-options .fieldset-wrapper > div {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-self: flex-start;\r\n}\r\n\r\n.webform-entity-reference-options .form-item {\r\n position: relative;\r\n}\r\n\r\n.webform-entity-reference-options .form-item input {\r\n position: absolute;\r\n top: 20px;\r\n left: 20px; \r\n}\r\n\r\n.webform-entity-reference-options .form-item label {\r\n display: block;\r\n border: 1px solid #ccc;\r\n background-color: #eee;\r\n width: 100px;\r\n margin: 0 10px 10px 0; \r\n padding: 10px; \r\n}\r\n\r\n.webform-entity-reference-options .form-item input:checked + label {\r\n display: block;\r\n border: 1px solid #ccc;\r\n background-color: #ffc;\r\n width: 100px;\r\n margin: 0 10px 10px 0; \r\n padding: 10px; \r\n}\r\n\r\n.webform-entity-reference-options label.option img {\r\n display: block;\r\n margin: 0 0 5px 0; \r\n}" +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: false + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 1 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: none + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: '' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + debug: + id: debug + label: Debug + handler_id: debug + status: true + conditions: { } + weight: 0 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test_entity_reference/webform_test_entity_reference_views.info.yml b/web/modules/webform/tests/modules/webform_test_entity_reference/webform_test_entity_reference_views.info.yml new file mode 100644 index 0000000000..b273138c30 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_entity_reference/webform_test_entity_reference_views.info.yml @@ -0,0 +1,16 @@ +name: 'Webform module entity reference tests' +type: module +description: 'Support module for Webform module entity reference testing.' +package: Testing +# core: 8.x +dependencies: + - 'drupal:node' + - 'drupal:user' + - 'drupal:views' + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_exporter/src/Plugin/WebformExporter/TestWebformExporter.php b/web/modules/webform/tests/modules/webform_test_exporter/src/Plugin/WebformExporter/TestWebformExporter.php new file mode 100644 index 0000000000..7d200f7a1a --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_exporter/src/Plugin/WebformExporter/TestWebformExporter.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\webform_test_exporter\Plugin\WebformExporter; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\webform\Plugin\WebformExporter\TableWebformExporter; +use Drupal\webform\WebformSubmissionInterface; + +/** + * Defines a test exporter. + * + * @WebformExporter( + * id = "test", + * label = @Translation("Test"), + * description = @Translation("Test exporter results as an HTML table in reverse column order."), + * ) + */ +class TestWebformExporter extends TableWebformExporter { + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return parent::defaultConfiguration() + [ + 'reverse' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $form['reverse'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Reverse the table's column order"), + '#return_value' => TRUE, + '#default_value' => $this->configuration['reverse'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function buildHeader() { + $header = parent::buildHeader(); + return ($this->configuration['reverse']) ? array_reverse($header) : $header; + } + + /** + * {@inheritdoc} + */ + protected function buildRecord(WebformSubmissionInterface $webform_submission) { + $record = parent::buildRecord($webform_submission); + return ($this->configuration['reverse']) ? array_reverse($record) : $record; + } + +} diff --git a/web/modules/webform/tests/modules/webform_test_exporter/webform_test_exporter.info.yml b/web/modules/webform/tests/modules/webform_test_exporter/webform_test_exporter.info.yml new file mode 100644 index 0000000000..173f2efbfc --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_exporter/webform_test_exporter.info.yml @@ -0,0 +1,13 @@ +name: 'Webform module exporter tests' +type: module +description: 'Support module for webform that provides exporter plugin tests.' +package: Testing +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_conditions.yml b/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_conditions.yml index 1fb22fdb9c..91a89ea93b 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_conditions.yml +++ b/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_conditions.yml @@ -8,8 +8,10 @@ dependencies: - webform_test_handler open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_conditions title: 'Test: Handler: Test conditions' description: 'Test handler conditions.' @@ -21,6 +23,7 @@ elements: | trigger_b: '#type': checkbox '#title': trigger_b + css: '' javascript: '' settings: @@ -29,6 +32,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -36,6 +40,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -51,22 +56,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -77,6 +97,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -95,9 +116,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -150,6 +173,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: test_a: id: test diff --git a/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_test.yml b/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_test.yml index 9e526e8522..499ff6a6ed 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_test.yml +++ b/web/modules/webform/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_test.yml @@ -8,8 +8,10 @@ dependencies: - webform_test_handler open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_test title: 'Test: Handler: Test invoke methods' description: 'Test invoking of webform handler methods.' @@ -19,6 +21,7 @@ elements: | '#type': textfield '#title': 'Empty element' '#description': 'Entering any value will throw an error' + css: '' javascript: '' settings: @@ -27,6 +30,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -34,6 +38,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -49,22 +54,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -75,6 +95,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -93,9 +114,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -148,6 +171,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: test: id: test @@ -158,3 +185,11 @@ handlers: weight: 1 settings: message: 'One two one two this is just a test' + debug: + id: debug + label: Debug + handler_id: debug + status: false + conditions: { } + weight: 2 + settings: { } diff --git a/web/modules/webform/tests/modules/webform_test_handler/config/schema/webform_test_handler.webform.schema.yml b/web/modules/webform/tests/modules/webform_test_handler/config/schema/webform_test_handler.webform.schema.yml index f383478238..81017d2dab 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/config/schema/webform_test_handler.webform.schema.yml +++ b/web/modules/webform/tests/modules/webform_test_handler/config/schema/webform_test_handler.webform.schema.yml @@ -1,13 +1,10 @@ -# Schema for the configuration files of the webform test handler module. - webform.handler.test: type: mapping - label: 'Test' + label: Test mapping: message: - label: 'Message' + label: Message type: string - webform.handler.test_entity_mapping: type: mapping label: 'Test entity mapping' @@ -16,8 +13,8 @@ webform.handler.test_entity_mapping: label: 'Entity type' type: string bundle: - label: 'Bundle' + label: Bundle type: string fields: type: ignore - label: 'Fields' + label: Fields diff --git a/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestEntityMappingWebformHandler.php b/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestEntityMappingWebformHandler.php index 089bc2d4b0..de5e365a94 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestEntityMappingWebformHandler.php +++ b/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestEntityMappingWebformHandler.php @@ -115,7 +115,6 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $form['container']['bundle'] = [ '#type' => 'select', '#title' => $this->t('Bundles'), - '#parents' => ['settings', 'bundle'], '#default_value' => $this->configuration['bundle'], '#options' => $bundle_options, '#ajax' => $ajax, @@ -147,12 +146,11 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#description_display' => 'before', '#default_value' => $this->configuration['fields'], '#required' => TRUE, - '#parents' => ['settings', 'fields'], '#source' => $element_options, '#destination' => $field_options, ]; - return $form; + return $this->setSettingsParents($form); } /** diff --git a/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestWebformHandler.php b/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestWebformHandler.php index 782445e293..3623a72e11 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestWebformHandler.php +++ b/web/modules/webform/tests/modules/webform_test_handler/src/Plugin/WebformHandler/TestWebformHandler.php @@ -94,7 +94,7 @@ public function submitForm(array &$form, FormStateInterface $form_state, Webform * {@inheritdoc} */ public function confirmForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) { - drupal_set_message($this->configuration['message'], 'status', TRUE); + $this->messenger()->addStatus($this->configuration['message'], TRUE); \Drupal::logger('webform.test_form')->notice($this->configuration['message']); $this->displayMessage(__FUNCTION__); } @@ -214,7 +214,7 @@ protected function displayMessage($method_name, $context1 = NULL) { '@method_name' => $method_name, '@context1' => $context1, ]; - drupal_set_message($this->t('Invoked @id: @class_name:@method_name @context1', $t_args), 'status', TRUE); + $this->messenger()->addStatus($this->t('Invoked @id: @class_name:@method_name @context1', $t_args), TRUE); \Drupal::logger('webform.test_form')->notice('Invoked: @class_name:@method_name @context1', $t_args); } } diff --git a/web/modules/webform/tests/modules/webform_test_handler/webform_test_handler.info.yml b/web/modules/webform/tests/modules/webform_test_handler/webform_test_handler.info.yml index b294720ae2..c76a9d3b20 100644 --- a/web/modules/webform/tests/modules/webform_test_handler/webform_test_handler.info.yml +++ b/web/modules/webform/tests/modules/webform_test_handler/webform_test_handler.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.info.yml b/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.info.yml new file mode 100644 index 0000000000..69b5cae8ff --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.info.yml @@ -0,0 +1,13 @@ +name: 'Webform module handler invoke alter tests' +type: module +description: 'Support module for webform that provides handler invoke alter tests.' +package: Testing +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.module b/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.module new file mode 100644 index 0000000000..db54daa4e3 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_handler_invoke_alter/webform_test_handler_invoke_alter.module @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Support module for webform that provides handler invoke alter tests. + */ + +use Drupal\webform\Plugin\WebformHandlerInterface; + +/** + * Implements hook_webform_handler_invoke_alter(). + */ +function webform_test_handler_invoke_alter_webform_handler_invoke_alter(WebformHandlerInterface $handler, $method_name, array $args) { + $t_args = [ + '@webform_id' => $handler->getWebform()->id(), + '@handler_id' => $handler->getHandlerId(), + '@method_name' => $method_name, + ]; + \Drupal::messenger()->addStatus(t('Invoking hook_webform_handler_invoke_alter() for "@webform_id:@handler_id::@method_name"', $t_args), TRUE); +} + +/** + * Implements hook_webform_handler_invoke_METHOD_NAME_alter(). + */ +function webform_test_handler_invoke_alter_webform_handler_invoke_pre_create_alter(WebformHandlerInterface $handler, array $args) { + $t_args = [ + '@webform_id' => $handler->getWebform()->id(), + '@handler_id' => $handler->getHandlerId(), + ]; + \Drupal::messenger()->addStatus(t('Invoking hook_webform_handler_invoke_pre_create_alter() for "@webform_id:@handler_id"', $t_args), TRUE); +} diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_get.yml b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_get.yml index 412f041673..8bf1befcbd 100644 --- a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_get.yml +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_get.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_handler_remote_post open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_remote_get title: 'Test: Handler: Remote HTTP GET' description: 'Test remote post handler using HTTP GET method.' @@ -35,6 +37,7 @@ elements: | '#title': 'Confirmation number' '#type': value '#value': '[webform:handler:remote_post:completed:confirmation_number]' + css: '' javascript: '' settings: @@ -43,6 +46,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -50,6 +54,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: false form_prepopulate_source_entity: false @@ -65,22 +70,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -91,6 +111,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -100,6 +121,7 @@ settings: confirmation_title: '' confirmation_message: | <p>Your confirmation number is [webform_submission:values:confirmation_number].</p> + confirmation_url: '' confirmation_attributes: { } confirmation_back: true @@ -110,9 +132,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -165,6 +189,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: remote_post: id: remote_post @@ -199,24 +227,31 @@ handlers: confirmation_number: confirmation_number custom_data: | custom_data: true + custom_options: | headers: custom_header: 'true' + debug: true completed_url: 'http://webform-test-handler-remote-post/completed' completed_custom_data: | custom_completed: true + updated_url: 'http://webform-test-handler-remote-post/updated' updated_custom_data: | custom_updated: true + deleted_url: 'http://webform-test-handler-remote-post/deleted' deleted_custom_data: | custom_deleted: true + draft_url: 'http://webform-test-handler-remote-post/draft' draft_custom_data: | custom_draft: true + converted_url: 'http://webform-test-handler-remote-post/converted' converted_custom_data: | custom_converted: true + message: '' - messages: { } + messages: { } diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml index c384595a75..35464c3f3a 100644 --- a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_handler_remote_post open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_remote_post title: 'Test: Handler: Remote post' description: 'Test remote post handler.' @@ -36,6 +38,7 @@ elements: | '#title': 'Confirmation number' '#type': value '#value': '[webform:handler:remote_post:completed:confirmation_number]' + css: '' javascript: '' settings: @@ -44,6 +47,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -51,6 +55,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: false form_prepopulate_source_entity: false @@ -66,22 +71,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -92,6 +112,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -101,6 +122,7 @@ settings: confirmation_title: '' confirmation_message: | <p>Your confirmation number is [webform_submission:values:confirmation_number].</p> + confirmation_url: '' confirmation_attributes: { } confirmation_back: true @@ -111,9 +133,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -166,6 +190,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: remote_post: id: remote_post @@ -200,25 +228,33 @@ handlers: confirmation_number: confirmation_number custom_data: | custom_data: true + custom_options: | headers: + 'Accept-Language': '[webform_submission:langcode]' custom_header: 'true' + debug: true completed_url: 'http://webform-test-handler-remote-post/completed' completed_custom_data: | custom_completed: true + updated_url: 'http://webform-test-handler-remote-post/updated' updated_custom_data: | custom_updated: true + deleted_url: 'http://webform-test-handler-remote-post/deleted' deleted_custom_data: | custom_deleted: true + draft_url: 'http://webform-test-handler-remote-post/draft' draft_custom_data: | custom_draft: true + converted_url: 'http://webform-test-handler-remote-post/converted' converted_custom_data: | custom_converted: true + message: '' messages: - code: 401 diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post_file.yml b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post_file.yml index c7c31b19a2..b94f477d6d 100644 --- a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post_file.yml +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post_file.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_handler_remote_post open: null close: null +weight: 0 uid: null template: false +archive: false id: test_handler_remote_post_file title: 'Test: Handler: Remote post file' description: 'Test remote post handler file.' @@ -18,6 +20,13 @@ elements: | '#type': managed_file '#required': true '#file_extensions': txt + files: + '#title': files + '#type': managed_file + '#required': true + '#multiple': true + '#file_extensions': txt + css: '' javascript: '' settings: @@ -26,6 +35,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -33,6 +43,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: true form_prepopulate: false form_prepopulate_source_entity: false @@ -48,22 +59,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -74,6 +100,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: all draft_multiple: false draft_auto_save: false @@ -83,6 +110,7 @@ settings: confirmation_title: '' confirmation_message: | <p>Your confirmation number is [webform_submission:values:confirmation_number].</p> + confirmation_url: '' confirmation_attributes: { } confirmation_back: true @@ -93,9 +121,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -148,6 +178,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: remote_post: id: remote_post @@ -194,4 +228,4 @@ handlers: converted_url: 'http://webform-test-handler-remote-post/converted' converted_custom_data: '' message: '' - messages: { } + messages: { } diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_put.yml b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_put.yml new file mode 100644 index 0000000000..cec9caef83 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_put.yml @@ -0,0 +1,257 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_test_handler_remote_post +open: null +close: null +weight: 0 +uid: null +template: false +archive: false +id: test_handler_remote_put +title: 'Test: Handler: Remote HTTP PUT' +description: 'Test remote post handler using HTTP PUT method.' +category: 'Test: Handler' +elements: | + response_type: + '#title': 'Response type' + '#type': select + '#options': + 200: '200 OK' + 500: '500 Internal Server Error' + 404: '404 Not Found' + '#default_value': 200 + first_name: + '#title': 'First name' + '#type': textfield + '#required': true + '#default_value': John + last_name: + '#title': 'Last name' + '#type': textfield + '#required': true + '#default_value': Smith + confirmation_number: + '#title': 'Confirmation number' + '#type': value + '#value': '[webform:handler:remote_post:completed:confirmation_number]' + +css: '' +javascript: '' +settings: + ajax: false + ajax_scroll_top: form + page: true + page_submit_path: '' + page_confirm_path: '' + form_title: source_entity_webform + form_submit_once: false + form_exception_message: '' + form_open_message: '' + form_close_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_remote_addr: true + form_convert_anonymous: true + form_prepopulate: false + form_prepopulate_source_entity: false + form_prepopulate_source_entity_required: false + form_prepopulate_source_entity_type: '' + form_reset: false + form_disable_autocomplete: false + form_novalidate: false + form_disable_inline_errors: false + form_required: false + form_unsaved: false + form_disable_back: false + form_submit_back: false + form_autofocus: false + form_details_toggle: false + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' + submission_label: '' + submission_log: false + submission_views: { } + submission_views_replace: { } + submission_user_columns: { } + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } + submission_exception_message: '' + submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' + autofill: false + autofill_message: '' + autofill_excluded_elements: { } + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_progress_link: false + wizard_start_label: '' + wizard_preview_link: false + wizard_confirmation: true + wizard_confirmation_label: '' + wizard_track: '' + preview: 0 + preview_label: '' + preview_title: '' + preview_message: '' + preview_attributes: { } + preview_excluded_elements: { } + preview_exclude_empty: true + preview_exclude_empty_checkbox: false + draft: all + draft_multiple: false + draft_auto_save: false + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: inline + confirmation_title: '' + confirmation_message: | + <p>Your confirmation number is [webform_submission:values:confirmation_number].</p> + + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + confirmation_exclude_query: false + confirmation_exclude_token: false + limit_total: null + limit_total_interval: null + limit_total_message: '' + limit_total_unique: false + limit_user: null + limit_user_interval: null + limit_user_message: '' + limit_user_unique: false + entity_limit_total: null + entity_limit_total_interval: null + entity_limit_user: null + entity_limit_user_interval: null + purge: none + purge_days: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + permissions: { } + view_any: + roles: { } + users: { } + permissions: { } + update_any: + roles: { } + users: { } + permissions: { } + delete_any: + roles: { } + users: { } + permissions: { } + purge_any: + roles: { } + users: { } + permissions: { } + view_own: + roles: { } + users: { } + permissions: { } + update_own: + roles: { } + users: { } + permissions: { } + delete_own: + roles: { } + users: { } + permissions: { } + administer: + roles: { } + users: { } + permissions: { } + test: + roles: { } + users: { } + permissions: { } + configuration: + roles: { } + users: { } + permissions: { } +handlers: + remote_post: + id: remote_post + label: 'Remote HTTP PUT' + handler_id: remote_post + status: true + conditions: { } + weight: 1 + settings: + method: PUT + type: '' + excluded_data: + serial: serial + sid: sid + uuid: uuid + token: token + uri: uri + created: created + completed: completed + changed: changed + in_draft: in_draft + current_page: current_page + remote_addr: remote_addr + uid: uid + langcode: langcode + webform_id: webform_id + entity_type: entity_type + entity_id: entity_id + sticky: sticky + locked: locked + notes: notes + confirmation_number: confirmation_number + custom_data: | + custom_data: true + + custom_options: | + headers: + custom_header: 'true' + + debug: true + completed_url: 'http://webform-test-handler-remote-post/completed' + completed_custom_data: | + custom_completed: true + + updated_url: 'http://webform-test-handler-remote-post/updated' + updated_custom_data: | + custom_updated: true + + deleted_url: 'http://webform-test-handler-remote-post/deleted' + deleted_custom_data: | + custom_deleted: true + + draft_url: 'http://webform-test-handler-remote-post/draft' + draft_custom_data: | + custom_draft: true + + converted_url: 'http://webform-test-handler-remote-post/converted' + converted_custom_data: | + custom_converted: true + + message: '' + messages: { } diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php b/web/modules/webform/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php index d54a7ad5d8..ace37b4ec8 100644 --- a/web/modules/webform/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php @@ -29,7 +29,7 @@ public function request($method, $uri = '', array $options = []) { } $response_type = (isset($params['response_type'])) ? $params['response_type'] : 200; - $operation = str_replace('http://webform-test-handler-remote-post/', '', $uri); + $operation = ltrim(parse_url($uri, PHP_URL_PATH), '/'); $random = new Random(); // Handle 404 errors. switch ($response_type) { @@ -42,6 +42,7 @@ public function request($method, $uri = '', array $options = []) { $status = 401; $headers = ['Content-Type' => ['application/json']]; $json = [ + 'method' => $method, 'status' => 'unauthorized', 'message' => (string) new FormattableMarkup('Unauthorized to process @type request.', ['@type' => $operation]), 'options' => $options, @@ -53,6 +54,7 @@ public function request($method, $uri = '', array $options = []) { $status = 500; $headers = ['Content-Type' => ['application/json']]; $json = [ + 'method' => $method, 'status' => 'fail', 'message' => (string) new FormattableMarkup('Failed to process @type request.', ['@type' => $operation]), 'options' => $options, @@ -65,6 +67,7 @@ public function request($method, $uri = '', array $options = []) { $status = 200; $headers = ['Content-Type' => ['application/json']]; $json = [ + 'method' => $method, 'status' => 'success', 'message' => (string) new FormattableMarkup('Processed @type request.', ['@type' => $operation]), 'options' => $options, diff --git a/web/modules/webform/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml b/web/modules/webform/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml index 6cfb6200e9..d811a4d727 100644 --- a/web/modules/webform/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml +++ b/web/modules/webform/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.info.yml b/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.info.yml new file mode 100644 index 0000000000..a0d294aa1c --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.info.yml @@ -0,0 +1,13 @@ +name: 'Webform module markup tests' +type: module +description: 'Support module for markup tests.' +package: Testing +# core: 8.x +dependencies: + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.module b/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.module new file mode 100644 index 0000000000..0838c16c5f --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_markup/webform_test_markup.module @@ -0,0 +1,17 @@ +<?php + +/** + * @file + * Support module for markup tests. + */ + +/** + * Prepares variables for Webform HTML Editor markup templates. + * + * @see webform.webform.test_element_markup.yml + */ +function webform_test_markup_preprocess_webform_html_editor_markup(array &$variables) { + if ((string) $variables['content']['#markup'] === '<p>Alter this markup.</p>') { + $variables['content']['#markup'] = '<p><em>Alter this markup.</em> <strong>This markup was altered.</strong></p>'; + } +} diff --git a/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.info.yml b/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.info.yml index 73a96b5d20..586aea832a 100644 --- a/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.info.yml +++ b/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.install b/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.install index 12d8b1f547..b58f82ca61 100644 --- a/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.install +++ b/web/modules/webform/tests/modules/webform_test_message_custom/webform_test_message_custom.install @@ -5,7 +5,6 @@ * Install, update and uninstall functions for the Webform Test Message Custom module. */ - /** * Implements hook_uninstall(). */ diff --git a/web/modules/webform/tests/modules/webform_test_options/config/install/webform.webform.test_options.yml b/web/modules/webform/tests/modules/webform_test_options/config/install/webform.webform.test_options.yml index cf482666c7..e5d27cade8 100644 --- a/web/modules/webform/tests/modules/webform_test_options/config/install/webform.webform.test_options.yml +++ b/web/modules/webform/tests/modules/webform_test_options/config/install/webform.webform.test_options.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_options open: null close: null +weight: 0 uid: null template: false +archive: false id: test_options title: 'Test: Options' description: 'Test webform options.' @@ -217,6 +219,7 @@ elements: | '#options': range '#min': a '#max': z + css: '' javascript: '' settings: @@ -225,6 +228,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -232,6 +236,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -247,22 +252,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -273,6 +293,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -291,9 +312,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -346,4 +369,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test_options/webform_test_options.info.yml b/web/modules/webform/tests/modules/webform_test_options/webform_test_options.info.yml index d8b817f111..c2f6198f55 100644 --- a/web/modules/webform/tests/modules/webform_test_options/webform_test_options.info.yml +++ b/web/modules/webform/tests/modules/webform_test_options/webform_test_options.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_options/webform_test_options.module b/web/modules/webform/tests/modules/webform_test_options/webform_test_options.module index abe403c170..e175f64102 100644 --- a/web/modules/webform/tests/modules/webform_test_options/webform_test_options.module +++ b/web/modules/webform/tests/modules/webform_test_options/webform_test_options.module @@ -20,7 +20,7 @@ function webform_test_options_webform_options_test_alter(array &$options, array * Implements hook_webform_options_alter(). */ function webform_test_options_webform_options_alter(array &$options, array &$element, $id) { - if ($id == 'custom') { + if ($id === 'custom') { $options = [ 'one' => t('One'), 'two' => t('Two'), diff --git a/web/modules/webform/tests/modules/webform_test_paragraphs/webform_test_paragraphs.info.yml b/web/modules/webform/tests/modules/webform_test_paragraphs/webform_test_paragraphs.info.yml index 6a6c4f9d3e..ba1df7bcad 100644 --- a/web/modules/webform/tests/modules/webform_test_paragraphs/webform_test_paragraphs.info.yml +++ b/web/modules/webform/tests/modules/webform_test_paragraphs/webform_test_paragraphs.info.yml @@ -7,8 +7,8 @@ dependencies: - 'paragraphs:paragraphs' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform.yml b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform.yml new file mode 100644 index 0000000000..280a19d359 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - serialization + - user + - webform +id: entity.webform +plugin_id: 'entity:webform' +granularity: resource +configuration: + methods: + - GET + formats: + - json + - hal_json + - xml + authentication: + - cookie diff --git a/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_options.yml b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_options.yml new file mode 100644 index 0000000000..2899ef3bb7 --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_options.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - serialization + - user + - webform +id: entity.webform_options +plugin_id: 'entity:webform_options' +granularity: resource +configuration: + methods: + - GET + formats: + - json + - hal_json + - xml + authentication: + - cookie diff --git a/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_submission.yml b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_submission.yml new file mode 100644 index 0000000000..69b7ccd7ad --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_rest/config/install/rest.resource.entity.webform_submission.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - serialization + - user + - webform +id: entity.webform_submission +plugin_id: 'entity:webform_submission' +granularity: resource +configuration: + methods: + - GET + formats: + - json + - hal_json + - xml + authentication: + - cookie diff --git a/web/modules/webform/tests/modules/webform_test_rest/webform_test_rest.info.yml b/web/modules/webform/tests/modules/webform_test_rest/webform_test_rest.info.yml new file mode 100644 index 0000000000..4bbc06833e --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_rest/webform_test_rest.info.yml @@ -0,0 +1,16 @@ +name: 'Webform module REST API tests' +type: module +description: 'Support module for Webform module REST API testing.' +package: Testing +# core: 8.x +dependencies: + - 'drupal:hal' + - 'drupal:rest' + - 'drupal:serialization' + - 'webform:webform' + +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' +core: '8.x' +project: 'webform' +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_submissions/config/install/webform.webform.test_submissions.yml b/web/modules/webform/tests/modules/webform_test_submissions/config/install/webform.webform.test_submissions.yml index d87d966879..1a6a514b3f 100644 --- a/web/modules/webform/tests/modules/webform_test_submissions/config/install/webform.webform.test_submissions.yml +++ b/web/modules/webform/tests/modules/webform_test_submissions/config/install/webform.webform.test_submissions.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_submissions open: null close: null +weight: 0 uid: null template: false +archive: false id: test_submissions title: 'Test: Submissions' description: 'Test webform submissions.' @@ -56,6 +58,7 @@ elements: | address: '#type': webform_address '#title': Address + css: '' javascript: '' settings: @@ -64,6 +67,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -71,6 +75,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -86,22 +91,37 @@ settings: form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -112,6 +132,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -130,9 +151,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -185,4 +208,8 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: { } diff --git a/web/modules/webform/tests/modules/webform_test_submissions/webform_test_submissions.info.yml b/web/modules/webform/tests/modules/webform_test_submissions/webform_test_submissions.info.yml index caa0728e6d..510f659873 100644 --- a/web/modules/webform/tests/modules/webform_test_submissions/webform_test_submissions.info.yml +++ b/web/modules/webform/tests/modules/webform_test_submissions/webform_test_submissions.info.yml @@ -7,8 +7,8 @@ dependencies: - 'drupal:node' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.info.yml b/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.info.yml index bf30bfa3c1..0d3f3603e0 100644 --- a/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.info.yml +++ b/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.webform.inc b/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.webform.inc index 4be8912c1d..47eb1a3b0a 100644 --- a/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.webform.inc +++ b/web/modules/webform/tests/modules/webform_test_third_party_settings/webform_test_third_party_settings.webform.inc @@ -66,6 +66,6 @@ function webform_test_third_party_settings_webform_submission_form_alter(&$form, // If a message is set, display it. if ($message) { - drupal_set_message($message); + \Drupal::messenger()->addStatus($message); } } diff --git a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.settings.yml b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.settings.yml index b195b33576..d05e881fd3 100644 --- a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.settings.yml +++ b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.settings.yml @@ -1,7 +1,7 @@ settings: default_submit_button_label: Enviar default_preview_next_button_label: 'Vista previa' - default_form_close_message: 'Lo siento ... Esta forma está cerrado a nuevas presentaciones.' + default_form_close_message: 'Lo siento … Esta forma está cerrado a nuevas presentaciones.' default_form_exception_message: 'No se puede mostrar este formulario. Por favor contacte al administrador del sitio.' default_preview_prev_button_label: '< Anterior' default_preview_message: 'Por favor revise su presentación. Su presentación no está completo hasta que se pulsa el botón "Enviar"!' @@ -16,10 +16,11 @@ mail: default_body: | Enviada [webform_submission:created] Presentado por:[webform_submission:user] - + Los valores presentados son: - + [webform_submission:values] + test: types: | checkbox: @@ -73,13 +74,14 @@ test: - 'random@random.com' webform_email_multiple: - 'example@example.com, test@test.com, random@random.com' - webform_location: + webform_location_geocomplete: - value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA' - value: 'London SW1A 1AA, United Kingdom' - value: 'Moscow, Russia, 10307' webform_time: - '09:00' - '17:00' + names: | first_name: - 'John' diff --git a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform.test_translation.yml b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform.test_translation.yml index e9fde213a6..73f75dbe14 100644 --- a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform.test_translation.yml +++ b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform.test_translation.yml @@ -11,7 +11,7 @@ elements: | 4: 'Las cuatro' 5: Cinco 6: Seis - '#other__option_label': 'Número personalizado...' + '#other__option_label': 'Número personalizado…' details: '#title': Detalles markup: @@ -26,6 +26,14 @@ elements: | age: '#title': Edad '#field_suffix': 'años. antiguo' + address: + '#title': 'Dirección' + '#address__title': 'Dirección' + '#address_2__title': 'Dirección 2' + '#city__title': 'Ciudad / Pueblo' + '#state_province__title': 'Estado / Provincia' + '#postal_code__title': 'ZIP / Código Postal' + '#country__title': 'Acciones de país' actions: '#type': webform_actions '#title': 'Submit button(s)' @@ -35,6 +43,7 @@ settings: handlers: email_confirmation: label: ' Confirmación de correo electrónico' +weight: 0 dependencies: enforced: module: diff --git a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform_options.test_translation.yml b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform_options.test_translation.yml index e3fa6e5f7a..28ac3d8f7c 100644 --- a/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform_options.test_translation.yml +++ b/web/modules/webform/tests/modules/webform_test_translation/config/install/language/es/webform.webform_options.test_translation.yml @@ -3,6 +3,7 @@ options: | 1: Uno 2: Dos 3: Tres + dependencies: enforced: module: diff --git a/web/modules/webform/tests/modules/webform_test_translation/config/install/webform.webform.test_translation.yml b/web/modules/webform/tests/modules/webform_test_translation/config/install/webform.webform.test_translation.yml index 4e5acb878c..437d5f98ab 100644 --- a/web/modules/webform/tests/modules/webform_test_translation/config/install/webform.webform.test_translation.yml +++ b/web/modules/webform/tests/modules/webform_test_translation/config/install/webform.webform.test_translation.yml @@ -6,8 +6,10 @@ dependencies: - webform_test_translation open: null close: null +weight: 0 uid: null template: false +archive: false id: test_translation title: 'Test: Translations' description: 'Test translating a webform.' @@ -27,7 +29,7 @@ elements: | 4: Four 5: Five 6: Six - '#other__option_label': 'Custom number...' + '#other__option_label': 'Custom number…' details: '#type': details '#title': Details @@ -45,14 +47,22 @@ elements: | '#title': 'Last name' age: '#type': number - '#title': 'Age' + '#title': Age '#field_suffix': ' yrs. old' '#min': 1 '#max': 125 + address: + '#type': webform_address + '#title': Address + token: + '#type': webform_computed_token + '#title': 'Computed (token)' + '#value': 'Site name: [site:name]' actions: '#type': webform_actions '#title': 'Submit button(s)' '#submit__label': 'Send message' + css: '' javascript: '' settings: @@ -61,6 +71,7 @@ settings: page: true page_submit_path: '' page_confirm_path: '' + form_title: source_entity_webform form_submit_once: false form_exception_message: '' form_open_message: '' @@ -68,6 +79,7 @@ settings: form_previous_submissions: true form_confidential: false form_confidential_message: '' + form_remote_addr: true form_convert_anonymous: false form_prepopulate: false form_prepopulate_source_entity: false @@ -76,28 +88,44 @@ settings: form_reset: false form_disable_autocomplete: false form_novalidate: false + form_disable_inline_errors: false form_required: false form_unsaved: false form_disable_back: false form_submit_back: false form_autofocus: false form_details_toggle: false - form_login: false - form_login_message: '' + form_access_denied: default + form_access_denied_title: '' + form_access_denied_message: '' + form_access_denied_attributes: { } + form_file_limit: '' submission_label: '' submission_log: false + submission_views: { } + submission_views_replace: { } submission_user_columns: { } - submission_login: false - submission_login_message: '' + submission_user_duplicate: false + submission_access_denied: default + submission_access_denied_title: '' + submission_access_denied_message: '' + submission_access_denied_attributes: { } submission_exception_message: '' submission_locked_message: '' + submission_excluded_elements: { } + submission_exclude_empty: false + submission_exclude_empty_checkbox: false + previous_submission_message: '' + previous_submissions_message: '' autofill: false autofill_message: '' autofill_excluded_elements: { } wizard_progress_bar: true wizard_progress_pages: false wizard_progress_percentage: false + wizard_progress_link: false wizard_start_label: '' + wizard_preview_link: false wizard_confirmation: true wizard_confirmation_label: '' wizard_track: '' @@ -108,6 +136,7 @@ settings: preview_attributes: { } preview_excluded_elements: { } preview_exclude_empty: true + preview_exclude_empty_checkbox: false draft: none draft_multiple: false draft_auto_save: false @@ -126,9 +155,11 @@ settings: limit_total: null limit_total_interval: null limit_total_message: '' + limit_total_unique: false limit_user: null limit_user_interval: null limit_user_message: '' + limit_user_unique: false entity_limit_total: null entity_limit_total_interval: null entity_limit_user: null @@ -181,6 +212,10 @@ access: roles: { } users: { } permissions: { } + configuration: + roles: { } + users: { } + permissions: { } handlers: email_confirmation: id: email @@ -192,23 +227,25 @@ handlers: settings: states: - completed - to_mail: default + to_mail: _default to_options: { } cc_mail: '' cc_options: { } bcc_mail: '' bcc_options: { } - from_mail: default + from_mail: _default from_options: { } - from_name: default - subject: default - body: default + from_name: _default + subject: _default + body: _default excluded_elements: { } ignore_access: false exclude_empty: true + exclude_empty_checkbox: false html: true attachments: false twig: false + theme_name: '' debug: false reply_to: '' return_path: '' diff --git a/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.info.yml b/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.info.yml index 9c2289c82c..96a4139824 100644 --- a/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.info.yml +++ b/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.info.yml @@ -9,8 +9,8 @@ dependencies: - 'drupal:locale' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.install b/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.install new file mode 100644 index 0000000000..a1ba31c7bf --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_translation/webform_test_translation.install @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the Webform test translation module. + * + * drush php-eval 'module_load_include('install', 'webform_test_translation');webform_test_translation_install()'; drush cr; + */ + +use Drupal\Core\Serialization\Yaml; + +/** + * Implements hook_install(). + */ +function webform_test_translation_install() { + // Skip simpletest runner which has no issue importing + // translated configuration. + if (isset($GLOBALS['conf']) && array_key_exists('simpletest.settings', $GLOBALS['conf'])) { + return; + } + + _webform_test_translation_install_config('config'); + _webform_test_translation_install_config('config_snapshot'); +} + +/** + * Fixes Issue #2845437: Process translation config files for custom modules. + * + * @param string $table_name + * Config table name. + */ +function _webform_test_translation_install_config($table_name) { + if (!\Drupal::database()->schema()->tableExists($table_name)) { + return; + } + + $query = \Drupal::database()->select($table_name, 'c') + ->fields('c', ['name', 'collection', 'data']) + ->condition('collection', 'language.es'); + $result = $query->execute(); + while ($record = $result->fetchAssoc()) { + $data = unserialize($record['data']); + + $filename = drupal_get_path('module', 'webform_test_translation') . '/config/install/language/es/' . $record['name'] . '.yml'; + if (!file_exists($filename)) { + continue; + } + + $translated_data = Yaml::decode(file_get_contents($filename)); + foreach ($translated_data as $key => $value) { + $data[$key] = $value; + } + + \Drupal::database()->update($table_name) + ->fields(['data' => serialize($data)]) + ->condition('collection', $record['collection']) + ->condition('name', $record['name']) + ->execute(); + } +} diff --git a/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/es/webform.settings.yml b/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/es/webform.settings.yml index 2b9f490db6..7d1ad30ce3 100644 --- a/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/es/webform.settings.yml +++ b/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/es/webform.settings.yml @@ -1,7 +1,7 @@ settings: default_form_submit_label: Enviar default_preview_next_button_label: 'Vista previa' - default_form_close_message: 'Lo siento... Este formulario está cerrado a nuevas presentaciones.' + default_form_close_message: 'Lo siento… Este formulario está cerrado a nuevas presentaciones.' default_form_exception_message: 'No se puede mostrar este formulario Web. Póngase en contacto con el administrador del sitio.' default_preview_prev_button_label: '< Anterior' default_preview_message: 'Por favor revise su presentación. Su solicitud no está completa hasta que usted oprima el botón "Enviar"!' diff --git a/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/ru/webform.settings.yml b/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/ru/webform.settings.yml index 85f3bc298d..1e0fdaf63d 100644 --- a/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/ru/webform.settings.yml +++ b/web/modules/webform/tests/modules/webform_test_translation_lingotek/config/install/language/ru/webform.settings.yml @@ -2,7 +2,7 @@ settings: default_form_submit_label: Отправить default_preview_next_button_label: 'Предварительный просмотр' default_form_open_message: 'Эта форма еще не был открыт для представлений.' - default_form_close_message: 'Извините... Эта форма закрыта для новых представлений.' + default_form_close_message: 'Извините… Эта форма закрыта для новых представлений.' default_form_exception_message: 'Не удается отобразить эту веб-форму. Пожалуйста, свяжитесь с администратором сайта.' default_form_confidential_message: 'Эта форма является конфиденциальной. Вы должны <a href="[site:login-url]/logout?destination=[current-page:url:relative]">выйти из</a> представить его.' default_wizard_prev_button_label: '< Предыдущая страница' diff --git a/web/modules/webform/tests/modules/webform_test_translation_lingotek/webform_test_translation_lingotek.info.yml b/web/modules/webform/tests/modules/webform_test_translation_lingotek/webform_test_translation_lingotek.info.yml index de1c92ea2e..e20e978784 100644 --- a/web/modules/webform/tests/modules/webform_test_translation_lingotek/webform_test_translation_lingotek.info.yml +++ b/web/modules/webform/tests/modules/webform_test_translation_lingotek/webform_test_translation_lingotek.info.yml @@ -10,8 +10,8 @@ dependencies: - 'lingotek:lingotek' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_validate/webform_test_validate.info.yml b/web/modules/webform/tests/modules/webform_test_validate/webform_test_validate.info.yml index 09e2b94788..ef28015ba4 100644 --- a/web/modules/webform/tests/modules/webform_test_validate/webform_test_validate.info.yml +++ b/web/modules/webform/tests/modules/webform_test_validate/webform_test_validate.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_access.yml b/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_access.yml new file mode 100644 index 0000000000..5cbf535aea --- /dev/null +++ b/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_access.yml @@ -0,0 +1,298 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform_test_views + module: + - user + - webform +id: webform_test_views_access +label: 'Webform test: Views access' +module: views +description: 'Test webform submission access.' +tag: '' +base_table: webform_submission +base_field: sid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access webform overview' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + tags: + previous: ‹‹ + next: ›› + first: '« First' + last: 'Last »' + expose: + items_per_page: true + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50, 100, 200' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + sid: sid + webform_id: webform_id + info: + sid: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + webform_id: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: sid + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + sid: + id: sid + table: webform_submission + field: sid + relationship: none + group_type: group + admin_label: '' + label: '#' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: sid + plugin_id: field + webform_id: + id: webform_id + table: webform_submission + field: webform_id + relationship: none + group_type: group + admin_label: '' + label: Webform + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: true + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: webform_submission + entity_field: webform_id + plugin_id: field + filters: { } + sorts: { } + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No submissions available.' + plugin_id: text_custom + relationships: { } + arguments: { } + display_extenders: { } + filter_groups: + operator: AND + groups: { } + title: 'Views bulk form' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - user + - user.permissions + tags: { } + page: + display_plugin: page + id: page + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: admin/structure/webform/test/views_access + menu: + type: normal + title: 'Views access' + description: 'Test webform submission access.' + expanded: false + parent: webform_test.index + weight: 0 + context: '0' + menu_name: admin + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - user + - user.permissions + tags: { } diff --git a/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_bulk_form.yml b/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_bulk_form.yml index 358ce902a7..994c565724 100644 --- a/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_bulk_form.yml +++ b/web/modules/webform/tests/modules/webform_test_views/config/install/views.view.webform_test_views_bulk_form.yml @@ -943,7 +943,35 @@ display: content: 'No submissions available.' plugin_id: text_custom relationships: { } - arguments: { } + arguments: + webform_id: + id: webform_id + table: webform_submission + field: webform_id + entity_type: webform_submission + entity_field: webform_id + plugin_id: string + entity_type: + id: entity_type + table: webform_submission + field: entity_type + entity_type: webform_submission + entity_field: entity_type + plugin_id: string + entity_id: + id: entity_id + table: webform_submission + field: entity_id + entity_type: webform_submission + entity_field: entity_id + plugin_id: string + uid: + id: uid + table: webform_submission + field: uid + entity_type: webform_submission + entity_field: uid + plugin_id: numeric display_extenders: { } filter_groups: operator: AND @@ -960,6 +988,23 @@ display: - user - user.permissions tags: { } + embed: + display_plugin: embed + id: embed + display_title: Embed + position: 2 + display_options: + display_extenders: { } + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - user.permissions + tags: { } page: display_plugin: page id: page diff --git a/web/modules/webform/tests/modules/webform_test_views/webform_test_views.info.yml b/web/modules/webform/tests/modules/webform_test_views/webform_test_views.info.yml index 83dd47c588..06cd56b330 100644 --- a/web/modules/webform/tests/modules/webform_test_views/webform_test_views.info.yml +++ b/web/modules/webform/tests/modules/webform_test_views/webform_test_views.info.yml @@ -9,8 +9,8 @@ dependencies: - 'drupal:views' - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/modules/webform_test_wizard_custom/webform_test_wizard_custom.info.yml b/web/modules/webform/tests/modules/webform_test_wizard_custom/webform_test_wizard_custom.info.yml index 9cb58fb810..8af24c8e3b 100644 --- a/web/modules/webform/tests/modules/webform_test_wizard_custom/webform_test_wizard_custom.info.yml +++ b/web/modules/webform/tests/modules/webform_test_wizard_custom/webform_test_wizard_custom.info.yml @@ -6,8 +6,8 @@ package: Testing dependencies: - 'webform:webform' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/tests/src/Functional/WebformAssertLegacyTrait.php b/web/modules/webform/tests/src/Functional/WebformAssertLegacyTrait.php new file mode 100644 index 0000000000..f13a611985 --- /dev/null +++ b/web/modules/webform/tests/src/Functional/WebformAssertLegacyTrait.php @@ -0,0 +1,768 @@ +<?php + +namespace Drupal\Tests\webform\Functional; + +use Behat\Mink\Element\NodeElement; +use Behat\Mink\Exception\ExpectationException; +use Behat\Mink\Selector\Xpath\Escaper; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Utility\Xss; + +/** + * Provides convenience methods for assertions in browser tests. + * + * Copies of legacy traits without deprecated warnings. + * + * @see \Drupal\KernelTests\AssertLegacyTrait + * @see \Drupal\FunctionalTests\AssertLegacyTrait + * @see http://blog.fclement.info/convert-simpletest-to-phpunit + * @see https://www.drupal.org/node/2735005 + */ +trait WebformAssertLegacyTrait { + + /** + * @see \Drupal\simpletest\TestBase::assert() + */ + protected function assert($actual, $message = '') { + parent::assertTrue((bool) $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::assertTrue() + */ + public static function assertTrue($actual, $message = '') { + if (is_bool($actual)) { + parent::assertTrue($actual, $message); + } + else { + parent::assertNotEmpty($actual, $message); + } + } + + /** + * @see \Drupal\simpletest\TestBase::assertFalse() + */ + public static function assertFalse($actual, $message = '') { + if (is_bool($actual)) { + parent::assertFalse($actual, $message); + } + else { + parent::assertEmpty($actual, $message); + } + } + + /** + * @see \Drupal\simpletest\TestBase::assertEqual() + */ + protected function assertEqual($actual, $expected, $message = '') { + $this->assertEquals($expected, $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::assertNotEqual() + */ + protected function assertNotEqual($actual, $expected, $message = '') { + $this->assertNotEquals($expected, $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::assertIdentical() + */ + protected function assertIdentical($actual, $expected, $message = '') { + $this->assertSame($expected, $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::assertNotIdentical() + */ + protected function assertNotIdentical($actual, $expected, $message = '') { + $this->assertNotSame($expected, $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::assertIdenticalObject() + */ + protected function assertIdenticalObject($actual, $expected, $message = '') { + // Note: ::assertSame checks whether its the same object. ::assertEquals + // though compares + $this->assertEquals($expected, $actual, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::pass() + */ + protected function pass($message) { + $this->assertTrue(TRUE, $message); + } + + /** + * @see \Drupal\simpletest\TestBase::verbose() + */ + protected function verbose($message) { + if (in_array('--debug', $_SERVER['argv'], TRUE)) { + // Write directly to STDOUT to not produce unexpected test output. + // The STDOUT stream does not obey output buffering. + fwrite(STDOUT, $message . "\n"); + } + } + + /** + * Asserts that the element with the given CSS selector is present. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + */ + protected function assertElementPresent($css_selector) { + $this->assertSession()->elementExists('css', $css_selector); + } + + /** + * Asserts that the element with the given CSS selector is not present. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + */ + protected function assertElementNotPresent($css_selector) { + $this->assertSession()->elementNotExists('css', $css_selector); + } + + /** + * Passes if the page (with HTML stripped) contains the text. + * + * Note that stripping HTML tags also removes their attributes, such as + * the values of text fields. + * + * @param string $text + * Plain text to look for. + */ + protected function assertText($text) { + // Cast MarkupInterface to string. + $text = (string) $text; + + $content_type = $this->getSession()->getResponseHeader('Content-type'); + // In case of a Non-HTML response (example: XML) check the original + // response. + if (strpos($content_type, 'html') === FALSE) { + $this->assertSession()->responseContains($text); + } + else { + $this->assertTextHelper($text, FALSE); + } + } + + /** + * Passes if the page (with HTML stripped) does not contains the text. + * + * Note that stripping HTML tags also removes their attributes, such as + * the values of text fields. + * + * @param string $text + * Plain text to look for. + */ + protected function assertNoText($text) { + // Cast MarkupInterface to string. + $text = (string) $text; + + $content_type = $this->getSession()->getResponseHeader('Content-type'); + // In case of a Non-HTML response (example: XML) check the original + // response. + if (strpos($content_type, 'html') === FALSE) { + $this->assertSession()->responseNotContains($text); + } + else { + $this->assertTextHelper($text); + } + } + + /** + * Helper for assertText and assertNoText. + * + * @param string $text + * Plain text to look for. + * @param bool $not_exists + * (optional) TRUE if this text should not exist, FALSE if it should. + * Defaults to TRUE. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertTextHelper($text, $not_exists = TRUE) { + $args = ['@text' => $text]; + $message = $not_exists ? new FormattableMarkup('"@text" not found', $args) : new FormattableMarkup('"@text" found', $args); + + $raw_content = $this->getSession()->getPage()->getContent(); + // Trying to simulate what the user sees, given that it removes all text + // inside the head tags, removes inline Javascript, fix all HTML entities, + // removes dangerous protocols and filtering out all HTML tags, as they are + // not visible in a normal browser. + $raw_content = preg_replace('@<head>(.+?)</head>@si', '', $raw_content); + $page_text = Xss::filter($raw_content, []); + + $actual = $not_exists == (strpos($page_text, (string) $text) === FALSE); + $this->assertTrue($actual, $message); + + return $actual; + } + + /** + * Passes if the text is found ONLY ONCE on the text version of the page. + * + * The text version is the equivalent of what a user would see when viewing + * through a web browser. In other words the HTML has been filtered out of + * the contents. + * + * @param string|\Drupal\Component\Render\MarkupInterface $text + * Plain text to look for. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). If left blank, a default message will be displayed. + */ + protected function assertUniqueText($text, $message = NULL) { + // Cast MarkupInterface objects to string. + $text = (string) $text; + + $message = $message ?: "'$text' found only once on the page"; + $page_text = $this->getSession()->getPage()->getText(); + $nr_found = substr_count($page_text, $text); + $this->assertSame(1, $nr_found, $message); + } + + /** + * Passes if the text is found MORE THAN ONCE on the text version of the page. + * + * The text version is the equivalent of what a user would see when viewing + * through a web browser. In other words the HTML has been filtered out of + * the contents. + * + * @param string|\Drupal\Component\Render\MarkupInterface $text + * Plain text to look for. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). If left blank, a default message will be displayed. + */ + protected function assertNoUniqueText($text, $message = '') { + // Cast MarkupInterface objects to string. + $text = (string) $text; + + $message = $message ?: "'$text' found more than once on the page"; + $page_text = $this->getSession()->getPage()->getText(); + $nr_found = substr_count($page_text, $text); + $this->assertGreaterThan(1, $nr_found, $message); + } + + /** + * Asserts the page responds with the specified response code. + * + * @param int $code + * Response code. For example 200 is a successful page request. For a list + * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. + */ + protected function assertResponse($code) { + $this->assertSession()->statusCodeEquals($code); + } + + /** + * Asserts that a field exists with the given name and value. + * + * @param string $name + * Name of field to assert. + * @param string $value + * (optional) Value of the field to assert. You may pass in NULL (default) + * to skip checking the actual value, while still checking that the field + * exists. + */ + protected function assertFieldByName($name, $value = NULL) { + $this->assertFieldByXPath($this->constructFieldXpath('name', $name), $value); + } + + /** + * Asserts that a field does not exist with the given name and value. + * + * @param string $name + * Name of field to assert. + * @param string $value + * (optional) Value for the field, to assert that the field's value on the + * page does not match it. You may pass in NULL to skip checking the + * value, while still checking that the field does not exist. However, the + * default value ('') asserts that the field value is not an empty string. + */ + protected function assertNoFieldByName($name, $value = '') { + $this->assertNoFieldByXPath($this->constructFieldXpath('name', $name), $value); + } + + /** + * Asserts that a field exists with the given ID and value. + * + * @param string $id + * ID of field to assert. + * @param string|\Drupal\Component\Render\MarkupInterface $value + * (optional) Value for the field to assert. You may pass in NULL to skip + * checking the value, while still checking that the field exists. + * However, the default value ('') asserts that the field value is an empty + * string. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + */ + protected function assertFieldById($id, $value = '') { + $this->assertFieldByXPath($this->constructFieldXpath('id', $id), $value); + } + + /** + * Asserts that a field exists with the given name or ID. + * + * @param string $field + * Name or ID of field to assert. + */ + protected function assertField($field) { + $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field)); + } + + /** + * Asserts that a field does NOT exist with the given name or ID. + * + * @param string $field + * Name or ID of field to assert. + */ + protected function assertNoField($field) { + $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field)); + } + + /** + * Passes if the raw text IS found on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + protected function assertRaw($raw) { + $this->assertSession()->responseContains($raw); + } + + /** + * Passes if the raw text IS not found on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + protected function assertNoRaw($raw) { + $this->assertSession()->responseNotContains($raw); + } + + /** + * Pass if the page title is the given string. + * + * @param string $expected_title + * The string the page title should be. + */ + protected function assertTitle($expected_title) { + // Cast MarkupInterface to string. + $expected_title = (string) $expected_title; + return $this->assertSession()->titleEquals($expected_title); + } + + /** + * Passes if a link with the specified label is found. + * + * An optional link index may be passed. + * + * @param string|\Drupal\Component\Render\MarkupInterface $label + * Text between the anchor tags. + * @param int $index + * Link position counting from zero. + */ + protected function assertLink($label, $index = 0) { + return $this->assertSession()->linkExists($label, $index); + } + + /** + * Passes if a link with the specified label is not found. + * + * @param string|\Drupal\Component\Render\MarkupInterface $label + * Text between the anchor tags. + */ + protected function assertNoLink($label) { + return $this->assertSession()->linkNotExists($label); + } + + /** + * Passes if a link containing a given href (part) is found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param int $index + * Link position counting from zero. + */ + protected function assertLinkByHref($href, $index = 0) { + $this->assertSession()->linkByHrefExists($href, $index); + } + + /** + * Passes if a link containing a given href (part) is not found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + */ + protected function assertNoLinkByHref($href) { + $this->assertSession()->linkByHrefNotExists($href); + } + + /** + * Asserts that a field does not exist with the given ID and value. + * + * @param string $id + * ID of field to assert. + * @param string $value + * (optional) Value for the field, to assert that the field's value on the + * page doesn't match it. You may pass in NULL to skip checking the value, + * while still checking that the field doesn't exist. However, the default + * value ('') asserts that the field value is not an empty string. + * + * @throws \Behat\Mink\Exception\ExpectationException + */ + protected function assertNoFieldById($id, $value = '') { + $this->assertNoFieldByXPath($this->constructFieldXpath('id', $id), $value); + } + + /** + * Passes if the internal browser's URL matches the given path. + * + * @param \Drupal\Core\Url|string $path + * The expected system path or URL. + */ + protected function assertUrl($path) { + $this->assertSession()->addressEquals($path); + } + + /** + * Asserts that a select option in the current page exists. + * + * @param string $id + * ID of select field to assert. + * @param string $option + * Option to assert. + */ + protected function assertOption($id, $option) { + return $this->assertSession()->optionExists($id, $option); + } + + /** + * Asserts that a select option with the visible text exists. + * + * @param string $id + * The ID of the select field to assert. + * @param string $text + * The text for the option tag to assert. + */ + protected function assertOptionByText($id, $text) { + return $this->assertSession()->optionExists($id, $text); + } + + /** + * Asserts that a select option does NOT exist in the current page. + * + * @param string $id + * ID of select field to assert. + * @param string $option + * Option to assert. + */ + protected function assertNoOption($id, $option) { + return $this->assertSession()->optionNotExists($id, $option); + } + + /** + * Asserts that a select option in the current page is checked. + * + * @param string $id + * ID of select field to assert. + * @param string $option + * Option to assert. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). If left blank, a default message will be displayed. + */ + protected function assertOptionSelected($id, $option, $message = NULL) { + $option_field = $this->assertSession()->optionExists($id, $option); + $message = $message ?: "Option $option for field $id is selected."; + $this->assertTrue($option_field->hasAttribute('selected'), $message); + } + + /** + * Asserts that a checkbox field in the current page is checked. + * + * @param string $id + * ID of field to assert. + */ + protected function assertFieldChecked($id) { + $this->assertSession()->checkboxChecked($id); + } + + /** + * Asserts that a checkbox field in the current page is not checked. + * + * @param string $id + * ID of field to assert. + */ + protected function assertNoFieldChecked($id) { + $this->assertSession()->checkboxNotChecked($id); + } + + /** + * Asserts that a field exists in the current page by the given XPath. + * + * @param string $xpath + * XPath used to find the field. + * @param string $value + * (optional) Value of the field to assert. You may pass in NULL (default) + * to skip checking the actual value, while still checking that the field + * exists. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + */ + protected function assertFieldByXPath($xpath, $value = NULL, $message = '') { + $fields = $this->xpath($xpath); + + $this->assertFieldsByValue($fields, $value, $message); + } + + /** + * Asserts that a field does not exist or its value does not match, by XPath. + * + * @param string $xpath + * XPath used to find the field. + * @param string $value + * (optional) Value of the field, to assert that the field's value on the + * page does not match it. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + * + * @throws \Behat\Mink\Exception\ExpectationException + */ + protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '') { + $fields = $this->xpath($xpath); + + if (!empty($fields)) { + if (isset($value)) { + $found = FALSE; + try { + $this->assertFieldsByValue($fields, $value); + $found = TRUE; + } + catch (\Exception $e) { + } + + if ($found) { + throw new ExpectationException(sprintf('The field resulting from %s was found with the provided value %s.', $xpath, $value), $this->getSession()->getDriver()); + } + } + else { + throw new ExpectationException(sprintf('The field resulting from %s was found.', $xpath), $this->getSession()->getDriver()); + } + } + } + + /** + * Asserts that a field exists in the current page with a given Xpath result. + * + * @param \Behat\Mink\Element\NodeElement[] $fields + * Xml elements. + * @param string $value + * (optional) Value of the field to assert. You may pass in NULL (default) to skip + * checking the actual value, while still checking that the field exists. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + */ + protected function assertFieldsByValue($fields, $value = NULL, $message = '') { + // If value specified then check array for match. + $found = TRUE; + if (isset($value)) { + $found = FALSE; + if ($fields) { + foreach ($fields as $field) { + if ($field->getAttribute('type') == 'checkbox') { + if (is_bool($value)) { + $found = $field->isChecked() == $value; + } + else { + $found = TRUE; + } + } + elseif ($field->getAttribute('value') == $value) { + // Input element with correct value. + $found = TRUE; + } + elseif ($field->find('xpath', '//option[@value = ' . (new Escaper())->escapeLiteral($value) . ' and @selected = "selected"]')) { + // Select element with an option. + $found = TRUE; + } + elseif ($field->getTagName() === 'textarea' && $field->getValue() == $value) { + // Text area with correct text. Use getValue() here because + // getText() would remove any newlines in the value. + $found = TRUE; + } + elseif ($field->getTagName() !== 'input' && $field->getText() == $value) { + $found = TRUE; + } + } + } + } + $this->assertTrue($fields && $found, $message); + } + + /** + * Passes if the raw text IS found escaped on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + protected function assertEscaped($raw) { + $this->assertSession()->assertEscaped($raw); + } + + /** + * Passes if the raw text is not found escaped on the loaded page. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + protected function assertNoEscaped($raw) { + $this->assertSession()->assertNoEscaped($raw); + } + + /** + * Triggers a pass if the Perl regex pattern is found in the raw content. + * + * @param string $pattern + * Perl regex to look for including the regex delimiters. + */ + protected function assertPattern($pattern) { + $this->assertSession()->responseMatches($pattern); + } + + /** + * Triggers a pass if the Perl regex pattern is not found in the raw content. + * + * @param string $pattern + * Perl regex to look for including the regex delimiters. + * + * @see https://www.drupal.org/node/2864262 + */ + protected function assertNoPattern($pattern) { + $this->assertSession()->responseNotMatches($pattern); + } + + /** + * Asserts whether an expected cache tag was present in the last response. + * + * @param string $expected_cache_tag + * The expected cache tag. + */ + protected function assertCacheTag($expected_cache_tag) { + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $expected_cache_tag); + } + + /** + * Asserts whether an expected cache tag was absent in the last response. + * + * @param string $cache_tag + * The cache tag to check. + * + * @see https://www.drupal.org/node/2864029 + */ + protected function assertNoCacheTag($cache_tag) { + $this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', $cache_tag); + } + + /** + * Checks that current response header equals value. + * + * @param string $name + * Name of header to assert. + * @param string $value + * Value of the header to assert. + */ + protected function assertHeader($name, $value) { + $this->assertSession()->responseHeaderEquals($name, $value); + } + + /** + * Returns WebAssert object. + * + * @param string $name + * (optional) Name of the session. Defaults to the active session. + * + * @return \Drupal\Tests\WebAssert + * A new web-assert option for asserting the presence of elements with. + */ + abstract public function assertSession($name = NULL); + + /** + * Builds an XPath query. + * + * Builds an XPath query by replacing placeholders in the query by the value + * of the arguments. + * + * XPath 1.0 (the version supported by libxml2, the underlying XML library + * used by PHP) doesn't support any form of quotation. This function + * simplifies the building of XPath expression. + * + * @param string $xpath + * An XPath query, possibly with placeholders in the form ':name'. + * @param array $args + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * + * @return string + * An XPath query with arguments replaced. + */ + protected function buildXPathQuery($xpath, array $args = []) { + return $this->assertSession()->buildXPathQuery($xpath, $args); + } + + /** + * Helper: Constructs an XPath for the given set of attributes and value. + * + * @param string $attribute + * Field attributes. + * @param string $value + * Value of field. + * + * @return string + * XPath for specified values. + */ + protected function constructFieldXpath($attribute, $value) { + $xpath = '//textarea[@' . $attribute . '=:value]|//input[@' . $attribute . '=:value]|//select[@' . $attribute . '=:value]'; + return $this->buildXPathQuery($xpath, [':value' => $value]); + } + + /** + * Gets the current raw content. + */ + protected function getRawContent() { + return $this->getSession()->getPage()->getContent(); + } + + /** + * Get all option elements, including nested options, in a select. + * + * @param \Behat\Mink\Element\NodeElement $element + * The element for which to get the options. + * + * @return \Behat\Mink\Element\NodeElement[] + * Option elements in select. + */ + protected function getAllOptions(NodeElement $element) { + return $element->findAll('xpath', '//option'); + } + +} diff --git a/web/modules/webform/tests/src/Functional/WebformBlockCacheTest.php b/web/modules/webform/tests/src/Functional/WebformBlockCacheTest.php index 8f996138eb..75076e5784 100644 --- a/web/modules/webform/tests/src/Functional/WebformBlockCacheTest.php +++ b/web/modules/webform/tests/src/Functional/WebformBlockCacheTest.php @@ -22,7 +22,7 @@ class WebformBlockCacheTest extends BrowserTestBase { /** * Authenticated user. - * + * * @var \Drupal\user\Entity\User */ private $authenticatedUser; @@ -33,7 +33,7 @@ class WebformBlockCacheTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - $this->authenticatedUser = $this->drupalCreateUser([ + $this->authenticatedUser = $this->createUser([ 'access content', ]); @@ -78,13 +78,17 @@ public function testAuthenticatedVisitIsCacheable() { * Test that if an Webform is access restricted the page can still be cached. */ public function testAuthenticatedAndRestrictedVisitIsCacheable() { + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + $default_access_rules = $access_rules_manager->getDefaultAccessRules(); + $access_rules = [ 'create' => [ 'roles' => [], 'users' => [], 'permissions' => ['access content'], ], - ] + Webform::getDefaultAccessRules(); + ] + $default_access_rules; Webform::load('contact')->setAccessRules($access_rules)->save(); diff --git a/web/modules/webform/tests/src/Functional/WebformBrowserTestBase.php b/web/modules/webform/tests/src/Functional/WebformBrowserTestBase.php new file mode 100644 index 0000000000..9651fbdd6b --- /dev/null +++ b/web/modules/webform/tests/src/Functional/WebformBrowserTestBase.php @@ -0,0 +1,475 @@ +<?php + +namespace Drupal\Tests\webform\Functional; + +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Config\FileStorage; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Test\AssertMailTrait; +use Drupal\filter\Entity\FilterFormat; +use Drupal\Tests\BrowserTestBase; +use Drupal\taxonomy\Entity\Term; +use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\webform\WebformInterface; +use Drupal\webform\Entity\Webform; + +/** + * Defines an abstract test base for webform tests. + */ +abstract class WebformBrowserTestBase extends BrowserTestBase { + + use AssertMailTrait; + use WebformAssertLegacyTrait; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = []; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->loadWebforms(static::$testWebforms); + } + + /** + * {@inheritdoc} + */ + public function tearDown() { + $this->purgeSubmissions(); + parent::tearDown(); + } + + /****************************************************************************/ + // Block. + /****************************************************************************/ + + /** + * Place breadcrumb page, tasks, and actions. + */ + protected function placeBlocks() { + $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('page_title_block'); + $this->drupalPlaceBlock('local_tasks_block'); + $this->drupalPlaceBlock('local_actions_block'); + } + + /** + * Place webform test module blocks. + * + * @param string $module_name + * Test module name. + */ + protected function placeWebformBlocks($module_name) { + $config_directory = drupal_get_path('module', 'webform') . '/tests/modules/' . $module_name . '/config'; + $config_files = file_scan_directory($config_directory, '/block\..*/'); + foreach ($config_files as $config_file) { + $data = Yaml::decode(file_get_contents($config_file->uri)); + $plugin_id = $data['plugin']; + $settings = $data['settings']; + unset($settings['id']); + $this->drupalPlaceBlock($plugin_id, $settings); + } + } + + /****************************************************************************/ + // Filter. + /****************************************************************************/ + + /** + * Basic HTML filter format. + * + * @var \Drupal\filter\FilterFormatInterface + */ + protected $basicHtmlFilter; + + /** + * Full HTML filter format. + * + * @var \Drupal\filter\FilterFormatInterface + */ + protected $fullHtmlFilter; + + /** + * Create basic HTML filter format. + */ + protected function createFilters() { + $this->basicHtmlFilter = FilterFormat::create([ + 'format' => 'basic_html', + 'name' => 'Basic HTML', + 'filters' => [ + 'filter_html' => [ + 'status' => 1, + 'settings' => [ + 'allowed_html' => '<p> <br> <strong> <a> <em>', + ], + ], + ], + ]); + $this->basicHtmlFilter->save(); + + $this->fullHtmlFilter = FilterFormat::create([ + 'format' => 'full_html', + 'name' => 'Full HTML', + ]); + $this->fullHtmlFilter->save(); + } + + /****************************************************************************/ + // Taxonomy. + /****************************************************************************/ + + /** + * Create the 'tags' taxonomy vocabulary. + */ + protected function createTags() { + $vocabulary = Vocabulary::create([ + 'name' => 'Tags', + 'vid' => 'tags', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $vocabulary->save(); + for ($i = 1; $i <= 3; $i++) { + $parent_term = Term::create([ + 'name' => "Parent $i", + 'vid' => 'tags', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $parent_term->save(); + for ($x = 1; $x <= 3; $x++) { + $child_term = Term::create([ + 'name' => "Parent $i: Child $x", + 'parent' => $parent_term->id(), + 'vid' => 'tags', + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $child_term->save(); + } + } + } + + /****************************************************************************/ + // Webform. + /****************************************************************************/ + + /** + * Lazy load a test webforms. + * + * @param array $ids + * Webform ids. + */ + protected function loadWebforms(array $ids) { + foreach ($ids as $id) { + $this->loadWebform($id); + } + $this->pass(new FormattableMarkup('Loaded webforms: %webforms.', [ + '%webforms' => implode(', ', $ids), + ])); + } + + /** + * Lazy load a test webform. + * + * @param string $id + * Webform id. + * + * @return \Drupal\webform\WebformInterface|null + * A webform. + * + * @see \Drupal\views\Tests\ViewTestData::createTestViews + */ + protected function loadWebform($id) { + $storage = \Drupal::entityTypeManager()->getStorage('webform'); + if ($webform = $storage->load($id)) { + return $webform; + } + else { + $config_name = 'webform.webform.' . $id; + if (strpos($id, 'test_') === 0) { + $config_directory = drupal_get_path('module', 'webform') . '/tests/modules/webform_test/config/install'; + } + elseif (strpos($id, 'example_') === 0) { + $config_directory = drupal_get_path('module', 'webform') . '/modules/webform_examples/config/install'; + } + elseif (strpos($id, 'template_') === 0) { + $config_directory = drupal_get_path('module', 'webform') . '/modules/webform_templates/config/install'; + } + else { + throw new \Exception("Webform $id not valid"); + } + + if (!file_exists("$config_directory/$config_name.yml")) { + throw new \Exception("Webform $id does not exist in $config_directory"); + } + + $file_storage = new FileStorage($config_directory); + $values = $file_storage->read($config_name); + $webform = $storage->create($values); + $webform->save(); + return $webform; + } + } + + /** + * Create a webform. + * + * @param array|null $values + * (optional) Array of values. + * @param array|null $elements + * (optional) Array of elements. + * @param array $settings + * (optional) Webform settings. + * + * @return \Drupal\webform\WebformInterface + * A webform. + */ + protected function createWebform($values = [], array $elements = [], array $settings = []) { + // Create new webform. + $id = $this->randomMachineName(8); + $webform = Webform::create($values + [ + 'langcode' => 'en', + 'status' => WebformInterface::STATUS_OPEN, + 'id' => $id, + 'title' => $id, + 'elements' => Yaml::encode($elements), + 'settings' => $settings + Webform::getDefaultSettings(), + ]); + $webform->save(); + return $webform; + } + + /****************************************************************************/ + // Submission. + /****************************************************************************/ + + /** + * Post a new submission to a webform. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param array $edit + * Submission values. + * @param string $submit + * Value of the submit button whose click is to be emulated. + * @param array $options + * Options to be forwarded to the url generator. + * + * @return int + * The created submission's sid. + */ + protected function postSubmission(WebformInterface $webform, array $edit = [], $submit = NULL, array $options = []) { + $submit = $this->getWebformSubmitButtonLabel($webform, $submit); + $this->drupalPostForm('webform/' . $webform->id(), $edit, $submit, $options); + return $this->getLastSubmissionId($webform); + } + + /** + * Post a new test submission to a webform. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param array $edit + * Submission values. + * @param string $submit + * Value of the submit button whose click is to be emulated. + * @param array $options + * Options to be forwarded to the url generator. + * + * @return int + * The created test submission's sid. + */ + protected function postSubmissionTest(WebformInterface $webform, array $edit = [], $submit = NULL, array $options = []) { + $submit = $this->getWebformSubmitButtonLabel($webform, $submit); + $this->drupalPostForm('webform/' . $webform->id() . '/test', $edit, $submit, $options); + return $this->getLastSubmissionId($webform); + } + + /****************************************************************************/ + // Submission. + /****************************************************************************/ + + /** + * Load the specified webform submission from the storage. + * + * @param int $sid + * The submission identifier. + * + * @return \Drupal\webform\WebformSubmissionInterface + * The loaded webform submission. + */ + protected function loadSubmission($sid) { + /** @var \Drupal\webform\WebformSubmissionStorage $storage */ + $storage = $this->container->get('entity_type.manager')->getStorage('webform_submission'); + $storage->resetCache([$sid]); + return $storage->load($sid); + } + + /** + * Purge all submission before the webform.module is uninstalled. + */ + protected function purgeSubmissions() { + \Drupal::database()->query('DELETE FROM {webform_submission}'); + } + + /** + * Get a webform's submit button label. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param string $submit + * Value of the submit button whose click is to be emulated. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup|string + * The webform's submit button label. + */ + protected function getWebformSubmitButtonLabel(WebformInterface $webform, $submit = NULL) { + if ($submit) { + return $submit; + } + + $actions_element = $webform->getElement('actions'); + if ($actions_element && isset($actions_element['#submit__label'])) { + return $actions_element['#submit__label']; + } + + return t('Submit'); + } + + /** + * Get the last submission id. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return int|null + * The last submission id. NULL if saving of results is disabled. + */ + protected function getLastSubmissionId(WebformInterface $webform) { + if ($webform->getSetting('results_disabled')) { + return NULL; + } + + // Get submission sid. + $url = UrlHelper::parse($this->getUrl()); + if (isset($url['query']['sid'])) { + return $url['query']['sid']; + } + else { + $entity_ids = $this->container->get('entity_type.manager')->getStorage('webform_submission')->getQuery() + ->sort('sid', 'DESC') + ->condition('webform_id', $webform->id()) + ->accessCheck(FALSE) + ->execute(); + return reset($entity_ids); + } + } + + /****************************************************************************/ + // Export. + /****************************************************************************/ + + /** + * Request a webform results export CSV. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param array $options + * An associative array of export options. + */ + protected function getExport(WebformInterface $webform, array $options = []) { + /** @var \Drupal\webform\WebformSubmissionExporterInterface $exporter */ + $exporter = \Drupal::service('webform_submission.exporter'); + $options += $exporter->getDefaultExportOptions(); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/download', ['query' => $options]); + } + + /** + * Get webform export columns. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return array + * An array of exportable columns. + */ + protected function getExportColumns(WebformInterface $webform) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + $field_definitions = $submission_storage->getFieldDefinitions(); + $field_definitions = $submission_storage->checkFieldDefinitionAccess($webform, $field_definitions); + $elements = $webform->getElementsInitializedAndFlattened(); + $columns = array_merge(array_keys($field_definitions), array_keys($elements)); + return array_combine($columns, $columns); + } + + /****************************************************************************/ + // Email. + /****************************************************************************/ + + /** + * Gets that last email sent during the currently running test case. + * + * @return array + * An array containing the last email message captured during the + * current test. + */ + protected function getLastEmail() { + $sent_emails = $this->getMails(); + $sent_email = end($sent_emails); + $this->debug($sent_email); + return $sent_email; + } + + /****************************************************************************/ + // Assert. + /****************************************************************************/ + + /** + * Passes if the CSS selector IS found on the loaded page, fail otherwise. + */ + protected function assertCssSelect($selector, $message = '') { + $element = $this->cssSelect($selector); + if (!$message) { + $message = new FormattableMarkup('Found @selector', ['@selector' => $selector]); + } + $this->assertTrue(!empty($element), $message); + } + + /** + * Passes if the CSS selector IS NOT found on the loaded page, fail otherwise. + */ + protected function assertNoCssSelect($selector, $message = '') { + $element = $this->cssSelect($selector); + $this->assertTrue(empty($element), $message); + } + + /****************************************************************************/ + // Debug. + /****************************************************************************/ + + /** + * Logs verbose (debug) message in a text file. + * + * @param mixed $data + * Data to be output. + */ + protected function debug($data) { + $string = var_export($data, TRUE); + $string = preg_replace('/=>\s*array\s*\(/', '=> array(', $string); + $this->htmlOutput('<pre>' . htmlentities($string) . '</pre>'); + } + +} diff --git a/web/modules/webform/tests/src/Functional/WebformBrowserTestBaseTest.php b/web/modules/webform/tests/src/Functional/WebformBrowserTestBaseTest.php new file mode 100644 index 0000000000..d1cb8c4768 --- /dev/null +++ b/web/modules/webform/tests/src/Functional/WebformBrowserTestBaseTest.php @@ -0,0 +1,81 @@ +<?php + +namespace Drupal\Tests\webform\Functional; + +use Drupal\webform\Entity\Webform; + +/** + * Test the webform test base class. + * + * @group webform_browser + */ +class WebformBrowserTestBaseTest extends WebformBrowserTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['webform', 'block', 'user']; + + /** + * Webforms to load. + * + * @var array + */ + protected static $testWebforms = ['test_ajax']; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + } + + /** + * Test base helper methods. + */ + public function testWebformBase() { + // Check that test webform is installed. + $this->assertNotNull(Webform::load('test_ajax')); + + // Check create webform. + $test_webform = $this->createWebform(); + $this->assertNotNull($test_webform); + + $webform = Webform::load('contact'); + + // Check post submission return NULL if post fails. + $sid = $this->postSubmission($webform); + $this->assertFalse($sid); + + // Login root user. + $this->drupalLogin($this->rootUser); + + // Check post test submission returns an sid. + $sid = $this->postSubmissionTest($webform); + $this->assertNotNull($sid); + + // Check submission load not from cache. + $webform_submission = $this->loadSubmission($sid); + $this->assertNotNull($webform_submission); + $this->assertEquals($webform_submission->getWebform()->id(), 'contact'); + + // Check submission email. + $last_email = $this->getLastEmail(); + $this->assertEquals($last_email['id'], 'webform_contact_email_notification'); + + // Check purge submission deletes the submission. + $this->purgeSubmissions(); + $webform_submission = $this->loadSubmission($sid); + $this->assertNull($webform_submission); + + // Check place blocks. + $this->placeBlocks(); + $this->drupalGet('/webform/contact'); + $this->assertRaw('block-system-breadcrumb-block'); + $this->assertRaw('block-page-title-block'); + $this->assertRaw('block-local-tasks-block'); + } + +} diff --git a/web/modules/webform/tests/src/Functional/WebformContributeFunctionalTest.php b/web/modules/webform/tests/src/Functional/WebformContributeFunctionalTest.php index 8afe67e4f0..8d84130c85 100644 --- a/web/modules/webform/tests/src/Functional/WebformContributeFunctionalTest.php +++ b/web/modules/webform/tests/src/Functional/WebformContributeFunctionalTest.php @@ -52,7 +52,7 @@ public function testContribute() { $this->assertSession()->responseNotContains('<strong><a href="https://www.drupal.org/u/jrockowitz">Jacob Rockowitz</a></strong>'); // Check that 'Contribute' local task is visible. - $this->drupalGet('/admin/structure/webform'); + $this->drupalGet('/admin/structure/webform/help'); $this->assertSession()->linkExists('Contribute'); // Check that 'Contribute' route is accessible. @@ -64,7 +64,7 @@ public function testContribute() { $this->drupalPostForm('/admin/structure/webform/config/advanced', $edit, t('Save')); // Check that 'Contribute' local task is hidden. - $this->drupalGet('/admin/structure/webform'); + $this->drupalGet('/admin/structure/webform/help'); $this->assertSession()->linkNotExists('Contribute'); // Check that 'Contribute' route is removed. diff --git a/web/modules/webform/tests/src/Functional/WebformListBuilderTest.php b/web/modules/webform/tests/src/Functional/WebformListBuilderTest.php index 8e0d1bb928..2574bd3107 100644 --- a/web/modules/webform/tests/src/Functional/WebformListBuilderTest.php +++ b/web/modules/webform/tests/src/Functional/WebformListBuilderTest.php @@ -25,7 +25,7 @@ public function testWebformOverview() { $assert_session = $this->assertSession(); // Test with a superuser. - $any_webform_user = $this->drupalCreateUser([ + $any_webform_user = $this->createUser([ 'access webform overview', 'create webform', 'edit any webform', @@ -35,18 +35,15 @@ public function testWebformOverview() { $list_path = '/admin/structure/webform'; $this->drupalGet($list_path); $assert_session->linkExists('Test: Submissions'); - $assert_session->linkExists('Submissions'); - $assert_session->linkExists('Download'); - $assert_session->linkExists('Clear'); + $assert_session->linkExists('Results'); $assert_session->linkExists('Build'); $assert_session->linkExists('Settings'); $assert_session->linkExists('View'); - $assert_session->linkExists('Test'); $assert_session->linkExists('Duplicate'); $assert_session->linkExists('Delete'); // Test with a user that only has submission access. - $any_webform_submission_user = $this->drupalCreateUser([ + $any_webform_submission_user = $this->createUser([ 'access webform overview', 'view any webform submission', 'edit any webform submission', @@ -57,15 +54,10 @@ public function testWebformOverview() { // Webform name should not be a link as the user doesn't have access to the // submission page. $assert_session->linkExists('Test: Submissions'); - $assert_session->linkExists('Submissions'); - $assert_session->linkExists('Download'); - // TODO: Is this a bug that this doesn't pass? User has delete any - // submission permission. - // $assert_session->linkExists('Clear') + $assert_session->linkExists('Results'); $assert_session->linkNotExists('Build'); $assert_session->linkNotExists('Settings'); $assert_session->linkExists('View'); - $assert_session->linkExists('Test'); $assert_session->linkNotExists('Duplicate'); $assert_session->linkNotExists('Delete'); @@ -80,8 +72,8 @@ public function testWebformOverview() { $this->assertLinkNotInRow('Test: Submissions', 'View'); // Test with role that is configured via webform access settings. - $rid = $this->createRole(['access webform overview']); - $special_access_user = $this->drupalCreateUser(); + $rid = $this->drupalCreateRole(['access webform overview']); + $special_access_user = $this->createUser(); $special_access_user->addRole($rid); $special_access_user->save(); $access = $webform_config->get('access'); @@ -90,8 +82,7 @@ public function testWebformOverview() { $this->drupalLogin($special_access_user); $this->drupalGet($list_path); $assert_session->responseContains('Test: Submissions'); - $assert_session->linkExists('Submissions'); - $assert_session->linkExists('Download'); + $assert_session->linkExists('Results'); } /** diff --git a/web/modules/webform/tests/src/Functional/WebformSubmissionViewsAccessTest.php b/web/modules/webform/tests/src/Functional/WebformSubmissionViewsAccessTest.php new file mode 100644 index 0000000000..ff1bd8b7f9 --- /dev/null +++ b/web/modules/webform/tests/src/Functional/WebformSubmissionViewsAccessTest.php @@ -0,0 +1,199 @@ +<?php + +namespace Drupal\Tests\webform\Functional; + +use Drupal\Tests\BrowserTestBase; +use Drupal\webform\Entity\Webform; +use Drupal\webform\Entity\WebformSubmission; +use Drupal\webform\WebformInterface; + +/** + * Tests access rules in the context of webform submission views access. + * + * @group webform_browser + */ +class WebformSubmissionViewsAccessTest extends BrowserTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = [ + 'system', + 'user', + 'views', + 'webform', + 'webform_test_views', + ]; + + /** + * Test webform submission entity access in a view query. + */ + public function testEntityAccess() { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = Webform::load('contact'); + + // Create any access user, own access user, and no (anonymous) access user. + $any_user = $this->drupalCreateUser(['access webform overview']); + $own_user = $this->drupalCreateUser(['access webform overview']); + $without_access_user = $this->drupalCreateUser(['access webform overview']); + + // Grant any and own access to submissions. + $webform->setAccessRules([ + 'view_any' => ['users' => [$any_user->id()]], + 'view_own' => ['users' => [$own_user->id()]], + ])->save(); + + // Create an array of the accounts. + $accounts = [ + 'any_user' => $any_user, + 'own_user' => $own_user, + 'without_access' => $without_access_user, + ]; + + // Create test submissions. + $this->createSubmissions($webform, $accounts); + + // Check user submission access. + $this->checkUserSubmissionAccess($webform, $accounts); + + // Clear webform access rules. + $webform->setAccessRules([])->save(); + + // Check user submission access cache is cleared. + $this->checkUserSubmissionAccess($webform, $accounts); + } + + /** + * Tests webform submission views enforce access per user's permissions. + */ + public function testPermissionAccess() { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = Webform::load('contact'); + + // Create any access user, own access user, and no (anonymous) access user. + $own_webform_user = $this->drupalCreateUser([ + 'access webform overview', + 'edit own webform', + ]); + $webform->setOwner($own_webform_user)->save(); + $any_submission_user = $this->drupalCreateUser([ + 'access webform overview', + 'view any webform submission', + ]); + $own_submission_user = $this->drupalCreateUser([ + 'access webform overview', + 'view own webform submission', + ]); + $without_access_user = $this->drupalCreateUser([ + 'access webform overview', + ]); + + // Create an array of the accounts. + /** @var \Drupal\user\Entity\User[] $accounts */ + $accounts = [ + 'own_webform_user' => $own_webform_user, + 'any_submission_user' => $any_submission_user, + 'own_submission_user' => $own_submission_user, + 'without_access' => $without_access_user, + ]; + + // Create test submissions. + $this->createSubmissions($webform, $accounts); + + // Check user submission access. + $this->checkUserSubmissionAccess($webform, $accounts); + + // Clear any and own permissions for all accounts. + foreach ($accounts as &$account) { + $roles = $account->getRoles(TRUE); + $rid = reset($roles); + user_role_revoke_permissions($rid, [ + 'view any webform submission', + 'view own webform submission', + 'edit own webform', + ]); + } + + // Check user submission access cache is cleared. + $this->checkUserSubmissionAccess($webform, $accounts); + } + + /** + * Create test a submission for each account. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * @param array $accounts + * An associative array of test users. + */ + protected function createSubmissions(WebformInterface $webform, array $accounts) { + /** @var \Drupal\webform\WebformSubmissionGenerateInterface $submission_generate */ + $submission_generate = \Drupal::service('webform_submission.generate'); + + // Create a test submission for each user account. + foreach ($accounts as $account) { + WebformSubmission::create([ + 'webform_id' => $webform->id(), + 'uid' => $account->id(), + 'data' => $submission_generate->getData($webform), + ])->save(); + } + } + + /** + * Check user submission access. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * @param array $accounts + * An associative array of test users. + * + * @see \Drupal\webform_access\Tests\WebformAccessSubmissionViewsTest::checkUserSubmissionAccess + */ + protected function checkUserSubmissionAccess(WebformInterface $webform, array $accounts) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $webform_submission_storage */ + $webform_submission_storage = \Drupal::entityTypeManager() + ->getStorage('webform_submission'); + + // Reset the static cache to make sure we are hitting actual fresh access + // results. + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + \Drupal::entityTypeManager()->getAccessControlHandler('webform_submission')->resetCache(); + + foreach ($accounts as $account_type => $account) { + // Login the current user. + $this->drupalLogin($account); + + // Get the webform_test_views_access view and the sid for each + // displayed record. Submission access is controlled via the query. + // @see webform_query_webform_submission_access_alter() + $this->drupalGet('/admin/structure/webform/test/views_access'); + + $views_sids = []; + foreach ($this->getSession()->getPage()->findAll('css', '.view .view-content tbody .views-field-sid') as $node) { + $views_sids[] = $node->getText(); + } + sort($views_sids); + + $expected_sids = []; + + // Load all webform submissions and check access using the access method. + // @see \Drupal\webform\WebformSubmissionAccessControlHandler::checkAccess + $webform_submissions = $webform_submission_storage->loadByEntities($webform); + + foreach ($webform_submissions as $webform_submission) { + if ($webform_submission->access('view', $account)) { + $expected_sids[] = $webform_submission->id(); + } + } + + sort($expected_sids); + + // Check that the views sids is equal to the expected sids. + $this->assertSame($expected_sids, $views_sids, "User '" . $account_type . "' access has correct access through view on webform submission entity type."); + } + } + +} diff --git a/web/modules/webform/tests/src/FunctionalJavascript/WebformSubmissionToggleFlagsTest.php b/web/modules/webform/tests/src/FunctionalJavascript/WebformSubmissionToggleFlagsTest.php index 9c69107519..3fa1338bbf 100644 --- a/web/modules/webform/tests/src/FunctionalJavascript/WebformSubmissionToggleFlagsTest.php +++ b/web/modules/webform/tests/src/FunctionalJavascript/WebformSubmissionToggleFlagsTest.php @@ -53,7 +53,7 @@ public function testHandlerJavascript() { 'edit any webform submission', 'delete any webform submission', ])); - $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); + $this->drupalGet('/admin/structure/webform/manage/' . $webform->id() . '/results/submissions'); $assert->statusCodeEquals(200); $assert->elementExists('css', "#webform-submission-$sid-sticky")->click(); $assert->assertWaitOnAjaxRequest(); diff --git a/web/modules/webform/tests/src/Kernel/Breadcrumb/WebformBreadcrumbBuilderTest.php b/web/modules/webform/tests/src/Kernel/Breadcrumb/WebformBreadcrumbBuilderTest.php index fea93f8aa6..ab485a881b 100644 --- a/web/modules/webform/tests/src/Kernel/Breadcrumb/WebformBreadcrumbBuilderTest.php +++ b/web/modules/webform/tests/src/Kernel/Breadcrumb/WebformBreadcrumbBuilderTest.php @@ -106,9 +106,9 @@ protected function setUp() { $this->setUpMockEntities(); // Make some test doubles. - $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $this->requestHandler = $this->getMock('Drupal\webform\WebformRequestInterface'); - $this->translationManager = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); + $this->moduleHandler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $this->requestHandler = $this->createMock('Drupal\webform\WebformRequestInterface'); + $this->translationManager = $this->createMock('Drupal\Core\StringTranslation\TranslationInterface'); // Make an object to test. $this->breadcrumbBuilder = $this->getMockBuilder('Drupal\webform\Breadcrumb\WebformBreadcrumbBuilder') @@ -197,13 +197,13 @@ public function providerTestApplies() { [FALSE, 'entity.webform'], [TRUE, 'entity.webform.handler.'], [TRUE, 'entity.webform_ui.element'], - [TRUE, 'webform.user.submissions'], - [TRUE, 'webform.user.submissions'], + [TRUE, 'entity.webform.user.submissions'], // Source entity. [TRUE, 'entity.{source_entity}.webform'], [TRUE, 'entity.{source_entity}.webform_submission'], [TRUE, 'entity.node.webform'], [TRUE, 'entity.node.webform_submission'], + [TRUE, 'entity.node.webform.user.submissions'], // Submissions. [FALSE, 'entity.webform.user.submission'], [TRUE, 'entity.webform.user.submission', [['webform_submission', $this->webformSubmissionAccess]]], @@ -255,7 +255,7 @@ public function providerTestType() { // Handler. ['webform_handler', 'entity.webform.handler.'], // User submissions. - ['webform_user_submissions', 'webform.user.submissions'], + ['webform_user_submissions', 'entity.webform.user.submissions'], ['webform_source_entity', 'entity.{source_entity}.webform.user.submissions'], ['webform_source_entity', 'entity.node.webform.user.submissions'], // User submission. @@ -492,7 +492,7 @@ protected function setSourceEntity(EntityInterface $entity) { * A mocked route match. */ protected function getMockRouteMatch($route_name = NULL, array $parameter_map = []) { - $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface'); $route_match->expects($this->any()) ->method('getRouteName') ->will($this->returnValue($route_name)); diff --git a/web/modules/webform/tests/src/Kernel/Entity/WebformEntityTest.php b/web/modules/webform/tests/src/Kernel/Entity/WebformEntityTest.php index 2855093271..74dd515760 100644 --- a/web/modules/webform/tests/src/Kernel/Entity/WebformEntityTest.php +++ b/web/modules/webform/tests/src/Kernel/Entity/WebformEntityTest.php @@ -2,9 +2,9 @@ namespace Drupal\Tests\webform\Kernel\Entity; -use Drupal\Core\Serialization\Yaml; use Drupal\KernelTests\KernelTestBase; use Drupal\webform\Entity\Webform; +use Drupal\webform\Utility\WebformYaml; use Drupal\webform\WebformException; use Drupal\webform\WebformInterface; @@ -193,7 +193,7 @@ public function testWebformMethods() { $webform->setElements($elements); // Check that elements are serialized to YAML. - $this->assertTrue($webform->getElementsRaw(), Yaml::encode($elements)); + $this->assertTrue($webform->getElementsRaw(), WebformYaml::encode($elements)); // Check elements decoded and flattened. $flattened_elements = [ @@ -212,7 +212,7 @@ public function testWebformMethods() { ]; $this->assertEquals($webform->getElementsDecodedAndFlattened(), $flattened_elements); - // Check elements initialized and flattened. + // Check elements initialized and flattened. $elements_initialized_and_flattened = [ 'root' => [ '#type' => 'textfield', @@ -353,7 +353,7 @@ public function testElementsCrud() { ], ]; $webform->setElementProperties('root', $elements['root']); - $this->assertEquals($webform->getElementsRaw(), Yaml::encode($elements)); + $this->assertEquals($webform->getElementsRaw(), WebformYaml::encode($elements)); // Check add new container to root. $elements['root']['container'] = [ @@ -361,7 +361,7 @@ public function testElementsCrud() { '#title' => 'container', ]; $webform->setElementProperties('container', $elements['root']['container'], 'root'); - $this->assertEquals($webform->getElementsRaw(), Yaml::encode($elements)); + $this->assertEquals($webform->getElementsRaw(), WebformYaml::encode($elements)); // Check add new element to container. $elements['root']['container']['element'] = [ @@ -369,7 +369,7 @@ public function testElementsCrud() { '#title' => 'element', ]; $webform->setElementProperties('element', $elements['root']['container']['element'], 'container'); - $this->assertEquals($webform->getElementsRaw(), Yaml::encode($elements)); + $this->assertEquals($webform->getElementsRaw(), WebformYaml::encode($elements)); // Check delete container with al recursively delete all children. $elements = [ @@ -379,7 +379,7 @@ public function testElementsCrud() { ], ]; $webform->deleteElement('container'); - $this->assertEquals($webform->getElementsRaw(), Yaml::encode($elements)); + $this->assertEquals($webform->getElementsRaw(), WebformYaml::encode($elements)); } } diff --git a/web/modules/webform/tests/src/Kernel/Utility/WebformDialogHelperTest.php b/web/modules/webform/tests/src/Kernel/Utility/WebformDialogHelperTest.php index 27756c9c21..9a89615d59 100644 --- a/web/modules/webform/tests/src/Kernel/Utility/WebformDialogHelperTest.php +++ b/web/modules/webform/tests/src/Kernel/Utility/WebformDialogHelperTest.php @@ -34,14 +34,14 @@ public function testGetModalDialogAttributes() { $this->assertEquals(WebformDialogHelper::getModalDialogAttributes(), [ 'class' => ['webform-ajax-link'], 'data-dialog-type' => 'modal', - 'data-dialog-options' => '{"width":800,"dialogClass":"webform-modal"}', + 'data-dialog-options' => '{"width":800,"dialogClass":"webform-ui-dialog"}', ]); // Check custom width and attributes. $this->assertEquals(WebformDialogHelper::getModalDialogAttributes(400, ['custom']), [ 'class' => ['custom', 'webform-ajax-link'], 'data-dialog-type' => 'modal', - 'data-dialog-options' => '{"width":400,"dialogClass":"webform-modal"}', + 'data-dialog-options' => '{"width":400,"dialogClass":"webform-ui-dialog"}', ]); // Disable dialogs. diff --git a/web/modules/webform/tests/src/Kernel/WebformEntityElementsValidationTest.php b/web/modules/webform/tests/src/Kernel/WebformEntityElementsValidationTest.php index f8257a5b63..887a9f5451 100644 --- a/web/modules/webform/tests/src/Kernel/WebformEntityElementsValidationTest.php +++ b/web/modules/webform/tests/src/Kernel/WebformEntityElementsValidationTest.php @@ -64,6 +64,15 @@ public function testValidate() { ], ], + // Check names. + [ + 'getElementsRaw' => "Not Valid: + '#type': textfield", + 'messages' => [ + 'The element key <em class="placeholder">Not Valid</em> on line 1 must contain only lowercase letters, numbers, and underscores.', + ], + ], + // Check duplicate names. [ 'getElementsRaw' => "name: @@ -88,6 +97,18 @@ public function testValidate() { ], ], + // Check reserved names. + [ + 'getElementsRaw' => "name: + '#type': textfield +duplicate: + add: + '#type': textfield", + 'messages' => [ + 'The element key <em class="placeholder">add</em> on line 4 is a reserved key.', + ], + ], + // Check ignored properties. [ 'getElementsRaw' => "'tree': @@ -189,7 +210,7 @@ public function testValidate() { ]; /** @var \Drupal\webform\WebformInterface $webform */ - $webform = $this->getMock('\Drupal\webform\WebformInterface'); + $webform = $this->createMock('\Drupal\webform\WebformInterface'); $methods = $test; unset($methods['messages']); foreach ($methods as $method => $returnValue) { diff --git a/web/modules/webform/tests/src/Kernel/WebformSubmissionStorageTest.php b/web/modules/webform/tests/src/Kernel/WebformSubmissionStorageTest.php index fdae99ff56..d282318527 100644 --- a/web/modules/webform/tests/src/Kernel/WebformSubmissionStorageTest.php +++ b/web/modules/webform/tests/src/Kernel/WebformSubmissionStorageTest.php @@ -51,13 +51,13 @@ public function testStorage() { $this->assertEquals($webform_submission->id(), key($webform_submissions)); } - /** * Test purging of the webform submissions. * * @dataProvider providerPurge */ public function testPurge($webform_purging, $webform_submissions_definition, $purged) { + $request_time = \Drupal::time()->getRequestTime(); $days_to_seconds = 60 * 60 * 24; $purge_days = 10; $purge_amount = 2; @@ -81,7 +81,7 @@ public function testPurge($webform_purging, $webform_submissions_definition, $pu 'webform_id' => $v->id(), ]); $webform_submission->in_draft = $definition[0]; - $webform_submission->setCreatedTime($definition[1] ? (REQUEST_TIME - ($purge_days + 1) * $days_to_seconds) : REQUEST_TIME); + $webform_submission->setCreatedTime($definition[1] ? ($request_time - ($purge_days + 1) * $days_to_seconds) : $request_time); $webform_submission->save(); } } @@ -91,11 +91,13 @@ public function testPurge($webform_purging, $webform_submissions_definition, $pu // Make sure nothing has been purged in the webform where purging is // disabled. $query = \Drupal::entityTypeManager()->getStorage('webform_submission')->getQuery(); + $query->accessCheck(FALSE); $query->condition('webform_id', $webform_no_purging->id()); $result = $query->execute(); $this->assertEquals(count($webform_submissions_definition), count($result), 'No purging is executed when webform not not set up to purge.'); $query = \Drupal::entityTypeManager()->getStorage('webform_submission')->getQuery(); + $query->accessCheck(FALSE); $query->condition('webform_id', $webform->id()); $result = []; foreach (\Drupal::entityTypeManager()->getStorage('webform_submission')->loadMultiple($query->execute()) as $submission) { diff --git a/web/modules/webform/tests/src/Unit/Access/WebformAccessCheckTest.php b/web/modules/webform/tests/src/Unit/Access/WebformAccessCheckTest.php deleted file mode 100644 index bf99006e0f..0000000000 --- a/web/modules/webform/tests/src/Unit/Access/WebformAccessCheckTest.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php - -namespace Drupal\Tests\webform\Unit\Access; - -use Drupal\Core\Access\AccessResult; -use Drupal\Tests\UnitTestCase; -use Drupal\webform\Access\WebformAccountAccess; -use Drupal\webform\Access\WebformSubmissionAccess; - -/** - * @coversDefaultClass \Drupal\webform\Access\WebformAccountAccess - * - * @group webform - */ -class WebformAccessCheckTest extends UnitTestCase { - - /** - * The tested access checker. - * - * @var \Drupal\user\Access\PermissionAccessCheck - */ - public $accessCheck; - - /** - * The dependency injection container. - * - * @var \Symfony\Component\DependencyInjection\ContainerBuilder - */ - protected $container; - - /** - * Tests the check admin access. - * - * @covers ::checkAdminAccess - */ - public function testCheckAdminAccess() { - $account = $this->getMock('Drupal\Core\Session\AccountInterface'); - - $admin_account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $admin_account->expects($this->any()) - ->method('hasPermission') - ->will($this->returnValueMap([ - ['administer webform', TRUE], - ['administer webform submission', TRUE], - ] - )); - - $submission_manager_account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $submission_manager_account->expects($this->any()) - ->method('hasPermission') - ->will($this->returnValueMap([ - ['access webform overview', TRUE], - ['view any webform submission', TRUE], - ] - )); - - $webform_node = $this->getMockBuilder('Drupal\node\NodeInterface') - ->disableOriginalConstructor() - ->getMock(); - $webform_node->expects($this->any()) - ->method('access') - ->will($this->returnValue(TRUE)); - $webform_node->expects($this->any()) - ->method('hasField') - ->will($this->returnValue(TRUE)); - $webform_node->webform = (object) ['entity' => TRUE]; - - $webform = $this->getMock('Drupal\webform\WebformInterface'); - - $email_webform = $this->getMock('Drupal\webform\WebformInterface'); - $handler = $this->getMock('\Drupal\webform\Plugin\WebformHandlerMessageInterface'); - $email_webform->expects($this->any()) - ->method('getHandlers') - ->will($this->returnValue([$handler])); - $email_webform->expects($this->any()) - ->method('access') - ->with('submission_update_any') - ->will($this->returnValue(TRUE)); - $email_webform->expects($this->any()) - ->method('hasMessageHandler') - ->will($this->returnValue(TRUE)); - - $webform_submission = $this->getMock('Drupal\webform\WebformSubmissionInterface'); - $webform_submission->expects($this->any()) - ->method('getWebform') - ->will($this->returnValue($webform)); - $email_webform_submission = $this->getMock('Drupal\webform\WebformSubmissionInterface'); - $email_webform_submission->expects($this->any()) - ->method('getWebform') - ->will($this->returnValue($email_webform)); - - // Check submission access. - $this->assertEquals(AccessResult::neutral(), WebformAccountAccess::checkAdminAccess($account)); - $this->assertEquals(AccessResult::allowed(), WebformAccountAccess::checkAdminAccess($admin_account)); - - // Check submission access. - $this->assertEquals(AccessResult::neutral(), WebformAccountAccess::checkSubmissionAccess($account)); - $this->assertEquals(AccessResult::allowed(), WebformAccountAccess::checkSubmissionAccess($submission_manager_account)); - - // Check overview access. - $this->assertEquals(AccessResult::neutral(), WebformAccountAccess::checkOverviewAccess($account)); - $this->assertEquals(AccessResult::allowed(), WebformAccountAccess::checkOverviewAccess($submission_manager_account)); - - // Check resend (email) message access. - $this->assertEquals(AccessResult::forbidden(), WebformSubmissionAccess::checkResendAccess($webform_submission, $account)); - $this->assertEquals(AccessResult::allowed(), WebformSubmissionAccess::checkResendAccess($email_webform_submission, $submission_manager_account)); - - // @todo Fix below access check which is looping through the node's fields. - // Check entity results access. - // $this->assertEquals(AccessResult::neutral(), WebformSourceEntityAccess::checkEntityResultsAccess($node, $account)); - // $this->assertEquals(AccessResult::allowed(), WebformSourceEntityAccess::checkEntityResultsAccess($webform_node, $submission_manager_account)); - } - -} diff --git a/web/modules/webform/tests/src/Unit/Access/WebformAccessTestBase.php b/web/modules/webform/tests/src/Unit/Access/WebformAccessTestBase.php new file mode 100644 index 0000000000..c02798a048 --- /dev/null +++ b/web/modules/webform/tests/src/Unit/Access/WebformAccessTestBase.php @@ -0,0 +1,65 @@ +<?php + +namespace Drupal\Tests\webform\Unit\Access; + +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Tests\UnitTestCase; + +/** + * Base class for test access checks. + */ +abstract class WebformAccessTestBase extends UnitTestCase { + + /** + * The test container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->container = new ContainerBuilder(); + \Drupal::setContainer($this->container); + + // Mock cache context manager and set container. + // @copied from \Drupal\Tests\Core\Access\AccessResultTest::setUp + $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); + $this->container->set('cache_contexts_manager', $cache_contexts_manager); + } + + /** + * Create a mock account with permissions. + * + * @param array $permissions + * An associative array of permissions and results. + * + * @return \Drupal\Core\Session\AccountInterface + * A mock account with ::hasPermission method. + */ + protected function mockAccount(array $permissions = []) { + // Convert permission to value map. + $value_map = []; + foreach ($permissions as $permission => $result) { + $value_map[] = [$permission, $result]; + } + + $account = $this->createMock('Drupal\Core\Session\AccountInterface'); + + $account->expects($this->any()) + ->method('hasPermission') + ->will($this->returnValueMap($value_map)); + + /** @var \Drupal\Core\Session\AccountInterface $account */ + return $account; + } + +} diff --git a/web/modules/webform/tests/src/Unit/Access/WebformAccountAccessTest.php b/web/modules/webform/tests/src/Unit/Access/WebformAccountAccessTest.php new file mode 100644 index 0000000000..99cf39c786 --- /dev/null +++ b/web/modules/webform/tests/src/Unit/Access/WebformAccountAccessTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\Tests\webform\Unit\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\webform\Access\WebformAccountAccess; + +/** + * @coversDefaultClass \Drupal\webform\Access\WebformAccountAccess + * + * @group webform + */ +class WebformAccountAccessTest extends WebformAccessTestBase { + + /** + * Tests the check webform account access. + * + * @covers ::checkAdminAccess + * @covers ::checkSubmissionAccess + * @covers ::checkOverviewAccess + */ + public function testWebformAccountAccess() { + // Mock anonymous account. + $anonymous_account = $this->mockAccount(); + + // Mock admin account. + $admin_account = $this->mockAccount([ + 'administer webform' => TRUE, + 'administer webform submission' => TRUE, + ]); + + // Mock submission account. + $submission_account = $this->mockAccount([ + 'access webform overview' => TRUE, + 'view any webform submission' => TRUE, + ]); + + /**************************************************************************/ + + // Check admin access. + $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), WebformAccountAccess::checkAdminAccess($anonymous_account)->setReason('')); + $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), WebformAccountAccess::checkAdminAccess($admin_account)); + + // Check submission access. + $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), WebformAccountAccess::checkSubmissionAccess($anonymous_account)->setReason('')); + $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), WebformAccountAccess::checkSubmissionAccess($submission_account)); + + // Check overview access. + $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), WebformAccountAccess::checkOverviewAccess($anonymous_account)->setReason('')); + $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), WebformAccountAccess::checkOverviewAccess($submission_account)); + } + +} diff --git a/web/modules/webform/tests/src/Unit/Access/WebformSourceEntityAccessTest.php b/web/modules/webform/tests/src/Unit/Access/WebformSourceEntityAccessTest.php new file mode 100644 index 0000000000..2f6ab1834b --- /dev/null +++ b/web/modules/webform/tests/src/Unit/Access/WebformSourceEntityAccessTest.php @@ -0,0 +1,68 @@ +<?php + +namespace Drupal\Tests\webform\Unit\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\webform\Access\WebformSourceEntityAccess; + +/** + * @coversDefaultClass \Drupal\webform\Access\WebformSourceEntityAccess + * + * @group webform + */ +class WebformSourceEntityAccessTest extends WebformAccessTestBase { + + /** + * Tests the check webform source entity access. + * + * @covers ::checkEntityResultsAccess + */ + public function testWebformSourceEntityAccess() { + // Mock anonymous account. + $anonymous_account = $this->mockAccount(); + + // Mock submission account. + $submission_account = $this->mockAccount([ + 'access webform overview' => TRUE, + 'view any webform submission' => TRUE, + ]); + + // Mock node. + $node = $this->getMockBuilder('Drupal\node\NodeInterface') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('access') + ->willReturn(AccessResult::neutral()); + + // Mock webform. + $webform = $this->createMock('Drupal\webform\WebformInterface'); + + // Mock webform node. + $webform_node = $this->getMockBuilder('Drupal\node\NodeInterface') + ->disableOriginalConstructor() + ->getMock(); + $webform_node->expects($this->any()) + ->method('access') + ->willReturn(AccessResult::allowed()); + + // Mock entity reference manager. + $entity_reference_manager = $this->getMockBuilder('Drupal\webform\WebformEntityReferenceManagerInterface') + ->disableOriginalConstructor() + ->getMock(); + $entity_reference_manager->expects($this->any()) + ->method('getWebform') + ->will($this->returnValueMap([ + [$node, NULL], + [$webform_node, $webform], + ])); + $this->container->set('webform.entity_reference_manager', $entity_reference_manager); + + /**************************************************************************/ + + // Check entity results access. + $this->assertEquals(AccessResult::neutral(), WebformSourceEntityAccess::checkEntityResultsAccess($node, $anonymous_account)); + $this->assertEquals(AccessResult::allowed(), WebformSourceEntityAccess::checkEntityResultsAccess($webform_node, $submission_account)); + } + +} diff --git a/web/modules/webform/tests/src/Unit/Access/WebformSubmissionAccessTest.php b/web/modules/webform/tests/src/Unit/Access/WebformSubmissionAccessTest.php new file mode 100644 index 0000000000..459afc2264 --- /dev/null +++ b/web/modules/webform/tests/src/Unit/Access/WebformSubmissionAccessTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Drupal\Tests\webform\Unit\Access; + +use Drupal\Core\Access\AccessResult; +use Drupal\webform\Access\WebformSubmissionAccess; + +/** + * @coversDefaultClass \Drupal\webform\Access\WebformSubmissionAccess + * + * @group webform + */ +class WebformSubmissionAccessTest extends WebformAccessTestBase { + + /** + * Tests the check webform submission access. + * + * @covers ::checkResendAccess + * @covers ::checkWizardPagesAccess + */ + public function testWebformSubmissionAccess() { + // Mock anonymous account. + $anonymous_account = $this->mockAccount(); + + // Mock submission account. + $submission_account = $this->mockAccount([ + 'access webform overview' => TRUE, + 'view any webform submission' => TRUE, + ]); + + // Mock webform. + $webform = $this->createMock('Drupal\webform\WebformInterface'); + + // Mock webform submission. + $webform_submission = $this->createMock('Drupal\webform\WebformSubmissionInterface'); + $webform_submission->expects($this->any()) + ->method('getWebform') + ->will($this->returnValue($webform)); + + // Mock message handler. + $message_handler = $this->createMock('\Drupal\webform\Plugin\WebformHandlerMessageInterface'); + + // Mock email webform. + $email_webform = $this->createMock('Drupal\webform\WebformInterface'); + $email_webform->expects($this->any()) + ->method('getHandlers') + ->will($this->returnValue([$message_handler])); + $email_webform->expects($this->any()) + ->method('access') + ->with('submission_update_any') + ->will($this->returnValue(TRUE)); + $email_webform->expects($this->any()) + ->method('hasMessageHandler') + ->will($this->returnValue(TRUE)); + + // Mock email webform submission. + $email_webform_submission = $this->createMock('Drupal\webform\WebformSubmissionInterface'); + $email_webform_submission->expects($this->any()) + ->method('getWebform') + ->will($this->returnValue($email_webform)); + + // Mock webform wizard. + $webform_wizard = $this->createMock('Drupal\webform\WebformInterface'); + $webform_wizard->expects($this->any()) + ->method('hasWizardPages') + ->will($this->returnValue(TRUE)); + + // Mock webform wizard submission. + $webform_wizard_submission = $this->createMock('Drupal\webform\WebformSubmissionInterface'); + $webform_wizard_submission->expects($this->any()) + ->method('getWebform') + ->will($this->returnValue($webform_wizard)); + + /**************************************************************************/ + + // Check resend (email) message access. + $this->assertEquals(AccessResult::forbidden(), WebformSubmissionAccess::checkResendAccess($webform_submission, $anonymous_account)); + $this->assertEquals(AccessResult::allowed(), WebformSubmissionAccess::checkResendAccess($email_webform_submission, $submission_account)); + + // Check wizard page access. + $this->assertEquals(AccessResult::neutral(), WebformSubmissionAccess::checkWizardPagesAccess($webform_submission)); + $this->assertEquals(AccessResult::allowed(), WebformSubmissionAccess::checkWizardPagesAccess($webform_wizard_submission)); + + } + +} diff --git a/web/modules/webform/tests/src/Unit/Plugin/Block/WebformBlockTest.php b/web/modules/webform/tests/src/Unit/Plugin/Block/WebformBlockTest.php index 798578ab16..57786ec8b4 100644 --- a/web/modules/webform/tests/src/Unit/Plugin/Block/WebformBlockTest.php +++ b/web/modules/webform/tests/src/Unit/Plugin/Block/WebformBlockTest.php @@ -12,6 +12,7 @@ use Drupal\webform\WebformInterface; use Drupal\webform\WebformTokenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; /** * Tests webform submission bulk form actions. @@ -26,6 +27,7 @@ class WebformBlockTest extends UnitTestCase { * Tests the dependencies of a webform block. */ public function testCalculateDependencies() { + // Create mock webform and webform block. $webform = $this->getMockBuilder(WebformInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -35,35 +37,7 @@ public function testCalculateDependencies() { ->willReturn('config'); $webform->method('getConfigDependencyName') ->willReturn('config.webform.' . $webform->id()); - - $entity_type_manager = $this->getMockBuilder(EntityTypeManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $storage = $this->getMockBuilder(EntityStorageInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $entity_type_manager->method('getStorage') - ->willReturnMap([ - ['webform', $storage], - ]); - - $storage->method('load') - ->willReturnMap([ - [$webform->id(), $webform], - ]); - - $token_manager = $this->getMockBuilder(WebformTokenManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $block = new WebformBlock([ - 'webform_id' => $webform->id(), - 'default_data' => [], - ], 'webform_block', [ - 'provider' => 'unit_test', - ], $entity_type_manager, $token_manager); + $block = $this->mockWebformBlock($webform); $dependencies = $block->calculateDependencies(); $expected = [ @@ -105,6 +79,7 @@ public function testBlockAccess() { $access_result->addCacheTags(['dummy_cache_tag']); $access_result->addCacheContexts($cache_contexts); + // Create mock webform and webform block. $webform = $this->getMockBuilder(WebformInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -114,6 +89,30 @@ public function testBlockAccess() { ->willReturnMap([ ['submission_create', $account, TRUE, $access_result], ]); + $block = $this->mockWebformBlock($webform); + + $result = $block->access($account, TRUE); + + // Make sure the block transparently follows the webform access logic. + $this->assertSame($access_result->isAllowed(), $result->isAllowed(), 'Block access yields the same result as the access of the webform.'); + $this->assertEquals($access_result->getCacheContexts(), $result->getCacheContexts(), 'Block access has the same cache contexts as the access of the webform.'); + $this->assertEquals($access_result->getCacheTags(), $result->getCacheTags(), 'Block access has the same cache tags as the access of the webform.'); + $this->assertEquals($access_result->getCacheMaxAge(), $result->getCacheMaxAge(), 'Block access has the same cache max age as the access of the webform.'); + } + + /** + * Create a mock webform block. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return \Drupal\webform\Plugin\Block\WebformBlock + * A mock webform block. + */ + protected function mockWebformBlock(WebformInterface $webform) { + $request_stack = $this->getMockBuilder(RequestStack::class) + ->disableOriginalConstructor() + ->getMock(); $entity_type_manager = $this->getMockBuilder(EntityTypeManagerInterface::class) ->disableOriginalConstructor() @@ -122,35 +121,27 @@ public function testBlockAccess() { $storage = $this->getMockBuilder(EntityStorageInterface::class) ->disableOriginalConstructor() ->getMock(); - - $entity_type_manager->method('getStorage') + $storage->method('load') ->willReturnMap([ - ['webform', $storage], + [$webform->id(), $webform], ]); - $storage->method('load') + $entity_type_manager->method('getStorage') ->willReturnMap([ - [$webform->id(), $webform], + ['webform', $storage], ]); $token_manager = $this->getMockBuilder(WebformTokenManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $block = new WebformBlock([ - 'webform_id' => $webform->id(), - 'default_data' => [], - ], 'webform_block', [ - 'provider' => 'unit_test', - ], $entity_type_manager, $token_manager); + $configuration = ['webform_id' => $webform->id()]; - $result = $block->access($account, TRUE); + $plugin_id = 'webform_block'; - // Make sure the block transparently follows the webform access logic. - $this->assertSame($access_result->isAllowed(), $result->isAllowed(), 'Block access yields the same result as the access of the webform.'); - $this->assertEquals($access_result->getCacheContexts(), $result->getCacheContexts(), 'Block access has the same cache contexts as the access of the webform.'); - $this->assertEquals($access_result->getCacheTags(), $result->getCacheTags(), 'Block access has the same cache tags as the access of the webform.'); - $this->assertEquals($access_result->getCacheMaxAge(), $result->getCacheMaxAge(), 'Block access has the same cache max age as the access of the webform.'); + $plugin_definition = ['provider' => 'unit_test']; + + return new WebformBlock($configuration, $plugin_id, $plugin_definition, $request_stack, $entity_type_manager, $token_manager); } } diff --git a/web/modules/webform/tests/src/Unit/Plugin/WebformSourceEntity/QueryStringWebformSourceEntityTest.php b/web/modules/webform/tests/src/Unit/Plugin/WebformSourceEntity/QueryStringWebformSourceEntityTest.php index e3528003c5..fceba7fd90 100644 --- a/web/modules/webform/tests/src/Unit/Plugin/WebformSourceEntity/QueryStringWebformSourceEntityTest.php +++ b/web/modules/webform/tests/src/Unit/Plugin/WebformSourceEntity/QueryStringWebformSourceEntityTest.php @@ -6,7 +6,6 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\Language; -use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\webform\Plugin\WebformSourceEntity\QueryStringWebformSourceEntity; @@ -28,22 +27,8 @@ class QueryStringWebformSourceEntityTest extends UnitTestCase { /** * Tests detection of source entity via query string. * - * @param bool $webform_in_route - * Whether webform should be included in route object. - * @param string $source_entity_type_in_query - * Source entity type to include into query string. - * @param bool $source_entity_view_access - * Whether 'view' access should be allowed in the source entity. - * @param bool $webform_prepopulate_source_entity - * Value for the setting 'form_prepopulate_source_entity' of the webform. - * @param bool $source_entity_references_webform - * Whether the source entity should reference webform. - * @param bool $source_entity_has_translation - * Whether the source entity should have a translation and (whenever - * $source_entity_references_webform is TRUE) refer the webform from that - * translation. - * @param string[] $ignored_types - * Array of entity types that may not be source. + * @param array $options + * see ::providerGetCurrentSourceEntity. * @param bool $expect_source_entity * Whether we expect the tested method to return the source entity. * @param string $assert_message @@ -53,136 +38,178 @@ class QueryStringWebformSourceEntityTest extends UnitTestCase { * * @dataProvider providerGetCurrentSourceEntity */ - public function testGetCurrentSourceEntity($webform_in_route, $source_entity_type_in_query, $source_entity_view_access, $webform_prepopulate_source_entity, $source_entity_references_webform, $source_entity_has_translation, array $ignored_types, $expect_source_entity, $assert_message = '') { - $source_entity_type = 'node'; - $source_entity_id = 1; + public function testGetCurrentSourceEntity(array $options, $expect_source_entity, $assert_message = '') { + $options += [ + // Value for the setting 'form_prepopulate_source_entity' of the webform. + 'webform_settings_prepopulate_source_entity' => TRUE, + + // Source entity type. + 'source_entity_type' => 'node', + // Source entity id. + 'source_entity_id' => 1, + // Access result return by source entity 'view' operation. + 'source_entity_view_access_result' => TRUE, + // Whether source entity has a populate webform field. + 'source_entity_has_webform_field' => TRUE, + // Whether the source entity has translation. + 'source_entity_has_translation' => TRUE, + + // Source entity type return by request query string. + 'request_query_source_entity_type' => 'node', + + // Whether webform should be included in route object. + 'route_match_get_parameter_webform' => TRUE, + + // Array of entity types that may not be source. + 'ignored_types' => [], + ]; + + /**************************************************************************/ + + $webform = $this->getMockWebform($options); + list($source_entity, $source_entity_translation) = $this->getMockSourceEntity($options, $webform); + + // Mock source entity storage. + $source_entity_storage = $this->getMockBuilder(EntityStorageInterface::class) + ->getMock(); + $source_entity_storage->method('load') + ->willReturnMap([ + [$options['source_entity_id'], $source_entity], + ]); + // Move entity type manager which returns the mock source entity storage. $entity_type_manager = $this->getMockBuilder(EntityTypeManagerInterface::class) ->disableOriginalConstructor() ->getMock(); + $entity_type_manager->method('hasDefinition') + ->willReturnMap([ + [$options['source_entity_type'], TRUE], + ]); + $entity_type_manager->method('getStorage') + ->willReturnMap([ + [$options['source_entity_type'], $source_entity_storage], + ]); + // Mock route match. $route_match = $this->getMockBuilder(RouteMatchInterface::class) ->disableOriginalConstructor() ->getMock(); + $route_match->method('getParameter') + ->willReturnMap([ + ['webform', $options['route_match_get_parameter_webform'] ? $webform : NULL], + ]); + // Mock request stack. $request_stack = $this->getMockBuilder(RequestStack::class) ->disableOriginalConstructor() ->getMock(); - + $request_stack->method('getCurrentRequest') + ->will($this->returnValue( + new Request([ + 'source_entity_type' => $options['request_query_source_entity_type'], + 'source_entity_id' => $options['source_entity_id'], + ]) + )); + + // Move entity reference manager. $webform_entity_reference_manager = $this->getMockBuilder(WebformEntityReferenceManagerInterface::class) ->disableOriginalConstructor() ->getMock(); + $webform_entity_reference_manager->method('getFieldNames') + ->willReturnMap([ + [$source_entity, ['webform_field_name']], + [$source_entity_translation, ['webform_field_name']], + ]); + // Mock language manager. $language_manager = $this->getMockBuilder(LanguageManagerInterface::class) ->disableOriginalConstructor() ->getMock(); + $language_manager->method('getCurrentLanguage') + ->willReturn(new Language(['id' => 'es'])); - $webform = $this->getMockBuilder(WebformInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $request = new Request([ - 'source_entity_type' => $source_entity_type_in_query, - 'source_entity_id' => $source_entity_id, - ]); + /**************************************************************************/ - $source_entity_storage = $this->getMockBuilder(EntityStorageInterface::class) - ->getMock(); + // Create QueryStringWebformSourceEntity plugin instance. + $plugin = new QueryStringWebformSourceEntity([], 'query_string', [], $entity_type_manager, $route_match, $request_stack, $language_manager, $webform_entity_reference_manager); - $source_entity = $this->getMockBuilder(ContentEntityInterface::class) - ->getMock(); + $output = $plugin->getSourceEntity($options['ignored_types']); + if ($expect_source_entity) { + $this->assertSame($options['source_entity_has_translation'] ? $source_entity_translation : $source_entity, $output, $assert_message); + } + else { + $this->assertNull($output, $assert_message); + } + } - $source_entity_translation = $this->getMockBuilder(ContentEntityInterface::class) + /** + * Get mock webform entity. + * + * @param array $options + * Mock webform options. + * + * @return \PHPUnit\Framework\MockObject\MockObject + * A mocked webform entity. + */ + protected function getMockWebform(array $options) { + $webform = $this->getMockBuilder(WebformInterface::class) + ->disableOriginalConstructor() ->getMock(); - - $route_match->method('getParameter') - ->will($this->returnValueMap([ - ['webform', $webform_in_route ? $webform : NULL], - ])); - - $request_stack->method('getCurrentRequest') - ->will($this->returnValue($request)); - - $entity_type_manager->method('hasDefinition') - ->willReturnMap([ - [$source_entity_type, TRUE], - ]); - - $entity_type_manager->method('getStorage') - ->willReturnMap([ - [$source_entity_type, $source_entity_storage], - ]); - - $source_entity_storage->method('load') - ->willReturnMap([ - [$source_entity_id, $source_entity], - ]); - - $source_entity_storage->method('load') - ->willReturnMap([ - [$source_entity_id, $source_entity], - ]); - - $source_entity->method('access') - ->willReturnMap([ - ['view', NULL, FALSE, $source_entity_view_access], - ]); - - $source_entity_translation->method('access') - ->willReturnMap([ - ['view', NULL, FALSE, $source_entity_view_access], - ]); - $webform->method('getSetting') ->willReturnMap([ - ['form_prepopulate_source_entity', FALSE, $webform_prepopulate_source_entity], + ['form_prepopulate_source_entity', FALSE, $options['webform_settings_prepopulate_source_entity']], ]); - $webform->method('id') ->willReturn('webform_id'); + return $webform; + } - $webform_entity_reference_manager->method('getFieldName') + /** + * Get mocked source entity. + * + * @param array $options + * Mock source entity options. + * @param \Drupal\webform\WebformInterface $webform + * A mocked webform. + * + * @return array + * An array containing a mocked source entity and its + * translated source entity. + */ + protected function getMockSourceEntity(array $options, WebformInterface $webform) { + // Mock source entity. + $source_entity = $this->getMockBuilder(ContentEntityInterface::class) + ->getMock(); + $source_entity->method('access') ->willReturnMap([ - [$source_entity, 'webform_field_name'], - [$source_entity_translation, 'webform_field_name'], + ['view', NULL, FALSE, $options['source_entity_view_access_result']], ]); - - $language_manager->method('getCurrentLanguage') - ->willReturn(new Language([ - 'id' => 'ua', - 'name' => 'Ukrainian', - 'direction' => LanguageInterface::DIRECTION_LTR, - 'weight' => 0, - 'locked' => FALSE, - ])); - - $source_entity->webform_field_name = (object) [ - 'target_id' => $source_entity_references_webform && !$source_entity_has_translation ? $webform->id() : 'other_webform', + $source_entity->webform_field_name = [ + (object) ['target_id' => $options['source_entity_has_webform_field'] && !$options['source_entity_has_translation'] ? $webform->id() : 'other_webform'], ]; - $source_entity_translation->webform_field_name = (object) [ - 'target_id' => $source_entity_references_webform && $source_entity_has_translation ? $webform->id() : 'other_webform', + // Mock source entity translation. + $source_entity_translation = $this->getMockBuilder(ContentEntityInterface::class) + ->getMock(); + $source_entity_translation->method('access') + ->willReturnMap([ + ['view', NULL, FALSE, $options['source_entity_view_access_result']], + ]); + $source_entity_translation->webform_field_name = [ + (object) ['target_id' => $options['source_entity_has_webform_field'] && $options['source_entity_has_translation'] ? $webform->id() : 'other_webform'], ]; + // Add translation to source entity. $source_entity->method('hasTranslation') ->willReturnMap([ - [$language_manager->getCurrentLanguage()->getId(), $source_entity_has_translation], + ['es', $options['source_entity_has_translation']], ]); - $source_entity->method('getTranslation') ->willReturnMap([ - [$language_manager->getCurrentLanguage()->getId(), $source_entity_translation], + ['es', $source_entity_translation], ]); - $plugin = new QueryStringWebformSourceEntity([], 'query_string', [], $entity_type_manager, $route_match, $request_stack, $language_manager, $webform_entity_reference_manager); - $output = $plugin->getSourceEntity($ignored_types); - - if ($expect_source_entity) { - $this->assertSame($source_entity_has_translation ? $source_entity_translation : $source_entity, $output, $assert_message); - } - else { - $this->assertNull($output, $assert_message); - } + return [$source_entity, $source_entity_translation]; } /** @@ -191,16 +218,85 @@ public function testGetCurrentSourceEntity($webform_in_route, $source_entity_typ * @see testGetCurrentSourceEntity() */ public function providerGetCurrentSourceEntity() { - $tests[] = [FALSE, 'node', TRUE, TRUE, TRUE, FALSE, [], FALSE, 'No webform in route']; - $tests[] = [TRUE, 'user', TRUE, TRUE, TRUE, FALSE, [], FALSE, 'Inexisting entity type in query string']; - $tests[] = [TRUE, 'node', FALSE, TRUE, TRUE, FALSE, [], FALSE, 'Source entity without "view" access']; - $tests[] = [TRUE, 'node', FALSE, TRUE, TRUE, TRUE, [], FALSE, 'Source entity translated without "view" access']; - $tests[] = [TRUE, 'node', TRUE, TRUE, TRUE, FALSE, [], TRUE, 'Prepopulating of webform source entity is allowed']; - $tests[] = [TRUE, 'node', TRUE, TRUE, TRUE, FALSE, ['node'], TRUE, '$ignored_types is not considered']; - $tests[] = [TRUE, 'node', TRUE, FALSE, TRUE, FALSE, [], TRUE, 'Source entity references webform']; - $tests[] = [TRUE, 'node', TRUE, FALSE, TRUE, TRUE, [], TRUE, 'Translation of source entity references webform']; - $tests[] = [TRUE, 'node', TRUE, FALSE, FALSE, FALSE, [], FALSE, 'Source entity does not reference webform']; - $tests[] = [TRUE, 'node', TRUE, FALSE, FALSE, TRUE, [], FALSE, 'Translation of source entity does not reference webform']; + $tests[] = [ + [ + 'source_entity_has_translation' => FALSE, + 'route_match_get_parameter_webform' => FALSE, + ], + FALSE, + 'No webform in route', + ]; + $tests[] = [ + [ + 'source_entity_has_translation' => FALSE, + 'request_query_source_entity_type' => 'user', + ], + FALSE, + 'Inexisting entity type in query string', + ]; + $tests[] = [ + [ + 'source_entity_view_access_result' => FALSE, + 'source_entity_has_translation' => FALSE, + ], + FALSE, + 'Source entity without "view" access', + ]; + $tests[] = [ + [ + 'source_entity_view_access_result' => FALSE, + ], + FALSE, + 'Source entity translated without "view" access', + ]; + $tests[] = [ + [ + 'source_entity_has_translation' => FALSE, + ], + TRUE, + 'Prepopulating of webform source entity is allowed', + ]; + $tests[] = [ + [ + 'source_entity_has_translation' => FALSE, + 'ignored_types' => ['node'], + ], + TRUE, + 'Ignored_types is not considered by query string plugin.', + ]; + $tests[] = [ + [ + 'webform_settings_prepopulate_source_entity' => FALSE, + 'source_entity_has_translation' => FALSE, + ], + TRUE, + 'Source entity references webform', + ]; + $tests[] = [ + [ + 'webform_settings_prepopulate_source_entity' => FALSE, + ], + TRUE, + 'Translation of source entity references webform', + ]; + $tests[] = [ + [ + 'webform_settings_prepopulate_source_entity' => FALSE, + 'source_entity_has_webform_field' => FALSE, + 'source_entity_has_translation' => FALSE, + ], + FALSE, + 'Source entity does not reference webform', + ]; + $tests[] = [ + [ + 'webform_settings_prepopulate_source_entity' => FALSE, + 'source_entity_has_webform_field' => FALSE, + ], + FALSE, + 'Translation of source entity does not reference webform', + ]; + return $tests; } diff --git a/web/modules/webform/tests/src/Unit/Plugin/views/field/WebformSubmissionBulkFormTest.php b/web/modules/webform/tests/src/Unit/Plugin/views/field/WebformSubmissionBulkFormTest.php index 391da65d10..0b62cfc147 100644 --- a/web/modules/webform/tests/src/Unit/Plugin/views/field/WebformSubmissionBulkFormTest.php +++ b/web/modules/webform/tests/src/Unit/Plugin/views/field/WebformSubmissionBulkFormTest.php @@ -32,31 +32,33 @@ public function testConstructor() { $actions = []; for ($i = 1; $i <= 2; $i++) { - $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface'); + $action = $this->createMock('\Drupal\system\ActionConfigEntityInterface'); $action->expects($this->any()) ->method('getType') ->will($this->returnValue('webform_submission')); $actions[$i] = $action; } - $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface'); + $action = $this->createMock('\Drupal\system\ActionConfigEntityInterface'); $action->expects($this->any()) ->method('getType') ->will($this->returnValue('user')); $actions[] = $action; - $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + $entity_storage = $this->createMock('Drupal\Core\Entity\EntityStorageInterface'); $entity_storage->expects($this->any()) ->method('loadMultiple') ->will($this->returnValue($actions)); - $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + $entity_manager = $this->createMock('Drupal\Core\Entity\EntityManagerInterface'); $entity_manager->expects($this->once()) ->method('getStorage') ->with('action') ->will($this->returnValue($entity_storage)); - $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $language_manager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface'); + + $messenger = $this->createMock('\Drupal\Core\Messenger\MessengerInterface'); $views_data = $this->getMockBuilder('Drupal\views\ViewsData') ->disableOriginalConstructor() @@ -70,7 +72,7 @@ public function testConstructor() { $container->set('string_translation', $this->getStringTranslationStub()); \Drupal::setContainer($container); - $storage = $this->getMock('Drupal\views\ViewEntityInterface'); + $storage = $this->createMock('Drupal\views\ViewEntityInterface'); $storage->expects($this->any()) ->method('get') ->with('base_table') @@ -88,7 +90,7 @@ public function testConstructor() { $definition['title'] = ''; $options = []; - $webform_submission_bulk_form = new WebformSubmissionBulkForm([], 'webform_submission_bulk_form', $definition, $entity_manager, $language_manager); + $webform_submission_bulk_form = new WebformSubmissionBulkForm([], 'webform_submission_bulk_form', $definition, $entity_manager, $language_manager, $messenger); $webform_submission_bulk_form->init($executable, $display, $options); $this->assertAttributeEquals(array_slice($actions, 0, -1, TRUE), 'actions', $webform_submission_bulk_form); diff --git a/web/modules/webform/tests/src/Unit/Utility/WebformDateHelperTest.php b/web/modules/webform/tests/src/Unit/Utility/WebformDateHelperTest.php deleted file mode 100644 index de1d1b0052..0000000000 --- a/web/modules/webform/tests/src/Unit/Utility/WebformDateHelperTest.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -namespace Drupal\Tests\webform\Unit\Utility; - -use Drupal\webform\Utility\WebformDateHelper; -use Drupal\Tests\UnitTestCase; - -/** - * Tests webform date helper utility. - * - * @group webform - * - * @coversDefaultClass \Drupal\webform\Utility\WebformDateHelper - */ -class WebformDateHelperTest extends UnitTestCase { - - /** - * Tests WebformDateHelper::isValidDateFormat(). - * - * @param string $time - * The date/time string to run through WebformDateHelper::isValidDateFormat(). - * @param string $format - * Format accepted by date(). - * @param string $expected - * The expected result from calling the function. - * - * @see WebformDateHelper::isValidDateFormat() - * - * @dataProvider providerisValidDateFormat - */ - public function testisValidDateFormat($time, $format, $expected) { - $result = WebformDateHelper::isValidDateFormat($time, $format); - $this->assertEquals($expected, $result); - } - - /** - * Data provider for testisValidDateFormat(). - * - * @see testisValidDateFormat() - */ - public function providerisValidDateFormat() { - $tests[] = ['2013-13-01', 'Y-m-d', FALSE]; - $tests[] = ['2013-13-01', 'Y-m-d', FALSE]; - $tests[] = ['20132-13-01', 'Y-m-d', FALSE]; - $tests[] = ['2013-11-32', 'Y-m-d', FALSE]; - $tests[] = ['2012-2-25', 'Y-m-d', FALSE]; - $tests[] = ['2013-12-01', 'Y-m-d', TRUE]; - $tests[] = ['1970-12-01', 'Y-m-d', TRUE]; - $tests[] = ['2012-02-29', 'Y-m-d', TRUE]; - $tests[] = ['2013-13-01', 'Y-m-d', FALSE]; - return $tests; - } - -} diff --git a/web/modules/webform/tests/src/Unit/Utility/WebformElementHelperTest.php b/web/modules/webform/tests/src/Unit/Utility/WebformElementHelperTest.php index 90cddfce27..e35ff4175e 100644 --- a/web/modules/webform/tests/src/Unit/Utility/WebformElementHelperTest.php +++ b/web/modules/webform/tests/src/Unit/Utility/WebformElementHelperTest.php @@ -44,7 +44,6 @@ public function providerIsTitleDisplayed() { $tests[] = [['#title' => ''], FALSE]; $tests[] = [['#title' => NULL], FALSE]; $tests[] = [['#title' => 'Test', '#title_display' => 'invisible'], FALSE]; - $tests[] = [['#title' => 'Test', '#title_display' => 'attribute'], FALSE]; return $tests; } diff --git a/web/modules/webform/tests/src/Unit/Utility/WebformFormHelperTest.php b/web/modules/webform/tests/src/Unit/Utility/WebformFormHelperTest.php index 2e9e5a7b6b..4c4a6dd0e1 100644 --- a/web/modules/webform/tests/src/Unit/Utility/WebformFormHelperTest.php +++ b/web/modules/webform/tests/src/Unit/Utility/WebformFormHelperTest.php @@ -59,10 +59,10 @@ public function testFlattenElements() { ], ], ]; - $flattenend_elements = WebformFormHelper::flattenElements($elements); + $flattened_elements = WebformFormHelper::flattenElements($elements); // Check flattened elements. - $this->assertEquals($flattenend_elements, [ + $this->assertEquals($flattened_elements, [ 'one' => [ '#title' => 'one', 'two' => [ @@ -79,7 +79,7 @@ public function testFlattenElements() { $elements['one']['two']['#title'] .= '-UPDATED'; $elements['one']['two']['#type'] = 'textfield'; - $this->assertEquals($flattenend_elements, [ + $this->assertEquals($flattened_elements, [ 'one' => [ '#title' => 'one-UPDATED', 'two' => [ @@ -105,8 +105,8 @@ public function testFlattenElements() { '#title' => 'two-SECOND', ], ]; - $flattenend_elements = WebformFormHelper::flattenElements($elements); - $this->assertEquals($flattenend_elements, [ + $flattened_elements = WebformFormHelper::flattenElements($elements); + $this->assertEquals($flattened_elements, [ 'one' => [ '#title' => 'one', 'two' => [ @@ -128,7 +128,7 @@ public function testFlattenElements() { $elements['one']['two']['#title'] .= '-UPDATED'; $elements['one']['two']['#type'] = 'textfield'; - $this->assertEquals($flattenend_elements, [ + $this->assertEquals($flattened_elements, [ 'one' => [ '#title' => 'one-UPDATED', 'two' => [ diff --git a/web/modules/webform/tests/src/Unit/Utility/WebformObjectHelperTest.php b/web/modules/webform/tests/src/Unit/Utility/WebformObjectHelperTest.php new file mode 100644 index 0000000000..8bf3a389e8 --- /dev/null +++ b/web/modules/webform/tests/src/Unit/Utility/WebformObjectHelperTest.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\Tests\webform\Unit\Utility; + +use Drupal\webform\Utility\WebformObjectHelper; +use Drupal\Tests\UnitTestCase; + +/** + * Tests webform object utility. + * + * @group webform + * + * @coversDefaultClass \Drupal\webform\Utility\WebformObjectHelper + */ +class WebformObjectHelperTest extends UnitTestCase { + + /** + * Tests sorting object by properties. + * + * @param object $object + * The object to run through WebformObjectHelper::sortByProperty(). + * @param array $expected + * The expected result from calling the function. + * + * @see WebformObjectHelper::sortByProperty() + * + * @dataProvider providerSortByProperty + */ + public function testSortByProperty($object, array $expected) { + $result = (array) WebformObjectHelper::sortByProperty($object); + $this->assertEquals( + implode('|', array_keys($expected)), + implode('|', array_keys($result)) + ); + } + + /** + * Data provider for testSortByProperty(). + * + * @see testSortByProperty() + */ + public function providerSortByProperty() { + $object = new \stdClass(); + $object->c = 'c'; + $object->a = 'a'; + $object->b = 'b'; + $tests[] = [$object, ['a' => 'a', 'b' => 'b', 'c' => 'c']]; + + $object = new \stdClass(); + $object->c = 'c'; + $object->b = 'b'; + $object->a = 'a'; + $tests[] = [$object, ['a' => 'a', 'b' => 'b', 'c' => 'c']]; + + $object = new \stdClass(); + $object->b = 'b'; + $object->a = 'a'; + $object->_ = '_'; + $tests[] = [$object, ['_' => '_', 'a' => 'a', 'b' => 'b']]; + + return $tests; + } + +} diff --git a/web/modules/webform/tests/src/Unit/WebformEntityAccessControlHandlerTest.php b/web/modules/webform/tests/src/Unit/WebformEntityAccessControlHandlerTest.php index 07cbc726cd..eac7c80a9f 100644 --- a/web/modules/webform/tests/src/Unit/WebformEntityAccessControlHandlerTest.php +++ b/web/modules/webform/tests/src/Unit/WebformEntityAccessControlHandlerTest.php @@ -4,17 +4,18 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\Config\Entity\ConfigEntityType; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Tests\UnitTestCase; +use Drupal\webform\WebformAccessRulesManagerInterface; use Drupal\webform\Plugin\WebformSourceEntityManagerInterface; use Drupal\webform\WebformEntityAccessControlHandler; use Drupal\webform\WebformInterface; use Drupal\webform\WebformSubmissionInterface; use Drupal\webform\WebformSubmissionStorageInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -27,33 +28,43 @@ */ class WebformEntityAccessControlHandlerTest extends UnitTestCase { + use StringTranslationTrait; + + /** + * The test container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->container = new ContainerBuilder(); + \Drupal::setContainer($this->container); + + // Mock cache context manager and set container. + // @copied from \Drupal\Tests\Core\Access\AccessResultTest::setUp + $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') + ->disableOriginalConstructor() + ->getMock(); + + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); + $this->container->set('cache_contexts_manager', $cache_contexts_manager); + } + /** * Tests the access logic. * * @param string $operation * Operation to request from ::checkAccess() method. - * @param array $permissions - * Array of permissions to assign to a mocked account. - * @param array $check_access_rules - * Array of access rules that should yield 'allowed' when the mocked webform - * is requested ::checkAccessRules(). * @param array $options - * Array of extra options. Allowed key-value pairs are: - * - is_owner: (bool) Whether the mocked user should be owner of the - * webform. Defaults to FALSE. - * - is_template: (bool) Whether the mocked webform should be a template. - * Defaults to FALSE. - * - is_open: (bool) Whether the mocked webform should be open. - * Defaults to TRUE. - * - has_token: (bool) Whether the mocked webform submission should - * successfully load through token in query string. Defaults to FALSE. + * Array of extra options. * @param array $expected - * Expected data from the tested class. It should have the following - * structure: - * - is_allowed: (bool) Whether ::isAllowed() on the return should yield - * TRUE. - * - cache_tags: (array) Cache tags of the return. Defaults to []. - * - cache_contexts: (array) Cache contexts of the return. Defaults to []. + * Expected data from the tested class. * @param string $assert_message * Assertion message to use for this test case. * @@ -61,49 +72,59 @@ class WebformEntityAccessControlHandlerTest extends UnitTestCase { * * @dataProvider providerCheckAccess */ - public function testCheckAccess($operation, array $permissions, array $check_access_rules, array $options, array $expected, $assert_message = '') { + public function testCheckAccess($operation, array $options, array $expected, $assert_message = '') { + // Set $options default value. $options += [ - 'is_owner' => FALSE, - 'is_template' => FALSE, - 'is_open' => TRUE, - 'has_token' => FALSE, + // What is the request path. + 'request_path' => '', + // What is the request format. + 'request_format' => 'html', + // Array of permissions to assign to a mocked account. + 'permissions' => [], + // Array of access rules that should yield 'allowed' when the mocked + // access rules manager is requested ::checkWebformAccess() + // or ::checkWebformSubmissionAccess(). + 'access_rules' => [], + // Whether the mocked user should be owner of the webform. + 'account_is_webform_owner' => FALSE, + // Whether the mocked webform should be a template. + 'webform_is_template' => FALSE, + // Whether the mocked webform should be open. + 'webform_is_open' => TRUE, + // Whether the mocked webform submission should successfully + // load through token in query string. Defaults to FALSE. + 'submission_load_from_token' => FALSE, ]; + + // Set $expected default value. $expected += [ - 'cache_tags' => [], - 'cache_contexts' => [], + // Whether ::isAllowed() on the return should yield TRUE. + 'access_result_is_allowed' => TRUE, + // Cache tags of the return. + 'access_result_cache_tags' => [], + // Cache contexts of the return. + 'access_result_cache_contexts' => [], ]; - $cache_contexts_manager = $this->getMockBuilder(CacheContextsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $cache_contexts_manager->method('assertValidTokens') - ->willReturn(TRUE); + /**************************************************************************/ - $container = $this->getMockBuilder(ContainerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $container->method('get') - ->willReturnMap([ - ['cache_contexts_manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $cache_contexts_manager], - ]); + $token = $this->randomMachineName(); - \Drupal::setContainer($container); - - $entity_type = new ConfigEntityType([ - 'id' => 'webform', - ]); + // Mock entity type. + $entity_type = new ConfigEntityType(['id' => 'webform']); + // Mock request stack. $request_stack = $this->getMockBuilder(RequestStack::class) ->disableOriginalConstructor() ->getMock(); $request_stack->method('getCurrentRequest') - ->willReturn(new Request([ - 'token' => $this->randomMachineName(), - ])); + ->willReturn(new Request(['token' => $token], [], ['_format' => $options['request_format']])); + // Mock webform submission storage. $webform_submission_storage = $this->getMockBuilder(WebformSubmissionStorageInterface::class) ->getMock(); + // Mock entity type manager. $entity_type_manager = $this->getMockBuilder(EntityTypeManagerInterface::class) ->getMock(); $entity_type_manager->method('getStorage') @@ -111,33 +132,50 @@ public function testCheckAccess($operation, array $permissions, array $check_acc ['webform_submission', $webform_submission_storage], ]); + // Mock webform source entity manager. $webform_source_entity_manager = $this->getMockBuilder(WebformSourceEntityManagerInterface::class) ->getMock(); $webform_source_entity_manager->method('getSourceEntity') ->willReturn(NULL); - $access_handler = new WebformEntityAccessControlHandler($entity_type, $request_stack, $entity_type_manager, $webform_source_entity_manager); - + // Mock account. + $permissions = $options['permissions']; + $account_id = 2; $account = $this->getMockBuilder(AccountInterface::class) ->getMock(); $account->method('hasPermission') ->willReturnCallback(function ($permission) use ($permissions) { return in_array($permission, $permissions); }); + $account->method('id') + ->willReturn($options['account_is_webform_owner'] ? $account_id : $account_id + 1); + $account->method('isAuthenticated') + ->willReturn($account->id() > 0); + // Mock webform. $webform = $this->getMockBuilder(WebformInterface::class) ->getMock(); $webform->method('getOwnerId') - ->willReturn(2); + ->willReturn($account_id); $webform->method('isTemplate') - ->willReturn($options['is_template']); + ->willReturn($options['webform_is_template']); $webform->method('isOpen') - ->willReturn($options['is_open']); + ->willReturn($options['webform_is_open']); $webform->method('access') ->willReturnMap([ - ['create', $account, TRUE, AccessResult::forbidden()], + ['create', $account, TRUE, AccessResult::allowed()], ]); + $webform->method('getSetting')->willReturnMap([ + ['page', FALSE, TRUE], + ]); + $webform->method('getCacheMaxAge') + ->willReturn(Cache::PERMANENT); + $webform->method('getCacheContexts') + ->willReturn(['webform_cache_context']); + $webform->method('getCacheTags') + ->willReturn(['webform_cache_tag']); + // Mock webform submissions. $webform_submission = $this->getMockBuilder(WebformSubmissionInterface::class) ->getMock(); $webform_submission->method('getCacheContexts') @@ -146,35 +184,41 @@ public function testCheckAccess($operation, array $permissions, array $check_acc ->willReturn(['webform_submission_cache_tag']); $webform_submission->method('getCacheMaxAge') ->willReturn(Cache::PERMANENT); + $webform_submission->method('getWebform') + ->willReturn($webform); $webform_submission_storage->method('loadFromToken') ->willReturnMap([ - [$request_stack->getCurrentRequest()->query->get('token'), $webform, $webform_source_entity_manager->getSourceEntity(['webform']), NULL, ($options['has_token'] ? $webform_submission : NULL)], + [$token, $webform, NULL, NULL, ($options['submission_load_from_token'] ? $webform_submission : NULL)], ]); - $check_access_rules_map = []; - foreach (['administer', 'page', 'view_any', 'view_own'] as $v) { - $check_access_rules_map[] = [$v, $account, NULL, AccessResult::allowedIf(in_array($v, $check_access_rules))->addCacheContexts(['check_access_rules_cache_context'])->addCacheTags(['check_access_rules_cache_tag'])]; - } - $webform->method('checkAccessRules') - ->willReturnMap($check_access_rules_map); - $webform->method('getCacheMaxAge') - ->willReturn(Cache::PERMANENT); - $webform->method('getCacheContexts') - ->willReturn(['webform_cache_context']); - $webform->method('getCacheTags') - ->willReturn(['webform_cache_tag']); - - $account->method('id') - ->willReturn($options['is_owner'] ? $webform->getOwnerId() : $webform->getOwnerId() + 1); - $account->method('isAuthenticated') - ->willReturn($account->id() > 0); - + // Mock access rules manager. + $access_rules_manager = $this->getMockBuilder(WebformAccessRulesManagerInterface::class) + ->getMock(); + $access_rules_manager->method('checkWebformAccess') + ->will( + $this->returnCallback( + function ($operation, AccountInterface $account, WebformInterface $webform) use ($options) { + $condition = in_array($operation, $options['access_rules']) || in_array($operation . '_any', $options['access_rules']); + return AccessResult::allowedIf($condition) + ->addCacheContexts(['access_rules_cache_context']) + ->addCacheTags(['access_rules_cache_tag']); + } + ) + ); + + /**************************************************************************/ + + // Create webform access control handler. + $access_handler = new WebformEntityAccessControlHandler($entity_type, $request_stack, $entity_type_manager, $webform_source_entity_manager, $access_rules_manager); + + // Check access. $access_result = $access_handler->checkAccess($webform, $operation, $account); - $this->assertEquals($expected['is_allowed'], $access_result->isAllowed(), $assert_message); + // Check expected results. + $this->assertEquals($expected['access_result_is_allowed'], $access_result->isAllowed(), $assert_message); $this->assertEquals(Cache::PERMANENT, $access_result->getCacheMaxAge(), $assert_message . ': cache max age'); - $this->assertArrayEquals($expected['cache_contexts'], $access_result->getCacheContexts(), $assert_message . ': cache contexts'); - $this->assertArrayEquals($expected['cache_tags'], $access_result->getCacheTags(), $assert_message . ': cache tags'); + $this->assertArrayEquals($expected['access_result_cache_contexts'], $access_result->getCacheContexts(), $assert_message . ': cache contexts'); + $this->assertArrayEquals($expected['access_result_cache_tags'], $access_result->getCacheTags(), $assert_message . ': cache tags'); } /** @@ -183,164 +227,624 @@ public function testCheckAccess($operation, array $permissions, array $check_acc * @see testCheckAccess() */ public function providerCheckAccess() { - $tests[] = ['view', [], [], [], ['is_allowed' => TRUE], 'View operation']; + $tests = []; + + /**************************************************************************/ + // The "view" HTML operation. + /**************************************************************************/ + + $tests[] = [ + 'view', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context', 'request_format', 'url.path'], + ], + 'View when nobody', + ]; + + $tests[] = [ + 'view', + [ + 'permissions' => ['administer webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => [], + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'View when has "administer webform" permission', + ]; + $tests[] = [ + 'view', + [ + 'access_rules' => ['administer'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'View when has "administer" access rule', + ]; + + /**************************************************************************/ + // The "view" configuration operation. + /**************************************************************************/ + + $tests[] = [ + 'view', + [ + 'permissions' => ['access any webform configuration'], + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context', 'request_format', 'url.path'], + ], + 'View when has "access any webform configuration" permission and request form is HTML', + ]; + + $tests[] = [ + 'view', + [ + 'request_format' => 'not_html', + 'permissions' => ['access any webform configuration'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['request_format', 'url.path', 'user', 'user.permissions', 'webform_cache_context'], + ], + 'View when has "access any webform configuration" permission and request form is NOT HTML', + ]; + + $tests[] = [ + 'view', + [ + 'permissions' => ['access own webform configuration'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context', 'request_format', 'url.path'], + ], + 'View when has "access own webform configuration" permission and is not owner and request form is HTML', + ]; + + $tests[] = [ + 'view', + [ + 'request_format' => 'not_html', + 'permissions' => ['access own webform configuration'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context', 'request_format', 'url.path'], + ], + 'View when has "access own webform configuration" permission and is not owner and request form is NOT HTML', + ]; + + $tests[] = [ + 'view', + [ + 'permissions' => ['access own webform configuration'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context', 'request_format', 'url.path'], + ], + 'View when has "access own webform configuration" permission and is owner and request form is HTML', + ]; + + $tests[] = [ + 'view', + [ + 'request_format' => 'not_html', + 'permissions' => ['access own webform configuration'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['request_format', 'url.path', 'user', 'user.permissions', 'webform_cache_context'], + ], + 'View when has "access own webform configuration" permission and is owner and request form is NOT HTML', + ]; + + /**************************************************************************/ + // The "test" operation. + /**************************************************************************/ + + $tests[] = [ + 'test', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Test when nobody', + ]; + + $tests[] = [ + 'test', + [ + 'permissions' => ['administer webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => [], + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Test when has "administer webform" permission', + ]; + + $tests[] = [ + 'test', + [ + 'access_rules' => ['administer'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Test when has "administer" access rule', + ]; + + $tests[] = [ + 'test', + [ + 'permissions' => ['edit any webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Test when has "edit any webform" permission', + ]; + + $tests[] = [ + 'test', + [ + 'permissions' => ['edit own webform'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Test when has "edit own webform" permission and is not owner', + ]; + + $tests[] = [ + 'test', + [ + 'permissions' => ['edit own webform'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Test when has "edit own webform" permission and is owner', + ]; + + /**************************************************************************/ // The "update" operation. - $tests[] = ['update', [], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Update when nobody']; - - $tests[] = ['update', [], ['administer'], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Update when admin of the webform']; - - $tests[] = ['update', ['edit any webform'], [], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Update when has "edit any webform" permission']; - - $tests[] = ['update', ['edit own webform'], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Update when has "edit own webform" permission but is not owner']; - - $tests[] = ['update', ['edit own webform'], [], ['is_owner' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Update when has "edit own webform" permission and is owner']; + /**************************************************************************/ + + $tests[] = [ + 'update', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Update when nobody', + ]; + + $tests[] = [ + 'update', + [ + 'permissions' => ['administer webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => [], + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Update when has "administer webform" permission', + ]; + + $tests[] = [ + 'update', [ + 'access_rules' => ['administer'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Update when has "administer" access rule', + ]; + + $tests[] = [ + 'update', + [ + 'permissions' => ['edit any webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Update when has "edit any webform" permission', + ]; + $tests[] = [ + 'update', + [ + 'permission' => ['edit own webform'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Update when has "edit own webform" permission and is not owner', + ]; + + $tests[] = [ + 'update', [ + 'permissions' => ['edit own webform'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Update when has "edit own webform" permission and is owner', + ]; + + /**************************************************************************/ // The "duplicate" operation. - $tests[] = ['duplicate', [], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Duplicate when nobody']; - - $tests[] = ['duplicate', [], ['administer'], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Duplicate when admin of the webform']; - - $tests[] = ['duplicate', ['create webform'], [], ['is_template' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Duplicate when has "create webform" and the webform is a template']; - - $tests[] = ['duplicate', ['create webform', 'edit any webform'], [], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Duplicate when has "create webform" and "edit any webform"']; - - $tests[] = ['duplicate', ['create webform', 'edit own webform'], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Duplicate when has "create webform" and "edit own webform" but is not owner']; - - $tests[] = ['duplicate', ['create webform', 'edit own webform'], [], ['is_owner' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Duplicate when has "create webform" and "edit own webform" and is owner']; + /**************************************************************************/ + + $tests[] = [ + 'duplicate', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Duplicate when nobody', + ]; + + $tests[] = [ + 'duplicate', + [ + 'permissions' => ['administer webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => [], + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Duplicate when has "administer webform" permission', + ]; + + $tests[] = [ + 'duplicate', + [ + 'access_rules' => ['administer'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Duplicate when has "administer" access rule', + ]; + + $tests[] = [ + 'duplicate', + [ + 'permissions' => ['create webform'], + 'webform_is_template' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Duplicate when has "create webform" permission and webform is not template', + ]; + + $tests[] = [ + 'duplicate', + [ + 'permissions' => ['create webform', 'edit any webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Duplicate when has "create webform" and "edit any webform" permissions', + ]; + + $tests[] = [ + 'duplicate', [ + 'permisssions' => ['create webform', 'edit own webform'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Duplicate when has "create webform" and "edit own webform" permissions and is not owner', + ]; + + $tests[] = [ + 'duplicate', + [ + 'permissions' => ['create webform', 'edit own webform'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Duplicate when has "create webform" and "edit own webform" permissions and is owner', + ]; + /**************************************************************************/ // The "delete" operation. - $tests[] = ['delete', [], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Delete when nobody']; - - $tests[] = ['delete', [], ['administer'], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Delete when admin of the webform']; - $tests[] = ['delete', ['delete any webform'], [], [], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Delete when has "delete any webform"']; - - $tests[] = ['delete', ['delete own webform'], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Delete when has "delete own webform" but is not owner']; - - $tests[] = ['delete', ['delete own webform'], [], ['is_owner' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'user', 'user.permissions', 'webform_cache_context'], - ], 'Delete when has "delete own webform" and is owner']; - - $tests[] = ['submission_view_any', [], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Submission view any when nobody']; - - $tests[] = ['submission_view_any', ['view any webform submission'], [], [], [ - 'is_allowed' => TRUE, - 'cache_contexts' => ['user.permissions'], - ], 'Submission view any when has "view any webform submission" permission']; - - $tests[] = ['submission_view_any', ['view own webform submission'], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Submission view any when has "view own webform submission" permission but is not owner']; - - $tests[] = ['submission_view_any', ['view own webform submission'], [], ['is_owner' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['user', 'webform_cache_context'], - ], 'Submission view any when has "view own webform submission" permission and is owner']; - - $tests[] = ['submission_view_own', [], [], [], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Submission view own when nobody']; - - $tests[] = ['submission_view_own', ['view own webform submission'], [], [], [ - 'is_allowed' => TRUE, - 'cache_contexts' => ['user.permissions'], - ], 'Submission view own when has "view own webform submission" permission']; + /**************************************************************************/ + + $tests[] = [ + 'delete', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Delete when nobody', + ]; + + $tests[] = [ + 'delete', + [ + 'permissions' => ['administer webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => [], + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Delete when has "administer webform" permission', + ]; + + $tests[] = [ + 'delete', + [ + 'access_rules' => ['administer'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Delete when has "administer" access rule', + ]; + + $tests[] = [ + 'delete', + [ + 'permissions' => ['delete any webform'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Delete when has "delete any webform" permission', + ]; + + $tests[] = [ + 'delete', + [ + 'permissions' => ['delete own webform'], + 'account_is_webform_owner' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Delete when has "delete own webform" permission and is not owner', + ]; + + $tests[] = [ + 'delete', + [ + 'permissions' => ['delete own webform'], + 'account_is_webform_owner' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user', 'user.permissions', 'webform_cache_context'], + ], + 'Delete when has "delete own webform" permission and is owner', + ]; + + /**************************************************************************/ + // The "purge" operation. + /**************************************************************************/ + + $tests[] = [ + 'submission_purge', + [ + 'access_rules' => ['purge_any'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Purge when has "purge_any" access rule', + ]; + + $tests[] = [ + 'submission_purge_any', + [ + 'access_rules' => ['purge_any'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Purge when has "purge_any" access rule', + ]; + + /**************************************************************************/ + // The "view" operation. + /**************************************************************************/ + + $tests[] = [ + 'submission_view_any', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Submission view any when nobody', + ]; + + $tests[] = [ + 'submission_view_any', + [ + 'permissions' => ['view any webform submission'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Submission view any when has "view any webform submission" permission', + ]; + + $tests[] = [ + 'submission_view_any', + [ + 'permissions' => ['view own webform submission'], + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Submission view any when has "view own webform submission" permission but is not owner', + ]; + + $tests[] = [ + 'submission_view_own', + [], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Submission view own when nobody', + ]; + + $tests[] = [ + 'submission_view_own', + [ + 'permissions' => ['view own webform submission'], + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_contexts' => ['user.permissions'], + ], + 'Submission view own when has "view own webform submission" permission', + ]; // The "submission_page" operation. - $tests[] = ['submission_page', [], [], ['is_open' => FALSE], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Submission page when nobody']; - - $tests[] = ['submission_page', [], [], ['has_token' => TRUE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['webform_cache_tag', 'webform_submission_cache_tag'], - 'cache_contexts' => ['url', 'webform_cache_context', 'webform_submission_cache_context'], - ], 'Submission page when accessible through token']; - - $tests[] = ['submission_page', [], [], ['is_template' => TRUE, 'is_open' => FALSE], [ - 'is_allowed' => FALSE, - 'cache_tags' => ['webform_cache_tag'], - 'cache_contexts' => ['webform_cache_context'], - ], 'Submission page when the webform is template without create access']; - - $tests[] = ['submission_page', [], ['page'], ['is_open' => FALSE], [ - 'is_allowed' => TRUE, - 'cache_tags' => ['check_access_rules_cache_tag', 'webform_cache_tag'], - 'cache_contexts' => ['check_access_rules_cache_context', 'webform_cache_context'], - ], 'Submission page when the webform allows "page"']; + $tests[] = [ + 'submission_page', + [ + 'webform_is_open' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Submission page when nobody', + ]; + + $tests[] = [ + 'submission_page', + [ + 'submission_load_from_token' => TRUE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['webform_cache_tag', 'webform_submission_cache_tag'], + 'access_result_cache_contexts' => ['url', 'user.permissions', 'webform_cache_context', 'webform_submission_cache_context'], + ], + 'Submission page when accessible through token', + ]; + + $tests[] = [ + 'submission_page', + [ + 'webform_is_template' => TRUE, + 'webform_is_open' => FALSE, + ], + [ + 'access_result_is_allowed' => FALSE, + 'access_result_cache_tags' => ['webform_cache_tag'], + 'access_result_cache_contexts' => ['user.permissions', 'webform_cache_context'], + ], + 'Submission page when the webform is template without create access', + ]; + + $tests[] = [ + 'submission_page', + [ + 'access_rules' => ['create'], + 'webform_is_open' => FALSE, + ], + [ + 'access_result_is_allowed' => TRUE, + 'access_result_cache_tags' => ['access_rules_cache_tag'], + 'access_result_cache_contexts' => ['access_rules_cache_context'], + ], + 'Submission page when the webform allows "page"', + ]; return $tests; } diff --git a/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html--contact--email--email-confirmation.html.twig b/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html--contact--email--email-confirmation.html.twig index f035c2e306..9b6c7b73b5 100644 --- a/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html--contact--email--email-confirmation.html.twig +++ b/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html--contact--email--email-confirmation.html.twig @@ -4,7 +4,7 @@ * Default theme implementation for webform email wrapper template as HTML. * * Available variables: - * - message: The email message which contains to, from, subject, message, etc... + * - message: The email message which contains to, from, subject, message, etc… * - webform_submission: The webform submission. * - handler: The webform handler. * diff --git a/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html.html.twig b/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html.html.twig index 3e4139da0f..773fcb8932 100644 --- a/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html.html.twig +++ b/web/modules/webform/tests/themes/webform_test_bartik/templates/webform/webform-email-message-html.html.twig @@ -4,7 +4,7 @@ * Default theme implementation for webform email wrapper template as HTML. * * Available variables: - * - message: The email message which contains to, from, subject, message, etc... + * - message: The email message which contains to, from, subject, message, etc… * - webform_submission: The webform submission. * - handler: The webform handler. * diff --git a/web/modules/webform/tests/themes/webform_test_bartik/webform_test_bartik.info.yml b/web/modules/webform/tests/themes/webform_test_bartik/webform_test_bartik.info.yml index 3ac295b470..decb95e2a9 100644 --- a/web/modules/webform/tests/themes/webform_test_bartik/webform_test_bartik.info.yml +++ b/web/modules/webform/tests/themes/webform_test_bartik/webform_test_bartik.info.yml @@ -5,8 +5,8 @@ name: 'Webform Test Bartik' description: 'Test Webform Bartik integration.' package: 'Webform Test' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/third_party_settings/webform.antibot.inc b/web/modules/webform/third_party_settings/webform.antibot.inc index 2cb5cbf2b5..040085561d 100644 --- a/web/modules/webform/third_party_settings/webform.antibot.inc +++ b/web/modules/webform/third_party_settings/webform.antibot.inc @@ -137,7 +137,7 @@ function antibot_webform_third_party_settings_form_alter(&$form, FormStateInterf */ function antibot_webform_submission_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Only add an Antibot when a webform is initially load. - // After a webform is submitted, via a multistep webform and/or saving a draft, + // After a webform is submitted, via a multi-step webform and/or saving a draft, // we can skip adding an Antibot. if ($form_state->isSubmitted()) { return; diff --git a/web/modules/webform/third_party_settings/webform.captcha.inc b/web/modules/webform/third_party_settings/webform.captcha.inc new file mode 100644 index 0000000000..f2b6ff388e --- /dev/null +++ b/web/modules/webform/third_party_settings/webform.captcha.inc @@ -0,0 +1,148 @@ +<?php + +/** + * @file + * Integrates third party settings on the CAPTCHA module's behalf. + */ + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\webform\Utility\WebformElementHelper; + +/** + * Implements hook_webform_admin_third_party_settings_form_alter(). + */ +function captcha_webform_admin_third_party_settings_form_alter(&$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformThirdPartySettingsManagerInterface $third_party_settings_manager */ + $third_party_settings_manager = \Drupal::service('webform.third_party_settings_manager'); + $replace_administration_mode = $third_party_settings_manager->getThirdPartySetting('captcha', 'replace_administration_mode'); + + $t_args = [ + ':href' => Url::fromRoute('captcha_settings')->toString(), + ]; + + $form['third_party_settings']['captcha'] = [ + '#type' => 'details', + '#title' => t('CAPTCHA'), + '#open' => TRUE, + ]; + $form['third_party_settings']['captcha']['replace_administration_mode'] = [ + '#type' => 'checkbox', + '#title' => t('Replace <a href=":href">Add CAPTCHA administration links to forms</a> with CAPTCHA webform element', $t_args), + '#default_value' => $replace_administration_mode, + '#return_value' => TRUE, + ]; +} + +/** + * Implements hook_webform_submission_form_alter(). + */ +function captcha_webform_submission_form_alter(&$form, FormStateInterface $form_state, $form_id) { + // Make sure CAPTCHA admin mode is enabled and available to the current user. + $config = \Drupal::config('captcha.settings'); + if (!$config->get('administration_mode') + || !\Drupal::currentUser()->hasPermission('administer CAPTCHA settings')) { + return; + } + + // Make sure the CAPTCHA webform element is enabled. + /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + if ($element_manager->isExcluded('captcha')) { + return; + } + + // Make sure replace administrative mode is enabled. + /** @var \Drupal\webform\WebformThirdPartySettingsManagerInterface $third_party_settings_manager */ + $third_party_settings_manager = \Drupal::service('webform.third_party_settings_manager'); + $replace_administration_mode = $third_party_settings_manager->getThirdPartySetting('captcha', 'replace_administration_mode'); + if (!$replace_administration_mode) { + return; + } + + // If the webform already has a CAPTCHA point is alread configured, do not + // do anything. + /* @var \Drupal\captcha\CaptchaPointInterface $captcha_point */ + $captcha_point = \Drupal::entityTypeManager() + ->getStorage('captcha_point') + ->load($form_id); + if ($captcha_point) { + return; + } + + $form['#after_build'][] = '_captcha_webform_submission_form_after_build'; +} + +/** + * After buld callback to add warning to CAPTCHA placement. + */ +function _captcha_webform_submission_form_after_build(array $form, FormStateInterface $form_state) { + // Make sure 'Add CAPTCHA administration links to forms' is appended to the + // webform. + // @see /admin/config/people/captcha + // @see captcha_form_alter() + if (!isset($form['captcha']) || !isset($form['captcha']['add_captcha'])) { + return $form; + } + + /** @var \Drupal\webform\WebformSubmissionForm $form_object */ + $form_object = $form_state->getFormObject(); + $webform = $form_object->getWebform(); + + // Determine if the current user can update this webform via the UI. + $has_update_access = $webform->access('update') + && \Drupal::moduleHandler()->moduleExists('webform_ui'); + + // If the webform has a CAPTCHA element, display a link to edit the element. + $elements = $webform->getElementsInitializedAndFlattened(); + foreach ($elements as $key => $element) { + if (WebformElementHelper::isType($element, 'captcha')) { + // Update the details element's title. + $form['captcha']['#title'] = t('CAPTCHA: challenge enabled'); + + // Replace 'Place a CAPTCHA here for untrusted users' link with link to + // edit CAPTCHA element for this webform. + if ($has_update_access) { + $route_name = 'entity.webform_ui.element.edit_form'; + $route_parameters = ['webform' => $webform->id(), 'key' => $key]; + $route_options = ['query' => Drupal::destination()->getAsArray()]; + $form['captcha']['add_captcha'] = [ + '#type' => 'link', + '#title' => t('Untrusted users will see a CAPTCHA element on this webform.'), + '#url' => Url::fromRoute($route_name, $route_parameters, $route_options), + '#prefix' => '<em>', + '#suffix' => '</em>', + ]; + } + else { + $form['captcha']['add_captcha'] = [ + '#markup' => t('Untrusted users will see a CAPTCHA element on this webform.'), + '#prefix' => '<em>', + '#suffix' => '</em>', + ]; + } + return $form; + } + } + + // Replace 'Place a CAPTCHA here for untrusted users' link with link to + // add CAPTCHA element to this webform. + if ($has_update_access) { + $route_name = 'entity.webform_ui.element.add_form'; + $route_parameters = ['webform' => $webform->id(), 'type' => 'captcha']; + $route_options = ['query' => Drupal::destination()->getAsArray()]; + $form['captcha']['add_captcha'] = [ + '#type' => 'link', + '#title' => t('Add CAPTCHA element to this webform for untrusted users.'), + '#url' => Url::fromRoute($route_name, $route_parameters, $route_options), + ]; + } + else { + $form['captcha']['add_captcha'] = [ + '#type' => 'webform_message', + '#message_message' => t('CAPTCHA should be added as an element to this webform.'), + '#message_type' => 'warning', + ]; + } + return $form; +} diff --git a/web/modules/webform/third_party_settings/webform.honeypot.inc b/web/modules/webform/third_party_settings/webform.honeypot.inc index 89d106b9e5..a97529676d 100644 --- a/web/modules/webform/third_party_settings/webform.honeypot.inc +++ b/web/modules/webform/third_party_settings/webform.honeypot.inc @@ -201,7 +201,7 @@ function honeypot_webform_third_party_settings_form_alter(&$form, FormStateInter */ function honeypot_webform_submission_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Only add a Honeypot when a webform is initially load. - // After a webform is submitted, via a multistep webform and/or saving a draft, + // After a webform is submitted, via a multi-step webform and/or saving a draft, // we can skip adding a Honeypot. if ($form_state->isSubmitted()) { return; diff --git a/web/modules/webform/third_party_settings/webform.maillog.inc b/web/modules/webform/third_party_settings/webform.maillog.inc index 6bcebaffb0..54dc333498 100644 --- a/web/modules/webform/third_party_settings/webform.maillog.inc +++ b/web/modules/webform/third_party_settings/webform.maillog.inc @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\webform\Entity\Webform; +use Drupal\webform\Plugin\WebformHandler\EmailWebformHandler; /** * Implements hook_webform_submission_form_alter(). @@ -37,7 +38,7 @@ function maillog_webform_submission_form_alter(array &$form, FormStateInterface $sends_email = FALSE; $handlers = $webform->getHandlers(); foreach ($handlers as $handler) { - if ($handler instanceof \Drupal\webform\Plugin\WebformHandler\EmailWebformHandler) { + if ($handler instanceof EmailWebformHandler) { $sends_email = TRUE; } } @@ -66,9 +67,13 @@ function maillog_webform_submission_form_alter(array &$form, FormStateInterface } // Display warning if no emails are being sent. - $build[] = ['#markup' => t('No emails will be sent.'), '#prefix' => ' <b>', '#suffix' => '</b>']; + $build[] = [ + '#markup' => t('No emails will be sent.'), + '#prefix' => ' <b>', + '#suffix' => '</b>', + ]; if ($build) { - drupal_set_message(\Drupal::service('renderer')->renderPlain($build), 'warning'); + \Drupal::messenger()->addWarning(\Drupal::service('renderer')->renderPlain($build)); } } diff --git a/web/modules/webform/webform.api.php b/web/modules/webform/webform.api.php index e576472eed..303488a11e 100644 --- a/web/modules/webform/webform.api.php +++ b/web/modules/webform/webform.api.php @@ -188,6 +188,62 @@ function hook_webform_third_party_settings_form_alter(array &$form, \Drupal\Core } +/** + * Act on a webform handler when a method is invoked. + * + * Allows module developers to implement custom logic that can executed before + * any webform handler method is invoked. + * + * This hook can be used to… + * - Conditionally enable or disable a handler. + * - Alter a handler's configuration. + * - Preprocess submission data being passed to a webform handler. + * + * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler + * A webform handler attached to a webform. + * @param string $method_name + * The invoked method name converted to snake case. + * @param array $args + * Argument being passed to the handler's method. + * + * @see \Drupal\webform\Plugin\WebformHandlerInterface + */ +function hook_webform_handler_invoke_alter(\Drupal\webform\Plugin\WebformHandlerInterface $handler, $method_name, array &$args) { + $webform = $handler->getWebform(); + $webform_submission = $handler->getWebformSubmission(); + + $webform_id = $handler->getWebform()->id(); + $handler_id = $handler->getHandlerId(); + $state = $webform_submission->getState(); +} + +/** + * Act on a webform handler when a specific method is invoked. + * + * Allows module developers to implement custom logic that can executed before + * a specified webform handler method is invoked. + * + * This hook can be used to… + * - Conditionally enable or disable a handler. + * - Alter a handler's configuration. + * - Preprocess submission data being passed to a webform handler. + * + * @param \Drupal\webform\Plugin\WebformHandlerInterface $handler + * A webform handler attached to a webform. + * @param array $args + * Argument being passed to the handler's method. + * + * @see \Drupal\webform\Plugin\WebformHandlerInterface + */ +function hook_webform_handler_invoke_METHOD_NAME_alter(\Drupal\webform\Plugin\WebformHandlerInterface $handler, array &$args) { + $webform = $handler->getWebform(); + $webform_submission = $handler->getWebformSubmission(); + + $webform_id = $handler->getWebform()->id(); + $handler_id = $handler->getHandlerId(); + $state = $webform_submission->getState(); +} + /** * Return information about external webform libraries. * @@ -287,6 +343,114 @@ function hook_webform_help_info_alter(array &$help) { } } +/** + * Supply additional access rules that should be managed on per-webform level. + * + * If your module defines any additional access logic that should be managed on + * per webform level, this hook is likely to be of use. Provide additional + * access rules into the webform access system through this hook. Then website + * administrators can assign appropriate grants to your rules for each webform + * via admin UI. Whenever you need to check if a user has access to execute a + * certain operation you should do the following: + * + * \Drupal::entityTypeManager() + * ->getAccessControlHandler('webform_submission') + * ->access($webform_submission, $some_operation, $account); + * + * This will return either a positive or a negative result depending on what + * website administrator has supplied in access settings for the webform in + * question. + * + * @return array + * Array of metadata about additional access rules to be managed on per + * webform basis. Keys should be machine names whereas values are sub arrays + * with the following structure: + * - title: (string) Human friendly title of the rule. + * - description: (array) Renderable array that explains what this access rule + * stands for. Defaults to an empty array. + * - weight: (int) Sorting order of this access rule. Defaults to 0. + * - roles: (string[]) Array of role IDs that should be granted this access + * rule by default. Defaults to an empty array. + * - permissions: (string[]) Array of permissions that should be granted this + * access rule by default. Defaults to an empty array. + */ +function hook_webform_access_rules() { + return [ + // A custom operation. + 'some_operation' => [ + 'title' => t('Some operation'), + 'weight' => -100, + 'roles' => ['authenticated'], + 'permissions' => ['some permission', 'another permission'], + ], + + // Custom any and own operations using hook_submission_access(). + // + // - _any: means to grant access to all webform submissions independently + // of authorship + // - _own: means to grant access only if the user requesting access is + // the author of the webform submission on which the operation is + // being requested. + // + // The below 2 operations can be queried together as following: + // + // \Drupal::entityTypeManager() + // ->getAccessControlHandler('webform_submission') + // ->access($webform_submission, 'some_operation', $account); + // + // This will return TRUE as long as the $account is has either + // 'some_operation_any' or has 'some_operation_own' and is author of + // the $webform_submission. + // + // Note, to implement *_own and *_any you will need to implement + // hook_webform_submission_access(). + // + // @see hook_webform_submission_access() + 'some_operation_any' => [ + 'title' => t('Some operation on ALL webform submissions'), + 'description' => ['#markup' => t('Allow users to execute such particular operation on all webform submissions independently of whether they are authors of those submissions.')], + ], + 'some_operation_own' => [ + 'title' => t('Some operation on own webform submissions'), + ], + ]; +} + +/** + * Alter list of access rules that should be managed on per webform level. + * + * @param array $access_rules + * Array of known access rules. Its structure is identical to the return of + * hook_webform_access_rules(). + */ +function hook_webform_access_rules_alter(array &$access_rules) { + if (isset($access_rules['some_specific_rule_i_want_to_alter'])) { + $access_rules['some_specific_rule_i_want_to_alter']['title'] = t('My very cool altered title!'); + } +} + +/** + * Implements hook_webform_submission_access(). + * + * Implements *_any and *_own operations for a module. + */ +function hook_webform_submission_access(\Drupal\webform\WebformSubmissionInterface $webform_submission, $operation, \Drupal\Core\Session\AccountInterface $account) { + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + + // Add support for some module *_any and *_own access rules. + $access_rules = \Drupal::moduleHandler()->invoke('MY_MODULE', 'webform_access_rules'); + $access_any = isset($access_rules[$operation . '_any']) ? $access_rules_manager->checkWebformSubmissionAccess($operation . '_any', $account, $webform_submission) : \Drupal\Core\Access\AccessResult::forbidden(); + $access_own = (isset($access_rules[$operation . '_own']) && $webform_submission->isOwner($account)) ? $access_rules_manager->checkWebformSubmissionAccess($operation . '_own', $account, $webform_submission) : \Drupal\Core\Access\AccessResult::forbidden(); + $access = $access_any->orIf($access_own); + if ($access->isAllowed()) { + return $access; + } + else { + return \Drupal\Core\Access\AccessResult::neutral(); + } +} + /** * Act on a custom message being displayed, closed or reset. * @@ -294,11 +458,10 @@ function hook_webform_help_info_alter(array &$help) { * closed: Returns TRUE if the message is closed. * close: Sets the message's state to closed. * reset: Resets the message's closed state. - * * @param string $id * The message id. * - * @return mixed|boolean + * @return mixed|bool * TRUE if message is closed, else NULL * * @internal diff --git a/web/modules/webform/webform.info.yml b/web/modules/webform/webform.info.yml index 4e23b2d70e..10b8a6ebb9 100644 --- a/web/modules/webform/webform.info.yml +++ b/web/modules/webform/webform.info.yml @@ -6,13 +6,20 @@ package: Webform configure: entity.webform.collection dependencies: - 'drupal:field' - - 'drupal:system (>= 8.3)' + - 'drupal:system (>= 8.5)' - 'drupal:user' test_dependencies: + - 'address:address' + - 'captcha:captcha' + - 'chosen:chosen' + - 'jsonapi:jsonapi' + - 'mailsystem:mailsystem' + - 'select2:select2' + - 'telephone_validation/telephone_validation' - 'token:token' -# Information added by Drupal.org packaging script on 2018-04-26 -version: '8.x-5.0-rc12' +# Information added by Drupal.org packaging script on 2019-03-28 +version: '8.x-5.2' core: '8.x' project: 'webform' -datestamp: 1524706694 +datestamp: 1553768595 diff --git a/web/modules/webform/webform.install b/web/modules/webform/webform.install index d84ed430de..c1e2b2aced 100644 --- a/web/modules/webform/webform.install +++ b/web/modules/webform/webform.install @@ -6,13 +6,13 @@ */ // Webform install helper functions. -include_once 'includes/webform.install.inc'; +include_once __DIR__ . '/includes/webform.install.inc'; // Webform requirements. -include_once 'includes/webform.install.requirements.inc'; +include_once __DIR__ . '/includes/webform.install.requirements.inc'; // Webform update hooks. -include_once 'includes/webform.install.update.inc'; +include_once __DIR__ . '/includes/webform.install.update.inc'; /** * Implements hook_uninstall(). @@ -26,6 +26,10 @@ function webform_uninstall() { $mail_plugins = $config->get('interface'); unset($mail_plugins['webform']); $config->set('interface', $mail_plugins)->save(); + + // Delete editor uploaded files. + $config = \Drupal::configFactory()->get('webform.settings'); + _webform_config_delete($config); } /** diff --git a/web/modules/webform/webform.libraries.yml b/web/modules/webform/webform.libraries.yml index 36b37dbfcf..f144abf65c 100644 --- a/web/modules/webform/webform.libraries.yml +++ b/web/modules/webform/webform.libraries.yml @@ -6,11 +6,27 @@ webform.admin: theme: css/webform.admin.css: {} js: - js/webform.admin.js: {} + js/webform.admin.js: {} dependencies: + - core/jquery + - core/drupal - webform/webform.form + - webform/webform.filter - webform/webform.admin.dropbutton +webform.filter: + version: VERSION + css: + theme: + css/webform.filter.css: {} + js: + js/webform.filter.js: {} + dependencies: + - core/jquery + - core/drupal + - core/drupal.announce + - core/drupal.debounce + webform.admin.composite: version: VERSION css: @@ -25,11 +41,10 @@ webform.admin.dialog: theme: css/webform.admin.css: {} js: - js/webform.admin.js: {} - js/webform.dialog.js: {} + js/webform.admin.js: {} dependencies: - core/drupal.dialog.ajax - - core/jquery.ui.dialog + - webform/jquery.ui.dialog - webform/webform.ajax - webform/webform.form - webform/webform.form.tabs @@ -46,10 +61,26 @@ webform.admin.dropbutton: theme: css/webform.admin.dropbutton.css: {} js: - js/webform.admin.dropbutton.js: {} + js/webform.admin.dropbutton.js: {} dependencies: - core/drupal.dropbutton +webform.admin.settings: + version: VERSION + css: + theme: + css/webform.admin.settings.css: {} + +webform.admin.tabledrag: + version: VERSION + css: + theme: + css/webform.admin.tabledrag.css: {} + js: + js/webform.admin.tabledrag.js: {} + dependencies: + - core/drupal.tabledrag + webform.admin.translation: version: VERSION css: @@ -77,20 +108,33 @@ webform.ajax: theme: css/webform.ajax.css: {} js: - js/webform.ajax.js: {} + js/webform.ajax.js: {} dependencies: - core/drupal - core/drupalSettings - core/drupal.ajax + - core/drupal.announce - core/jquery.once - core/jquery.form -# Block +# Announce. + +webform.announce: + version: VERSION + js: + js/webform.announce.js: {} + dependencies: + - core/jquery + - core/jquery.once + - core/drupal + - core/drupal.announce + +# Block. webform.block: version: VERSION js: - js/webform.block.js: {} + js/webform.block.js: {} dependencies: - webform/webform.element.codemirror.yaml - webform/webform.element.select2 @@ -112,12 +156,13 @@ webform.confirmation: webform.confirmation.modal: version: VERSION js: - js/webform.confirmation.modal.js: {} + js/webform.confirmation.modal.js: {} dependencies: - core/drupal - core/drupal.dialog - core/drupal.dialog.position - core/jquery.once + - core/jquery.ui.dialog - webform/webform.confirmation # Contribute. @@ -133,10 +178,20 @@ webform.contribute: webform.contextual: version: VERSION js: - js/webform.contextual.js: {} + js/webform.contextual.js: {} dependencies: - core/jquery.once +# Dialog. + +webform.dialog: + version: VERSION + js: + js/webform.dialog.js: {} + dependencies: + - core/drupal.dialog.ajax + - webform/jquery.ui.dialog + # Help. webform.help: @@ -145,7 +200,7 @@ webform.help: theme: css/webform.help.css: {} js: - js/webform.help.js: {} + js/webform.help.js: {} # More. @@ -190,6 +245,16 @@ webform.progress.tracker: dependencies: - webform/libraries.progress-tracker +# Off canvas. + +webform.off_canvas: + version: VERSION + js: + js/webform.off-canvas.js: {} + dependencies: + - core/drupal.debounce + - core/drupal.dialog.off_canvas + # Promotions. webform.promotions: @@ -203,10 +268,20 @@ webform.promotions: webform.states: version: VERSION js: - js/webform.states.js: {} + js/webform.states.js: {} dependencies: - core/drupal.states +# Token. + +webform.token: + version: VERSION + css: + component: + css/webform.token.css: {} + dependencies: + - webform/webform.element.more + # Tooltip. webform.tooltip: @@ -240,9 +315,10 @@ webform.form: component: css/webform.form.css: {} js: - js/webform.form.js: {} + js/webform.form.js: {} dependencies: - core/drupal + - core/drupal.form - core/jquery.once - webform/webform.states @@ -250,29 +326,30 @@ webform.form.disable_back: version: VERSION header: true js: - js/webform.form.disable_back.js: {preprocess: false} + js/webform.form.disable_back.js: { preprocess: false } webform.form.submit_back: version: VERSION header: true js: - js/webform.form.submit_back.js: {preprocess: false} + js/webform.form.submit_back.js: { preprocess: false } dependencies: - core/jquery webform.form.submit_once: version: VERSION js: - js/webform.form.submit_once.js: {} + js/webform.form.submit_once.js: {} dependencies: - core/drupal + - core/drupal.ajax - core/jquery.once - system/base webform.form.tabs: version: VERSION js: - js/webform.form.tabs.js: {} + js/webform.form.tabs.js: {} dependencies: - core/drupal - core/jquery.once @@ -281,31 +358,33 @@ webform.form.tabs: webform.form.unsaved: version: VERSION js: - js/webform.form.unsaved.js: {} + js/webform.form.unsaved.js: {} dependencies: - core/jquery.once - core/drupal -webform.form.wizard: +webform.wizard.pages: version: VERSION + css: + component: + css/webform.wizard.pages.css: {} js: - js/webform.form.wizard.js: {} + js/webform.wizard.pages.js: {} dependencies: - core/jquery.once - core/drupal -# Element. - -# Drupal 8.3.x and below -webform.element.buttons.buttonset: +webform.wizard.track: version: VERSION js: - js/webform.element.buttons.buttonset.js: {} + js/webform.wizard.track.js: {} dependencies: - - core/jquery.ui.button + - core/jquery.once + - core/drupal -# Drupal 8.4.x and above -webform.element.buttons.checkboxradio: +# Element. + +webform.element.buttons: version: VERSION js: # Issue #2906737: With the new jquery 3 checkboxradio and selectmenu error. @@ -313,7 +392,7 @@ webform.element.buttons.checkboxradio: /core/assets/vendor/jquery.ui/ui/form-reset-mixin-min.js: { minified: true } /core/assets/vendor/jquery.ui/ui/escape-selector-min.js: { minified: true } /core/assets/vendor/jquery.ui/ui/widgets/checkboxradio-min.js: { minified: true } - js/webform.element.buttons.checkboxradio.js: {} + js/webform.element.buttons.js: {} # Issue #2933980: JQuery UI CSS dependencies are not met in core. # @see https://www.drupal.org/node/2933980 css: @@ -343,7 +422,7 @@ webform.element.codemirror.text: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.text @@ -353,7 +432,7 @@ webform.element.codemirror.yaml: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.yaml @@ -363,7 +442,7 @@ webform.element.codemirror.html: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.html @@ -373,7 +452,7 @@ webform.element.codemirror.htmlmixed: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.htmlmixed @@ -383,7 +462,7 @@ webform.element.codemirror.css: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.css @@ -393,7 +472,7 @@ webform.element.codemirror.javascript: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.javascript @@ -403,7 +482,7 @@ webform.element.codemirror.php: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.php @@ -413,7 +492,7 @@ webform.element.codemirror.twig: component: css/webform.element.codemirror.css: {} js: - js/webform.element.codemirror.js: {weight: -1} + js/webform.element.codemirror.js: { weight: -1 } dependencies: - webform/libraries.codemirror.twig @@ -439,23 +518,47 @@ webform.element.composite: - core/drupal - core/jquery.once +webform.element.computed: + version: VERSION + css: + component: + css/webform.element.computed.css: {} + js: + js/webform.element.computed.js: {} + dependencies: + - core/drupal.debounce + - core/jquery.once + - webform/webform.announce + webform.element.counter: version: VERSION + css: + component: + css/webform.element.counter.css: {} js: js/webform.element.counter.js: {} dependencies: - core/drupal - core/jquery.once - - webform/libraries.jquery.word-and-character-counter + - webform/libraries.jquery.textcounter webform.element.date: version: VERSION + css: + component: + css/webform.element.date.css: {} js: js/webform.element.date.js: {} dependencies: - core/drupal.date - core/drupalSettings +webform.element.datelist: + version: VERSION + css: + component: + css/webform.element.datelist.css: {} + webform.element.details.save: version: VERSION js: @@ -473,6 +576,7 @@ webform.element.details.toggle: js/webform.element.details.toggle.js: {} dependencies: - core/drupal + - core/drupal.announce - core/jquery.once webform.element.file.button: @@ -556,24 +660,62 @@ webform.element.inputmask: - core/jquery.once - webform/libraries.jquery.inputmask +webform.element.inputhide: + version: VERSION + js: + js/webform.element.inputhide.js: {} + dependencies: + - core/drupal + - core/jquery.once + webform.element.likert: version: VERSION css: component: css/webform.element.likert.css: {} -webform.element.location: +webform.element.location.geocomplete: version: VERSION css: component: - css/webform.element.location.css: {} + css/webform.element.location.geocomplete.css: {} js: - js/webform.element.location.js: {} + js/webform.element.location.geocomplete.js: {} dependencies: - core/drupal - core/jquery.once - webform/libraries.jquery.geocomplete +webform.element.location.places: + version: VERSION + css: + component: + css/webform.element.location.places.css: {} + js: + js/webform.element.location.places.js: {} + dependencies: + - core/drupal + - core/jquery.once + - webform/libraries.algolia.places + +webform.element.managed_file: + version: VERSION + css: + component: + css/webform.element.managed_file.css: {} + js: + js/webform.element.managed_file.js: {} + dependencies: + - core/drupal + - core/jquery.once + - file/drupal.file + +webform.element.mapping: + version: VERSION + css: + component: + css/webform.element.mapping.css: {} + webform.element.message: version: VERSION css: @@ -685,6 +827,12 @@ webform.element.states: css: component: css/webform.element.states.css: {} + js: + js/webform.element.states.js: {} + dependencies: + - core/drupal + - core/jquery.once + - core/jquery.ui.autocomplete webform.element.telephone: version: VERSION @@ -724,6 +872,8 @@ webform.element.text_format: webform.element.tableselect: version: VERSION + js: + js/webform.element.tableselect.js: {} css: component: css/webform.element.tableselect.css: {} @@ -776,32 +926,42 @@ webform.composite.telephone: dependencies: - webform/webform.composite +# jQuery UI. + +jquery.ui.dialog: + version: VERSION + js: + js/webform.jquery.ui.dialog.js: {} + dependencies: + - core/jquery.ui.dialog + # Contrib Module. imce.input: version: VERSION js: - js/webform.imce.js: {} + js/webform.imce.js: {} dependencies: - imce/drupal.imce.input + # External libraries. libraries.codemirror.text: remote: https://github.com/codemirror/codemirror - version: &webform_codemirror_version '5.36.0' + version: &webform_codemirror_version '5.44.0' license: &webform_codemirror_license name: MIT url: http://codemirror.net/LICENSE gpl-compatible: true cdn: &webform_codemirror_cdn - /libraries/codemirror/lib/: https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.36.0/ - /libraries/codemirror/: https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.36.0/ + /libraries/codemirror/lib/: https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.44.0/ + /libraries/codemirror/: https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.44.0/ css: component: /libraries/codemirror/lib/codemirror.css: {} js: - /libraries/codemirror/lib/codemirror.js: {weight: -1} + /libraries/codemirror/lib/codemirror.js: { weight: -1 } /libraries/codemirror/addon/runmode/runmode.js: {} /libraries/codemirror/addon/display/placeholder.js: {} @@ -834,7 +994,7 @@ libraries.codemirror.htmlmixed: dependencies: - webform/libraries.codemirror.text - webform/libraries.codemirror.css - - webform/libraries.codemirror.js + - webform/libraries.codemirror.javascript libraries.codemirror.css: version: *webform_codemirror_version @@ -861,7 +1021,6 @@ libraries.codemirror.php: js: /libraries/codemirror/mode/php/php.js: {} /libraries/codemirror/mode/clike/clike.js: {} - dependencies: - webform/libraries.codemirror.text @@ -874,19 +1033,17 @@ libraries.codemirror.twig: dependencies: - webform/libraries.codemirror.text -libraries.jquery.word-and-character-counter: - remote: https://github.com/qwertypants/jQuery-Word-and-Character-Counter-Plugin - version: '2.5.1' +libraries.algolia.places: + remote: https://github.com/algolia/places + version: '1.16.1' license: name: MIT - url: http://opensource.org/licenses/mit-license.php + url: https://github.com/algolia/places/blob/master/LICENSE gpl-compatible: true cdn: - /libraries/jquery.word-and-character-counter/: https://cdn.rawgit.com/qwertypants/jQuery-Word-and-Character-Counter-Plugin/2.5.1/ + /libraries/algolia.places/dist/cdn/: https://cdn.jsdelivr.net/npm/places.js@1.16.1/dist/cdn/ js: - /libraries/jquery.word-and-character-counter/jquery.word-and-character-counter.min.js: {} - dependencies: - - core/jquery + /libraries/algolia.places/dist/cdn/places.js: {} libraries.jquery.geocomplete: remote: http://ubilabs.github.io/geocomplete/ @@ -899,7 +1056,7 @@ libraries.jquery.geocomplete: /libraries/geocomplete/: https://cdnjs.cloudflare.com/ajax/libs/geocomplete/1.7.0/ js: https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places: {} - /libraries/geocomplete/jquery.geocomplete.min.js: {} + /libraries/geocomplete/jquery.geocomplete.min.js: { minified: true } dependencies: - core/jquery @@ -959,33 +1116,34 @@ libraries.jquery.icheck.square: libraries.jquery.inputmask: remote: https://github.com/RobinHerbots/jquery.inputmask - version: '3.3.11' + version: '4.0.6' license: name: MIT url: http://opensource.org/licenses/mit-license.php gpl-compatible: true cdn: - /libraries/jquery.inputmask/: https://cdn.rawgit.com/RobinHerbots/Inputmask/3.3.11/ + /libraries/jquery.inputmask/: https://cdn.jsdelivr.net/gh/RobinHerbots/Inputmask@4.0.6/ js: - /libraries/jquery.inputmask/dist/min/jquery.inputmask.bundle.min.js: {} + /libraries/jquery.inputmask/dist/min/jquery.inputmask.bundle.min.js: { minified: true } dependencies: - core/jquery libraries.jquery.intl-tel-input: remote: https://github.com/jackocnr/intl-tel-input - version: 'v12.1.12' + version: 'v15.0.1' license: name: MIT url: https://github.com/jackocnr/intl-tel-input/blob/master/LICENSE gpl-compatible: true cdn: - /libraries/jquery.intl-tel-input/build/: https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/12.1.12/ + /libraries/jquery.intl-tel-input/: https://cdn.jsdelivr.net/gh/jackocnr/intl-tel-input@v15.0.1/ css: component: - /libraries/jquery.intl-tel-input/build/css/intlTelInput.css: {} + /libraries/jquery.intl-tel-input/build/css/intlTelInput.min.css: { minified: true } js: - /libraries/jquery.intl-tel-input/build/js/utils.js : {} - /libraries/jquery.intl-tel-input/build/js/intlTelInput.min.js : {} + /libraries/jquery.intl-tel-input/build/js/intlTelInput.min.js: { minified: true } + /libraries/jquery.intl-tel-input/build/js/intlTelInput-jquery.min.js: { minified: true } + dependencies: - core/jquery @@ -997,12 +1155,12 @@ libraries.jquery.rateit: url: https://github.com/gjunge/rateit.js/blob/master/LICENSE.md gpl-compatible: true cdn: - /libraries/jquery.rateit/: https://cdn.rawgit.com/gjunge/rateit.js/1.1.1/ + /libraries/jquery.rateit/: https://cdn.jsdelivr.net/gh/gjunge/rateit.js@1.1.1/ css: component: /libraries/jquery.rateit/scripts/rateit.css: {} js: - /libraries/jquery.rateit/scripts/jquery.rateit.min.js: {} + /libraries/jquery.rateit/scripts/jquery.rateit.min.js: { minified: true } dependencies: - core/jquery @@ -1017,43 +1175,57 @@ libraries.jquery.select2: /libraries/jquery.select2/dist/: https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/ css: component: - /libraries/jquery.select2/dist/css/select2.min.css: {} + /libraries/jquery.select2/dist/css/select2.min.css: { minified: true } js: - /libraries/jquery.select2/dist/js/select2.min.js: {} + /libraries/jquery.select2/dist/js/select2.min.js: { minified: true } dependencies: - core/jquery libraries.jquery.chosen: remote: https://harvesthq.github.io/chosen/ - version: 'v1.8.3' + version: 'v1.8.7' license: name: MIT url: https://github.com/harvesthq/chosen/blob/master/LICENSE.md gpl-compatible: true cdn: - /libraries/jquery.chosen/: https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.3/ + /libraries/jquery.chosen/: https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/ css: component: - /libraries/jquery.chosen/chosen.css: {} + /libraries/jquery.chosen/chosen.min.css: { minified: true } + js: + /libraries/jquery.chosen/chosen.jquery.min.js: { minified: true } + dependencies: + - core/jquery + +libraries.jquery.textcounter: + remote: https://github.com/ractoon/jQuery-Text-Counter + version: '0.8.0' + license: + name: MIT + url: http://opensource.org/licenses/mit-license.php + gpl-compatible: true + cdn: + /libraries/jquery.textcounter/: https://cdn.jsdelivr.net/gh/ractoon/jQuery-Text-Counter@0.8.0/ js: - /libraries/jquery.chosen/chosen.jquery.js: {} + /libraries/jquery.textcounter/textcounter.min.js: { minified: true } dependencies: - core/jquery libraries.jquery.timepicker: remote: https://github.com/jonthornton/jquery-timepicker - version: '1.11.13' + version: '1.11.14' license: name: MIT url: http://opensource.org/licenses/mit-license.php gpl-compatible: true cdn: - /libraries/jquery.timepicker/: https://cdn.rawgit.com/jonthornton/jquery-timepicker/1.11.13/ + /libraries/jquery.timepicker/: https://cdn.jsdelivr.net/gh/jonthornton/jquery-timepicker@1.11.14/ css: component: - /libraries/jquery.timepicker/jquery.timepicker.css: {} + /libraries/jquery.timepicker/jquery.timepicker.min.css: { minified: true } js: - /libraries/jquery.timepicker/jquery.timepicker.min.js: {} + /libraries/jquery.timepicker/jquery.timepicker.min.js: { minified: true } dependencies: - core/jquery @@ -1065,12 +1237,12 @@ libraries.jquery.toggles: url: http://opensource.org/licenses/mit-license.php gpl-compatible: true cdn: - /libraries/jquery.toggles/: https://cdn.rawgit.com/simontabor/jquery-toggles/v4.0.0/ + /libraries/jquery.toggles/: https://cdn.jsdelivr.net/gh/simontabor/jquery-toggles@v4.0.0/ css: component: /libraries/jquery.toggles/css/toggles-full.css: {} js: - /libraries/jquery.toggles/toggles.min.js: {} + /libraries/jquery.toggles/toggles.min.js: { minified: true } dependencies: - core/jquery @@ -1082,7 +1254,7 @@ libraries.progress-tracker: url: https://github.com/NigelOToole/progress-tracker/blob/master/LICENSE gpl-compatible: true cdn: - /libraries/progress-tracker/: https://cdn.rawgit.com/NigelOToole/progress-tracker/v1.4.0/ + /libraries/progress-tracker/: https://cdn.jsdelivr.net/gh/NigelOToole/progress-tracker@v1.4.0/ css: component: /libraries/progress-tracker/app/styles/progress-tracker.css: {} @@ -1095,6 +1267,6 @@ libraries.signature_pad: url: http://opensource.org/licenses/mit-license.php gpl-compatible: true cdn: - /libraries/signature_pad/: https://cdn.rawgit.com/szimek/signature_pad/v2.3.0/ + /libraries/signature_pad/: https://cdn.jsdelivr.net/gh/szimek/signature_pad@v2.3.0/ js: /libraries/signature_pad/example/js/signature_pad.js: {} diff --git a/web/modules/webform/webform.links.action.yml b/web/modules/webform/webform.links.action.yml index 55b34502d7..db4554a06b 100644 --- a/web/modules/webform/webform.links.action.yml +++ b/web/modules/webform/webform.links.action.yml @@ -1,5 +1,29 @@ +entity.webform.add_form: + route_name: entity.webform.add_form + title: 'Add webform' + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + dialog: narrow + appears_on: + - entity.webform.collection + entity.webform_options.add_form: route_name: entity.webform_options.add_form title: 'Add options' appears_on: - entity.webform_options.collection + +entity.webform.handler.add_email: + route_name: entity.webform.handler.add_email + title: 'Add email' + off_canvas: normal + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + appears_on: + - entity.webform.handlers + +entity.webform.handler: + route_name: entity.webform.handler + title: 'Add handler' + dialog: normal + class: '\Drupal\webform\Plugin\Menu\LocalAction\WebformDialogLocalAction' + appears_on: + - entity.webform.handlers diff --git a/web/modules/webform/webform.links.menu.yml b/web/modules/webform/webform.links.menu.yml index 0146635dbe..f31ef94d4a 100644 --- a/web/modules/webform/webform.links.menu.yml +++ b/web/modules/webform/webform.links.menu.yml @@ -3,3 +3,8 @@ entity.webform.collection: description: 'Create webforms and manage submissions.' parent: system.admin_structure route_name: entity.webform.collection +webform.reports_plugins: + title: 'Webform plugins' + parent: system.admin_reports + description: 'Overview of plugins used by the Webform module.' + route_name: webform.reports_plugins.elements diff --git a/web/modules/webform/webform.links.task.yml b/web/modules/webform/webform.links.task.yml index 9739b7f93c..1f1c5dc8ce 100644 --- a/web/modules/webform/webform.links.task.yml +++ b/web/modules/webform/webform.links.task.yml @@ -26,12 +26,6 @@ entity.webform_submission.collection_purge: parent_id: entity.webform_submission.collection weight: 10 -entity.webform_submission.collection_log: - title: 'Log' - route_name: entity.webform_submission.collection_log - parent_id: entity.webform_submission.collection - weight: 20 - # Settings. webform.config: @@ -86,33 +80,7 @@ webform.config.advanced: title: 'Advanced' route_name: webform.config.advanced parent_id: webform.config - weight: 80 - -# Plugins. - -webform.plugins: - title: 'Plugins' - route_name: webform.element_plugins - base_route: entity.webform.collection - weight: 40 - -webform.plugins.elements: - title: 'Elements' - route_name: webform.element_plugins - parent_id: webform.plugins - weight: 0 - -webform.plugins.handlers: - title: 'Handlers' - route_name: webform.handler_plugins - parent_id: webform.plugins - weight: 10 - -webform.plugins.exporters: - title: 'Exporters' - route_name: webform.exporter_plugins - parent_id: webform.plugins - weight: 20 + weight: 100 webform.addons: title: 'Add-ons' @@ -120,11 +88,23 @@ webform.addons: base_route: entity.webform.collection weight: 50 +webform.help: + title: 'Help' + route_name: webform.help + base_route: entity.webform.collection + weight: 60 + +webform.help.videos: + title: 'Videos' + route_name: webform.help + parent_id: webform.help + weight: 0 + webform.contribute: title: 'Contribute' route_name: webform.contribute - base_route: entity.webform.collection - weight: 50 + parent_id: webform.help + weight: 10 # Form @@ -168,12 +148,6 @@ entity.webform.results_clear: parent_id: entity.webform.results weight: 20 -entity.webform.results_log: - title: 'Log' - route_name: entity.webform.results_log - parent_id: entity.webform.results - weight: 30 - # Webform edit (build). entity.webform.edit_form: @@ -232,6 +206,14 @@ entity.webform.settings_access: parent_id: entity.webform.settings weight: 60 +# Export + +entity.webform.export_form: + title: 'Export' + route_name: entity.webform.export_form + base_route: entity.webform.canonical + weight: 60 + # Submission entity.webform_submission.canonical: @@ -294,18 +276,6 @@ entity.webform_submission.resend_form: base_route: entity.webform_submission.canonical weight: 30 -entity.webform_submission.delete_form: - title: 'Delete' - route_name: entity.webform_submission.delete_form - base_route: entity.webform_submission.canonical - weight: 40 - -entity.webform_submission.log: - title: 'Log' - route_name: entity.webform_submission.log - base_route: entity.webform_submission.canonical - weight: 50 - # User Submission entity.webform.user.submission: @@ -320,8 +290,35 @@ entity.webform.user.submission.edit: base_route: entity.webform.user.submission weight: 10 -entity.webform.user.submission.delete: - title: 'Delete' - route_name: entity.webform.user.submission.delete +entity.webform.user.submission.duplicate: + title: 'Duplicate' + route_name: entity.webform.user.submission.duplicate base_route: entity.webform.user.submission weight: 20 + +# Webform Plugins. + +webform.reports_plugins.elements: + title: 'Elements' + route_name: webform.reports_plugins.elements + base_route: webform.reports_plugins.elements + weight: 0 + +webform.reports_plugins.handlers: + title: 'Handlers' + route_name: webform.reports_plugins.handlers + base_route: webform.reports_plugins.elements + weight: 10 + +webform.reports_plugins.exporters: + title: 'Exporters' + route_name: webform.reports_plugins.exporters + base_route: webform.reports_plugins.elements + weight: 20 + +# User Submissions. + +entity.webform_submission.user: + title: 'Submissions' + route_name: entity.webform_submission.user + base_route: entity.user.canonical diff --git a/web/modules/webform/webform.module b/web/modules/webform/webform.module index 28e42a6f22..8f4db07dd1 100644 --- a/web/modules/webform/webform.module +++ b/web/modules/webform/webform.module @@ -10,8 +10,10 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Asset\AttachedAssetsInterface; use Drupal\Core\Breadcrumb\Breadcrumb; +use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Render\Markup; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; @@ -22,7 +24,9 @@ use Drupal\webform\Entity\WebformSubmission; use Drupal\webform\Element\WebformMessage; use Drupal\webform\Plugin\WebformElement\ManagedFile; +use Drupal\webform\Utility\Mail; use Drupal\webform\Utility\WebformArrayHelper; +use Drupal\webform\Utility\WebformElementHelper; use Drupal\webform\Utility\WebformDialogHelper; use Drupal\webform\Utility\WebformOptionsHelper; use Drupal\webform\WebformInterface; @@ -33,6 +37,7 @@ require_once __DIR__ . '/includes/webform.options.inc'; require_once __DIR__ . '/includes/webform.theme.inc'; require_once __DIR__ . '/includes/webform.translation.inc'; +require_once __DIR__ . '/includes/webform.editor.inc'; /** * Implements hook_help(). @@ -102,11 +107,18 @@ function webform_modules_uninstalled($modules) { // Remove uninstalled module's third party settings from admin settings. $config = \Drupal::configFactory()->getEditable('webform.settings'); $third_party_settings = $config->get('third_party_settings'); + + $has_third_party_settings = FALSE; foreach ($modules as $module) { - unset($third_party_settings[$module]); + if (isset($third_party_settings[$module])) { + $has_third_party_settings = TRUE; + unset($third_party_settings[$module]); + } + } + if ($has_third_party_settings) { + $config->set('third_party_settings', $third_party_settings); + $config->save(); } - $config->set('third_party_settings', $third_party_settings); - $config->save(); // Check HTML email provider support as modules are ininstalled. /** @var \Drupal\webform\WebformEmailProviderInterface $email_provider */ @@ -139,6 +151,10 @@ function webform_local_tasks_alter(&$local_tasks) { $local_tasks['config_translation.local_tasks:entity.webform.config_translation_overview']['base_route'] = 'entity.webform.canonical'; } if (isset($local_tasks['config_translation.local_tasks:config_translation.item.overview.webform.config'])) { + // Set weight to 110 so that the 'Translate' tab comes after + // the 'Advanced' tab. + // @see webform.links.task.yml + $local_tasks['config_translation.local_tasks:config_translation.item.overview.webform.config']['weight'] = 110; $local_tasks['config_translation.local_tasks:config_translation.item.overview.webform.config']['parent_id'] = 'webform.config'; } @@ -153,10 +169,18 @@ function webform_local_tasks_alter(&$local_tasks) { * Implements hook_menu_local_tasks_alter(). */ function webform_menu_local_tasks_alter(&$data, $route_name) { - // Change 'Translate *' tab to be just label 'Translate'. - if (isset($data['tabs'][0]['config_translation.local_tasks:entity.webform.config_translation_overview']['#link']['title'])) { - $data['tabs'][0]['config_translation.local_tasks:entity.webform.config_translation_overview']['#link']['title'] = t('Translate'); + // Change config entities 'Translate *' tab to be just label 'Translate'. + $webform_entities = [ + 'webform', + 'webform_options', + ]; + foreach ($webform_entities as $webform_entity) { + if (isset($data['tabs'][0]["config_translation.local_tasks:entity.$webform_entity.config_translation_overview"]['#link']['title'])) { + $data['tabs'][0]["config_translation.local_tasks:entity.$webform_entity.config_translation_overview"]['#link']['title'] = t('Translate'); + } } + + // Change simple config 'Translate *' tab to be just label 'Translate'. if (isset($data['tabs'][1]['config_translation.local_tasks:config_translation.item.overview.webform.config'])) { $data['tabs'][1]['config_translation.local_tasks:config_translation.item.overview.webform.config']['#link']['title'] = t('Translate'); } @@ -213,18 +237,6 @@ function webform_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Make sure webform libraries are always attached to submission form. _webform_page_attachments($form); - // Execute hook_form_BASE_FORM_ID_alter for the webform & source entity - // without the operation. (i.e. 'add', 'edit', 'notes', etc...) - // @see \Drupal\webform\WebformSubmissionForm::getFormId - /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ - $webform_submission = $form_object->getEntity(); - $webform = $webform_submission->getWebform(); - - $hook = 'form_webform_submission_' . $webform->id() . '_form'; - $form_id = $form_object->getFormId(); - \Drupal::service('module_handler')->alter($hook, $form, $form_state, $form_id); - \Drupal::service('theme.manager')->alter($hook, $form, $form_state, $form_id); - // After build. $form['#after_build'][] = '_webform_form_webform_submission_form_after_build'; } @@ -309,7 +321,7 @@ function webform_form_update_manager_update_form_alter(&$form, FormStateInterfac '#weight' => -10, ]; - // Display warning to backup site when webform is checked. + // Display warning to backup site when webform is checked. $form['projects']['#options']['webform']['title']['data'] = [ 'title' => $form['projects']['#options']['webform']['title']['data'], 'container' => [ @@ -325,6 +337,20 @@ function webform_form_update_manager_update_form_alter(&$form, FormStateInterfac ]; } +/** + * Implements hook_form_FORM_ID_alter(). + */ +function webform_form_views_exposed_form_alter(&$form, FormStateInterface $form_state, $form_id) { + /** @var \Drupal\views\ViewExecutable $view */ + $view = $form_state->get('view'); + + // Check if this a is webform submission view. + // @see \Drupal\webform\WebformSubmissionListBuilder::buildSubmissionViews + if (isset($view->webform_submission_view)) { + $form['#action'] = Url::fromRoute(\Drupal::routeMatch()->getRouteName(), \Drupal::routeMatch()->getRawParameters()->all())->toString(); + } +} + /** * Implements hook_system_breadcrumb_alter(). */ @@ -351,10 +377,50 @@ function webform_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInte } } +/** + * Implements hook_token_info_alter(). + */ +function webform_token_info_alter(&$data) { + module_load_include('inc', 'webform', 'webform.tokens.inc'); + + // Append learn more about token suffixes to all webform token descriptions. + // @see \Drupal\webform\WebformTokenManager::replace + // @see webform_page_attachments() + $token_suffixes = t('Append the below suffixes to alter the returned value.') . + '<ul>' . + '<li>' . t('<code>:clear</code> to removes token when not replaced.') . '</li>' . + '<li>' . t('<code>:urlencode</code> URL encodes returned value.') . '</li>' . + '<li>' . t('<code>:xmlencode</code> XML encodes returned value.') . '</li>' . + '<li>' . t('<code>:htmldecode</code> decodes HTML enities in returned value.') . '<br/><b>' . t('This suffix has security implications.') . '</b><br/>' . t('Use <code>:htmldecode</code> with <code>:striptags</code>.') . '</li>' . + '<li>' . t('<code>:striptags</code> removes all HTML tags from returned value.') . '</li>' . + '</ul>'; + $more = _webform_token_render_more(t('Learn about token suffixes'), $token_suffixes); + foreach ($data['types'] as $type => &$info) { + if (strpos($type, 'webform') === 0) { + if (!empty($info['description'])) { + $description = $info['description'] . $more; + } + else { + $description = $more; + } + $info['description'] = Markup::create($description); + } + } +} + +/** + * Implements hook_entity_presave(). + */ +function webform_entity_presave(EntityInterface $entity) { + _webform_clear_webform_submission_list_cache_tag($entity); +} + /** * Implements hook_entity_delete(). */ function webform_entity_delete(EntityInterface $entity) { + _webform_clear_webform_submission_list_cache_tag($entity); + /** @var \Drupal\webform\WebformEntityReferenceManagerInterface $entity_reference_manager */ $entity_reference_manager = \Drupal::service('webform.entity_reference_manager'); @@ -366,6 +432,27 @@ function webform_entity_delete(EntityInterface $entity) { } } +/** + * Invalidate 'webform_submission_list' cache tag when user or role is updated. + * + * Once the below issue is resolved we should rework this approach. + * + * Issue #2811041: Allow views base tables to define additional + * cache tags and max age. + * https://www.drupal.org/project/drupal/issues/2811041 + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. + * + * @see \Drupal\webform\Entity\WebformSubmission + * @see webform_query_webform_submission_access_alter() + */ +function _webform_clear_webform_submission_list_cache_tag(EntityInterface $entity) { + if (in_array($entity->getEntityTypeId(), ['user', 'user_role'])) { + Cache::invalidateTags(['webform_submission_list']); + } +} + /** * Implements hook_mail(). */ @@ -393,11 +480,11 @@ function webform_mail($key, &$message, $params) { $message['body'][] = $params['body']; // Set the header 'From'. - // Usingthe 'from_mail' so that the webform's email from value is used + // Using the 'from_mail' so that the webform's email from value is used // instead of site's email address. // @see: \Drupal\Core\Mail\MailManager::mail. if (!empty($params['from_mail'])) { - $message['from'] = $message['headers']['From'] = (!empty($params['from_name'])) ? Unicode::mimeHeaderEncode($params['from_name'], TRUE) . ' <' . $params['from_mail'] . '>' : $params['from_mail']; + $message['from'] = $message['headers']['From'] = (!empty($params['from_name'])) ? Mail::formatDisplayName($params['from_name']) . ' <' . $params['from_mail'] . '>' : $params['from_mail']; } // Set header 'Cc'. @@ -413,7 +500,6 @@ function webform_mail($key, &$message, $params) { // Set header 'Reply-to'. $reply_to = $params['reply_to'] ?: ''; if (empty($reply_to) && !empty($params['from_mail'])) { - // @todo Determine if the 'reply-to' must only be the to 'from mail' if 'from mail' has the same domain as the 'site mail'. $reply_to = $message['from']; } if ($reply_to) { @@ -430,7 +516,7 @@ function webform_mail($key, &$message, $params) { $sender_mail = $params['sender_mail'] ?: ''; $sender_name = $params['sender_name'] ?: $params['from_name'] ?: ''; if ($sender_mail) { - $message['headers']['Sender'] = ($sender_name) ? Unicode::mimeHeaderEncode($sender_name, TRUE) . ' <' . $sender_mail . '>' : $sender_mail; + $message['headers']['Sender'] = ($sender_name) ? Mail::formatDisplayName($sender_name) . ' <' . $sender_mail . '>' : $sender_mail; } } @@ -454,11 +540,11 @@ function webform_mail_alter(&$message) { * Implements hook_page_attachments(). */ function webform_page_attachments(array &$attachments) { - $route_name = Drupal::routeMatch()->getRouteName(); + $route_name = \Drupal::routeMatch()->getRouteName(); $url = Url::fromRoute('<current>')->toString(); // Attach global libraries only to webform specific pages. - if (preg_match('/^(webform\.|^entity\.([^.]+\.)?webform)/', $route_name) || preg_match('#(/node/add/webform|/admin/help/webform)#', $url)) { + if (preg_match('/^(webform\.|^entity\.([^.]+\.)?webform)/', $route_name)) { _webform_page_attachments($attachments); } @@ -469,7 +555,7 @@ function webform_page_attachments(array &$attachments) { } // Attach 'Contribute' section style. - if (\Drupal::routeMatch()->getRouteName() === 'webform.contribute') { + if ($route_name === 'webform.contribute') { /** @var \Drupal\webform\WebformContributeManagerInterface $contribute_manager */ $contribute_manager = \Drupal::service('webform.contribute_manager'); $attachments['#attached']['html_head'][] = [ @@ -481,6 +567,25 @@ function webform_page_attachments(array &$attachments) { 'webform_contribute', ]; } + + // Attach webform dialog library and options to every page. + if (\Drupal::config('webform.settings')->get('settings.dialog')) { + $attachments['#attached']['library'][] = 'webform/webform.dialog'; + $attachments['#attached']['drupalSettings']['webform']['dialog']['options'] = \Drupal::config('webform.settings')->get('settings.dialog_options'); + + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + if ($source_entity = $request_handler->getCurrentSourceEntity()) { + $attachments['#attached']['drupalSettings']['webform']['dialog']['entity_type'] = $source_entity->getEntityTypeId(); + $attachments['#attached']['drupalSettings']['webform']['dialog']['entity_id'] = $source_entity->id(); + } + } + + // Attach webform more element to token token help. + // @see webform_token_info_alter() + if ($route_name === 'help.page' && \Drupal::routeMatch()->getRawParameter('name') === 'token') { + $attachments['#attached']['library'][] = 'webform/webform.token'; + } } /** @@ -511,6 +616,9 @@ function _webform_page_attachments(array &$attachments) { $attachments['#attached']['library'][] = 'webform/webform.element.details.save'; } + // Add 'info' message style to all webform pages. + $attachments['#attached']['library'][] = 'webform/webform.element.message'; + // Assets: Add custom shared and webform specific CSS and JS. // @see webform_library_info_build() /** @var \Drupal\webform\WebformRequestInterface $request_handler */ @@ -587,13 +695,18 @@ function webform_css_alter(&$css, AttachedAssetsInterface $assets) { */ function webform_js_alter(&$javascript, AttachedAssetsInterface $assets) { // Add Google API key required by webform/libraries.jquery.geocomplete - // which is dependency for webform/webform.element.location. - // @see \Drupal\webform\Element\WebformLocation::processWebformComposite + // which is dependency for webform/webform.element.location.geocomplete. + // + // @see \Drupal\webform\Element\WebformLocationGeocomplete::processWebformComposite // @see webform.libraries.yml + // @see webform/webform.element.location.geocomplete + // @see webform/libraries.jquery.geocomplete $settings = $assets->getSettings(); - if (!empty($settings['webform']['location']['google_maps_api_key'])) { - $api_key = $settings['webform']['location']['google_maps_api_key']; + if (!empty($settings['webform']['location']['geocomplete']['api_key']) && isset($javascript['https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places'])) { + $api_key = $settings['webform']['location']['geocomplete']['api_key']; $javascript['https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places']['data'] = "https://maps.googleapis.com/maps/api/js?key=$api_key&libraries=places"; + unset($settings['webform']['location']['geocomplete']['api_key']); + $assets->setSettings($settings); } _webform_asset_alter($javascript, 'javascript'); @@ -661,6 +774,61 @@ function webform_contextual_links_view_alter(&$element, $items) { } } +/** + * Implements hook_webform_access_rules(). + */ +function webform_webform_access_rules() { + return [ + 'create' => [ + 'title' => t('Create submissions'), + 'roles' => [ + 'anonymous', + 'authenticated', + ], + ], + 'view_any' => [ + 'title' => t('View any submissions'), + ], + 'update_any' => [ + 'title' => t('Update any submissions'), + ], + 'delete_any' => [ + 'title' => t('Delete any submissions'), + ], + 'purge_any' => [ + 'title' => t('Purge any submissions'), + ], + 'view_own' => [ + 'title' => t('View own submissions'), + ], + 'update_own' => [ + 'title' => t('Update own submissions'), + ], + 'delete_own' => [ + 'title' => t('Delete own submissions'), + ], + 'administer' => [ + 'title' => t('Administer webform & submissions'), + 'description' => [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => t('<strong>Warning</strong>: The below settings give users, permissions, and roles full access to this webform and its submissions.'), + ], + ], + 'test' => [ + 'title' => t('Test webform'), + ], + 'configuration' => [ + 'title' => t('Access webform configuration'), + 'description' => [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => t("<strong>Warning</strong>: The below settings give users, permissions, and roles full access to this webform's configuration via API requests."), + ], + ], + ]; +} + /** * Adds JavaScript to change the state of an element based on another element. * @@ -711,6 +879,10 @@ function webform_element_info_alter(array &$info) { * The processed element. */ function webform_process_options(&$element, FormStateInterface $form_state, &$complete_form) { + if (!WebformElementHelper::isWebformElement($element)) { + return $element; + } + if (!empty($element['#options_description_display'])) { $description_property_name = ($element['#options_description_display'] == 'help') ? '#help' : '#description'; foreach (Element::children($element) as $key) { @@ -722,9 +894,22 @@ function webform_process_options(&$element, FormStateInterface $form_state, &$co list($title, $description) = explode(WebformOptionsHelper::DESCRIPTION_DELIMITER, $title); $element[$key]['#title'] = $title; + $element[$key]['#webform_element'] = TRUE; $element[$key][$description_property_name] = $description; } } + + // Issue #2839344: Some aria-describedby refers to not existing element ID. + // @see https://www.drupal.org/project/drupal/issues/2839344 + if (!empty($element['#attributes']['aria-describedby'])) { + foreach (Element::children($element) as $key) { + if (empty($element[$key]['#attributes']['aria-describedby']) + && $element['#attributes']['aria-describedby'] === $element[$key]['#attributes']['aria-describedby']) { + unset($element[$key]['#attributes']['aria-describedby']); + } + } + } + return $element; } @@ -784,21 +969,21 @@ function _webform_entity_element_validate_rendering_exception_handler($exception throw $exception; } +/******************************************************************************/ +// Query alter functions. +/******************************************************************************/ + /** - * Implements hook_query_alter(). + * Implements hook_query_TAG_alter(). * * Append EAV sort to webform_submission entity query. * * @see http://stackoverflow.com/questions/12893314/sorting-eav-database * @see \Drupal\webform\WebformSubmissionListBuilder::getEntityIds */ -function webform_query_alter(AlterableInterface $query) { +function webform_query_webform_submission_list_builder_alter(AlterableInterface $query) { /** @var \Drupal\Core\Database\Query\SelectInterface $query */ $name = $query->getMetaData('webform_submission_element_name'); - if (!$name) { - return; - } - $direction = $query->getMetaData('webform_submission_element_direction'); $property_name = $query->getMetaData('webform_submission_element_property_name'); @@ -811,3 +996,228 @@ function webform_query_alter(AlterableInterface $query) { } $query->orderBy('value', $direction); } + +/** + * Implements hook_query_TAG_alter(). + */ +function webform_query_entity_reference_alter(AlterableInterface $query) { + /** @var \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection $handler */ + $handler = $query->getMetaData('entity_reference_selection_handler'); + + // Get webform settings used to limit and randomize results. + // @see \Drupal\webform\Plugin\WebformElement\WebformEntityReferenceTrait::getTestValues + // @see \Drupal\webform\Plugin\WebformElement\WebformEntityReferenceTrait::setOptions + // @see \Drupal\webform\Element\WebformEntityTrait::setOptions + $configuration = $handler->getConfiguration() + ['_webform_settings' => []]; + $settings = $configuration['_webform_settings']; + if (!empty($settings['random'])) { + $query->orderRandom(); + } + if (!empty($settings['limit'])) { + $query->range(0, $settings['limit']); + } +} + +/** + * Implements hook_query_TAG_alter(). + * + * This hook implementation adds webform submission access checks for the + * account stored in the 'account' meta-data (or current user if not provided), + * for an operation stored in the 'op' meta-data (or 'view' if not provided). + */ +function webform_query_webform_submission_access_alter(AlterableInterface $query) { + /** @var \Drupal\Core\Database\Query\SelectInterface $query */ + $op = $query->getMetaData('op') ?: 'view'; + $account = $query->getMetaData('account') ?: \Drupal::currentUser(); + + $entity_type = \Drupal::entityTypeManager()->getDefinition('webform_submission'); + + // Get webform submission tables which are used to build the alter query. + $webform_submission_tables = []; + foreach ($query->getTables() as $table) { + if (is_string($table['table']) && $table['table'] === $entity_type->getBaseTable()) { + $webform_submission_tables[] = [ + 'alias' => $table['alias'], + 'condition' => $query->orConditionGroup(), + ]; + } + } + + // If there are no webform submission tables then nothing needs to be altered. + if (empty($webform_submission_tables)) { + return; + } + + // If the user has administer access then exit. + if ($account->hasPermission('administer webform submission') + || $account->hasPermission('administer webform')) { + return; + } + + // Apply operation specific any and own permissions. + if (in_array($op, ['view', 'edit', 'delete'])) { + $permission_any = "$op any webform submission"; + $permission_own = "$op own webform submission"; + + // If the user has any permission the query does not have to be altered. + if ($account->hasPermission($permission_any)) { + return; + } + + // If the user has own permission, then add the account id to all + // webform submission tables conditions. + if ($account->hasPermission($permission_own)) { + foreach ($webform_submission_tables as $table) { + $table['condition']->condition($table['alias'] . '.uid', $account->id()); + } + } + } + + // Alter query based on update access to all webforms. + /** @var \Drupal\webform\WebformInterface[] $webforms */ + if ($account->isAuthenticated()) { + // Get cached list of webforms that the user can update so that we don't + // have to continually load every webform. + $cached = \Drupal::cache()->get('webform_submission_access__account_update__' . $account->id()); + if ($cached) { + $webform_account_access_update = $cached->data; + } + else { + $webform_account_access_update = []; + /** @var \Drupal\webform\WebformInterface[] $webforms */ + $webforms = Webform::loadMultiple(); + foreach ($webforms as $webform) { + if ($webform->access('update', $account)) { + $webform_account_access_update[] = $webform->id(); + } + } + \Drupal::cache()->set( + 'webform_submission_access__account_update__' . $account->id(), + $webform_account_access_update, + Cache::PERMANENT, + ['config:webform_list', 'user:' . $account->id()] + ); + } + foreach ($webform_account_access_update as $webform_id) { + foreach ($webform_submission_tables as $table) { + $table['condition']->condition($table['alias'] . '.webform_id', $webform_id); + } + } + } + + // Alter query based on access rules. + /** @var \Drupal\webform\WebformAccessRulesManagerInterface $access_rules_manager */ + $access_rules_manager = \Drupal::service('webform.access_rules_manager'); + + // Get cached webform access rules and cache tags so that we don't have + // to continually load every webform. + $cached = \Drupal::cache()->get('webform_submission_access__webform_access_rules'); + if ($cached) { + $webform_access_rules = $cached->data; + } + else { + /** @var \Drupal\webform\WebformInterface[] $webforms */ + $webforms = Webform::loadMultiple(); + $webform_access_rules = []; + foreach ($webforms as $webform_id => $webform) { + $webform_access_rules[$webform_id] = $access_rules_manager->getAccessRules($webform) ?: []; + } + \Drupal::cache()->set( + 'webform_submission_access__webform_access_rules', + $webform_access_rules, + Cache::PERMANENT, + ['config:webform_list'] + ); + } + + foreach ($webform_access_rules as $webform_id => $access_rules) { + // Check basic and any access rules and add webform id to all + // webform submission tables conditions. + if ($access_rules_manager->checkAccessRules($op, $account, $access_rules) + || $access_rules_manager->checkAccessRules($op . '_any', $account, $access_rules)) { + foreach ($webform_submission_tables as $table) { + $table['condition']->condition($table['alias'] . '.webform_id', $webform_id); + } + } + // If the user has own access rules, then add the account id to all + // webform submission tables conditions. + elseif ($access_rules_manager->checkAccessRules($op . '_own', $account, $access_rules)) { + foreach ($webform_submission_tables as $table) { + /** @var \Drupal\Core\Database\Query\SelectInterface $query */ + $condition = $query->andConditionGroup(); + $condition->condition($table['alias'] . '.uid', $account->id()); + $condition->condition($table['alias'] . '.webform_id', $webform_id); + $table['condition']->condition($condition); + } + } + } + + // Alter query based on webform access group. + // @todo Figure out how to move the below code to the webform_access.module. + if (\Drupal::moduleHandler()->moduleExists('webform_access')) { + // Collect access group ids with 'view_any' or 'administer' permissions. + /** @var \Drupal\webform_access\WebformAccessGroupStorageInterface $access_group_storage */ + $access_group_storage = \Drupal::entityTypeManager()->getStorage('webform_access_group'); + /** @var \Drupal\webform_access\WebformAccessGroupInterface $access_group */ + $access_groups = $access_group_storage->loadByEntities(NULL, NULL, $account); + $access_any_group_ids = []; + $access_own_group_ids = []; + foreach ($access_groups as $access_group) { + $access_group_permissions = $access_group->get('permissions'); + $access_group_permissions = array_combine($access_group_permissions, $access_group_permissions); + if (isset($access_group_permissions['view_any']) || isset($access_group_permissions['administer'])) { + $access_any_group_ids[] = $access_group->id(); + } + elseif (isset($access_group_permissions['view_own'])) { + $access_own_group_ids[] = $access_group->id(); + } + } + if ($access_any_group_ids) { + // Add access group entity type, entity id, and webform id to the query. + $result = \Drupal::database()->select('webform_access_group_entity', 'ge') + ->fields('ge', ['entity_type', 'entity_id', 'webform_id']) + ->condition('group_id', $access_any_group_ids, 'IN') + ->execute(); + while ($record = $result->fetchAssoc()) { + foreach ($webform_submission_tables as $table) { + /** @var \Drupal\Core\Database\Query\SelectInterface $query */ + $condition = $query->andConditionGroup(); + $condition->condition($table['alias'] . '.entity_type', $record['entity_type']); + $condition->condition($table['alias'] . '.entity_id', (string) $record['entity_id']); + $condition->condition($table['alias'] . '.webform_id', $record['webform_id']); + $table['condition']->condition($condition); + } + } + } + if ($access_own_group_ids) { + // Add access group entity type, entity id, and webform id to the query. + $result = \Drupal::database()->select('webform_access_group_entity', 'ge') + ->fields('ge', ['entity_type', 'entity_id', 'webform_id']) + ->condition('group_id', $access_own_group_ids, 'IN') + ->execute(); + while ($record = $result->fetchAssoc()) { + foreach ($webform_submission_tables as $table) { + /** @var \Drupal\Core\Database\Query\SelectInterface $query */ + $condition = $query->andConditionGroup(); + $condition->condition($table['alias'] . '.uid', $account->id()); + $condition->condition($table['alias'] . '.entity_type', $record['entity_type']); + $condition->condition($table['alias'] . '.entity_id', (string) $record['entity_id']); + $condition->condition($table['alias'] . '.webform_id', $record['webform_id']); + $table['condition']->condition($condition); + } + } + } + + } + + // Apply webform submission table conditions to query. + foreach ($webform_submission_tables as $table) { + // If a webform submission table does not have any conditions, + // we have to block access to the table. + if (count($table['condition']->conditions()) === 1) { + $table['condition']->where('1 = 0'); + } + + $query->condition($table['condition']); + } +} diff --git a/web/modules/webform/webform.permissions.yml b/web/modules/webform/webform.permissions.yml index 5b311d3b16..42420c8e80 100644 --- a/web/modules/webform/webform.permissions.yml +++ b/web/modules/webform/webform.permissions.yml @@ -1,9 +1,9 @@ 'access webform overview': title: 'Access the webform overview page' description: 'Get an overview of all webforms.' -'access webform submission log': - title: 'Access webform submission log' - description: 'Allows viewing of <em>all</em> submission events, if the user can access a webform''s results.' +'access webform submission user': + title: 'Access the webform user submission page' + description: 'Allows a user to view their submissions via ''Submissions'' tab on their profile page.' 'administer webform': title: 'Administer webforms' @@ -18,9 +18,14 @@ title: 'Administer webform element access' description: 'Restrict webform element access to certain roles and users.' +'edit webform source': + title: 'Edit webform source code' + description: 'Editing webform source code allows users to alter and possibly break a webform''s render array.' + restrict access: true + 'edit webform twig': title: 'Edit webform Twig templates' - description: 'Editing inline Twig template allows users access any data exposed by Twig functions.' + description: 'Editing inline Twig template allows users to access any data exposed by Twig functions.' restrict access: true 'edit webform assets': @@ -43,21 +48,35 @@ 'delete own webform': title: 'Delete own webform' +'access own webform configuration': + title: 'Access own webform configuration' + description: 'Allows users and applications to access a webform''s configuration created by the user.' + restrict access: true + warning: 'Warning: Give to trusted roles only; this permission has security implications. Note: A webform''s configuration includes all settings and handlers which can contain sensitive information.' +'access any webform configuration': + title: 'Access any webform configuration' + description: 'Allows users and applications to access any webform''s configuration.' + restrict access: true + warning: 'Warning: Give to trusted roles only; this permission has security implications. Note: A webform''s configuration includes all settings and handlers which can contain sensitive information.' + 'view any webform submission': title: 'View any webform submission' description: 'Allows viewing <em>all</em> submissions.' 'view own webform submission': title: 'View own webform submission' - description: 'Allows viewing <em>own</em> submissions.' + description: 'Allows viewing <em>own</em> submissions for all webforms.' + warning: 'Warning: This permission affects access to all webform. Note: To allow users to edit own submissions for an individual webform, please go to the webform''s ''Access'' tab.' 'edit any webform submission': title: 'Edit any webform submission' description: 'Allows updating <em>all</em> submissions.' 'edit own webform submission': title: 'Edit own webform submission' - description: 'Allows updating <em>own</em> submissions.' + description: 'Allows updating <em>own</em> submissions for all webforms.' + warning: 'Warning: This permission affects access to all webform. Note: To allow users to update own submissions for an individual webform, please go to the webform''s ''Access'' tab.' 'delete any webform submission': - title: 'Deleting any webform submission' + title: 'Delete any webform submission' description: 'Allows deleting <em>all</em> submissions.' 'delete own webform submission': title: 'Delete own webform submission' - description: 'Allows deleting <em>own</em> submissions.' + description: 'Allows deleting <em>own</em> submissions for all webforms.' + warning: 'Warning: This permission affects access to all webform. Note: To allow users to deleted own submissions for an individual webform, please go to the webform''s ''Access'' tab.' diff --git a/web/modules/webform/webform.routing.yml b/web/modules/webform/webform.routing.yml index 5e9ce44cd0..88f6a1faa6 100644 --- a/web/modules/webform/webform.routing.yml +++ b/web/modules/webform/webform.routing.yml @@ -56,15 +56,6 @@ webform.config.advanced: requirements: _permission: 'administer webform' -# Help - -webform.help.video: - path: '/admin/help/webform/video/{id}' - defaults: - _form: '\Drupal\webform\Form\WebformHelpVideoForm' - requirements: - _permission: 'access content' - # Add-ons webform.addons: @@ -75,6 +66,24 @@ webform.addons: requirements: _permission: 'administer webform' +# Help + +# webform.help is dynamically added. +# @see \Drupal\webform\Routing\WebformRouteSubscriber +webform.help: + path: '/admin/structure/webform/help' + defaults: + _controller: '\Drupal\webform\Controller\WebformHelpController::index' + requirements: + _permission: 'access content' + +webform.help.video: + path: '/admin/help/webform/video/{id}' + defaults: + _form: '\Drupal\webform\Form\WebformHelpVideoForm' + requirements: + _permission: 'access content' + # Contribute webform.contribute: @@ -117,6 +126,14 @@ entity.webform.autocomplete: requirements: _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkOverviewAccess' +entity.webform.autocomplete.archived: + path: '/admin/structure/webform/autocomplete/archived' + defaults: + _controller: '\Drupal\webform\Controller\WebformEntityController::autocomplete' + archived: TRUE + requirements: + _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkOverviewAccess' + entity.webform.canonical: path: '/webform/{webform}' defaults: @@ -125,6 +142,15 @@ entity.webform.canonical: requirements: _entity_access: 'webform.submission_page' +entity.webform.access_denied: + path: '/webform/{webform}/access-denied' + defaults: + _controller: '\Drupal\webform\Controller\WebformEntityController::accessDenied' + _title_callback: '\Drupal\webform\Controller\WebformEntityController::accessDeniedTitle' + requirements: + # Access denied is available to all users. + _access: 'TRUE' + entity.webform.assets.javascript: path: '/webform/javascript/{webform}' defaults: @@ -145,13 +171,14 @@ entity.webform.confirmation: _controller: '\Drupal\webform\Controller\WebformEntityController::confirmation' _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' requirements: - _entity_access: 'webform.submission_create' + _entity_access: 'webform.view' entity.webform.user.submissions: - path: '/webform/{webform}/submissions' + path: '/webform/{webform}/submissions/{submission_view}' defaults: _entity_list: 'webform_submission' _title: 'Submissions' + submission_view: '' options: parameters: webform: @@ -160,16 +187,18 @@ entity.webform.user.submissions: _entity_access: 'webform.submission_view_own' entity.webform.user.drafts: - path: '/webform/{webform}/drafts' + path: '/webform/{webform}/drafts/{submission_view}' defaults: _entity_list: 'webform_submission' _title: 'Drafts' + submission_view: '' options: parameters: webform: type: 'entity:webform' requirements: - _entity_access: 'webform.view' + _entity_access: 'webform.submission_create' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess::checkDraftsAccess' entity.webform.user.submission: path: '/webform/{webform}/submissions/{webform_submission}' @@ -196,6 +225,22 @@ entity.webform.user.submission.delete: requirements: _entity_access: 'webform_submission.delete' +entity.webform.user.submission.duplicate: + path: '/webform/{webform}/submissions/{webform_submission}/duplicate' + defaults: + _entity_form: 'webform_submission.duplicate' + _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' + duplicate: TRUE + setting: 'submission_user_duplicate' + value: TRUE + options: + parameters: + webform: + type: 'entity:webform' + requirements: + _entity_access: 'webform_submission.update' + _custom_access: '\Drupal\webform\Access\WebformEntityAccess::checkWebformSettingValue' + entity.webform.test_form: path: '/webform/{webform}/test' defaults: @@ -208,6 +253,14 @@ entity.webform.test_form: requirements: _entity_access: 'webform.test' +entity.webform.export_form: + path: '/admin/structure/webform/manage/{webform}/export' + defaults: + _entity_form: 'webform.export' + _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' + requirements: + _permission: 'export configuration' + entity.webform.add_form: path: '/admin/structure/webform/add' defaults: @@ -293,24 +346,24 @@ entity.webform.settings_access: # Webform submission results -entity.webform.results_user: - path: '/admin/structure/webform/manage/{webform}/results/user' +entity.webform.results_submissions: + path: '/admin/structure/webform/manage/{webform}/results/submissions/{submission_view}' defaults: _entity_list: 'webform_submission' - _title: 'Webform results' + _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' + submission_view: '' options: parameters: webform: type: 'entity:webform' requirements: - _entity_access: 'webform.submission_view_own' + _entity_access: 'webform.submission_view_any' _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkResultsAccess' -entity.webform.results_submissions: - path: '/admin/structure/webform/manage/{webform}/results/submissions' +entity.webform.results.source_entity.autocomplete: + path: '/admin/structure/webform/manage/{webform}/results/source-entity/autocomplete' defaults: - _entity_list: 'webform_submission' - _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' + _controller: '\Drupal\webform\Controller\WebformSubmissionsController::sourceEntityAutocomplete' options: parameters: webform: @@ -371,20 +424,6 @@ entity.webform.results_clear: _entity_access: 'webform.submission_purge_any' _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkResultsAccess' -entity.webform.results_log: - path: '/admin/structure/webform/manage/{webform}/results/log' - defaults: - _controller: '\Drupal\webform\Controller\WebformSubmissionLogController::overview' - _title_callback: '\Drupal\webform\Controller\WebformEntityController::title' - options: - parameters: - webform: - type: 'entity:webform' - requirements: - _permission: 'access webform submission log' - _entity_access: 'webform.submission_view_any' - _custom_access: '\Drupal\webform\Access\WebformEntityAccess:checkLogAccess' - # Webform options entity.webform_options.collection: @@ -395,6 +434,13 @@ entity.webform_options.collection: requirements: _permission: 'administer webform' +entity.webform_options.autocomplete: + path: '/admin/structure/webform/config/options/autocomplete' + defaults: + _controller: '\Drupal\webform\Controller\WebformOptionsController::autocomplete' + requirements: + _permission: 'administer webform' + entity.webform_options.add_form: path: '/admin/structure/webform/config/options/manage/add' defaults: @@ -430,10 +476,11 @@ entity.webform_options.delete_form: # Webform results (submissions) entity.webform_submission.collection: - path: '/admin/structure/webform/submissions/manage' + path: '/admin/structure/webform/submissions/manage/{submission_view}' defaults: _entity_list: 'webform_submission' _title: 'Webforms: Submissions' + submission_view: '' requirements: _custom_access: '\Drupal\webform\Access\WebformAccountAccess:checkSubmissionAccess' @@ -445,16 +492,20 @@ entity.webform_submission.collection_purge: requirements: _permission: 'administer webform' -# Webform log (submissions) +# Webform user (submissions) -entity.webform_submission.collection_log: - path: '/admin/structure/webform/submissions/log' +entity.webform_submission.user: + path: '/user/{user}/submissions/{submission_view}' defaults: - _controller: '\Drupal\webform\Controller\WebformSubmissionLogController::overview' - _title: 'Webforms: Submissions log' + _entity_list: 'webform_submission' + _title: 'Submissions' + submission_view: '' requirements: - _permission: 'access webform submission log' - _custom_access: '\Drupal\webform\Access\WebformAccountAccess:checkSubmissionAccess' + _custom_access: '\Drupal\webform\Access\WebformAccountAccess::checkUserSubmissionsAccess' + options: + parameters: + user: + type: entity:user # Webform submissions @@ -467,6 +518,15 @@ entity.webform_submission.canonical: requirements: _entity_access: 'webform_submission.view' +entity.webform_submission.access_denied: + path: '/admin/structure/webform/manage/{webform}/submission/{webform_submission}/access-denied' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::accessDenied' + _title_callback: '\Drupal\webform\Controller\WebformSubmissionController::accessDeniedTitle' + requirements: + # Access denied is available to all users. + _access: 'TRUE' + entity.webform_submission.table: path: '/admin/structure/webform/manage/{webform}/submission/{webform_submission}/table' defaults: @@ -492,18 +552,9 @@ entity.webform_submission.yaml: _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' view_mode: 'yaml' requirements: + _permission: 'edit webform source' _entity_access: 'webform_submission.view_any' -entity.webform_submission.log: - path: '/admin/structure/webform/manage/{webform}/submission/{webform_submission}/log' - defaults: - _controller: '\Drupal\webform\Controller\WebformSubmissionLogController::overview' - _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' - requirements: - _permission: 'access webform submission log' - _entity_access: 'webform_submission.view_any' - _custom_access: '\Drupal\webform\Access\WebformEntityAccess::checkLogAccess' - entity.webform_submission.edit_form: path: '/admin/structure/webform/manage/{webform}/submission/{webform_submission}/edit' defaults: @@ -535,6 +586,7 @@ entity.webform_submission.resend_form: _form: 'Drupal\webform\Form\WebformSubmissionResendForm' _title_callback: '\Drupal\webform\Controller\WebformSubmissionViewController::title' requirements: + _entity_access: 'webform_submission.update_any' _custom_access: '\Drupal\webform\Access\WebformSubmissionAccess::checkResendAccess' entity.webform_submission.duplicate_form: @@ -594,6 +646,7 @@ entity.webform.handler: _title: 'Select a handler' requirements: _entity_access: 'webform.update' + _custom_access: '\Drupal\webform\Access\WebformHandlerAccess::checkHandlerAccess' entity.webform.handler.add_form: path: '/admin/structure/webform/manage/{webform}/handlers/add/{webform_handler}' @@ -602,6 +655,17 @@ entity.webform.handler.add_form: _title: 'Add webform handler' requirements: _entity_access: 'webform.update' + _custom_access: '\Drupal\webform\Access\WebformHandlerAccess::checkHandlerAccess' + +entity.webform.handler.add_email: + path: '/admin/structure/webform/manage/{webform}/handlers/add/email' + defaults: + _form: '\Drupal\webform\Form\WebformHandlerAddForm' + _title: 'Add email' + webform_handler: email + requirements: + _entity_access: 'webform.update' + _custom_access: '\Drupal\webform\Access\WebformHandlerAccess::checkHandlerAccess' entity.webform.handler.edit_form: path: '/admin/structure/webform/manage/{webform}/handlers/{webform_handler}/edit' @@ -627,29 +691,47 @@ entity.webform.handler.delete_form: requirements: _entity_access: 'webform.update' +entity.webform.handler.enable: + path: '/admin/structure/webform/manage/{webform}/handlers/{webform_handler}/enable' + defaults: + _controller: '\Drupal\webform\WebformEntityHandlersForm::ajaxOperation' + op: enable + requirements: + _entity_access: 'webform.update' + _csrf_token: 'TRUE' + +entity.webform.handler.disable: + path: '/admin/structure/webform/manage/{webform}/handlers/{webform_handler}/disable' + defaults: + _controller: '\Drupal\webform\WebformEntityHandlersForm::ajaxOperation' + op: disable + requirements: + _entity_access: 'webform.update' + _csrf_token: 'TRUE' + # Plugins. -webform.element_plugins: - path: '/admin/structure/webform/plugins/elements' +webform.reports_plugins.elements: + path: '/admin/reports/webform-plugins/elements' defaults: _controller: '\Drupal\webform\Controller\WebformPluginElementController::index' - _title: 'Webforms: Element plugins' + _title: 'Webform plugins: Elements' requirements: _permission: 'administer webform' -webform.handler_plugins: - path: '/admin/structure/webform/plugins/handlers' +webform.reports_plugins.handlers: + path: '/admin/reports/webform-plugins/handlers' defaults: _controller: '\Drupal\webform\Controller\WebformPluginHandlerController::index' - _title: 'Webforms: Handler plugins' + _title: 'Webform plugins: Handlers' requirements: _permission: 'administer webform' -webform.exporter_plugins: - path: '/admin/structure/webform/plugins/exporters' +webform.reports_plugins.exporters: + path: '/admin/reports/webform-plugins/exporters' defaults: _controller: '\Drupal\webform\Controller\WebformPluginExporterController::index' - _title: 'Webforms: Export plugins' + _title: 'Webform plugins: Exporters' requirements: _permission: 'administer webform' diff --git a/web/modules/webform/webform.services.yml b/web/modules/webform/webform.services.yml index 07f4f4c0f0..180ccd2229 100644 --- a/web/modules/webform/webform.services.yml +++ b/web/modules/webform/webform.services.yml @@ -25,6 +25,11 @@ services: factory: logger.factory:get arguments: ['webform'] + logger.channel.webform_submission: + class: Drupal\Core\Logger\LoggerChannel + factory: logger.factory:get + arguments: ['webform_submission'] + # Services. webform.addons_manager: @@ -59,11 +64,11 @@ services: webform.message_manager: class: Drupal\webform\WebformMessageManager - arguments: ['@current_user', '@config.factory', '@entity_type.manager', '@logger.channel.webform', '@renderer', '@webform.request', '@webform.token_manager'] + arguments: ['@current_user', '@config.factory', '@entity_type.manager', '@logger.channel.webform', '@renderer', '@messenger', '@webform.request', '@webform.token_manager'] webform.translation_manager: class: Drupal\webform\WebformTranslationManager - arguments: ['@language_manager', '@config.factory', '@plugin.manager.webform.element'] + arguments: ['@current_route_match', '@language_manager', '@config.factory', '@messenger', '@plugin.manager.webform.element'] webform.request: class: Drupal\webform\WebformRequest @@ -75,7 +80,7 @@ services: webform_submission.exporter: class: Drupal\webform\WebformSubmissionExporter - arguments: ['@config.factory', '@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@plugin.manager.webform.element', '@plugin.manager.webform.exporter'] + arguments: ['@config.factory', '@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@plugin.manager.archiver', '@plugin.manager.webform.element', '@plugin.manager.webform.exporter'] webform.third_party_settings_manager: class: Drupal\webform\WebformThirdPartySettingsManager @@ -83,11 +88,15 @@ services: webform.token_manager: class: Drupal\webform\WebformTokenManager - arguments: ['@config.factory', '@module_handler', '@theme.manager', '@token'] + arguments: ['@current_user', '@language_manager', '@config.factory', '@module_handler', '@token'] webform.theme_manager: class: Drupal\webform\WebformThemeManager - arguments: ['@config.factory', '@renderer', '@theme.manager', '@theme.initialization'] + arguments: ['@config.factory', '@renderer', '@theme.manager', '@theme_handler', '@theme.initialization'] + + webform.access_rules_manager: + class: Drupal\webform\WebformAccessRulesManager + arguments: ['@module_handler'] webform_submission.conditions_validator: class: Drupal\webform\WebformSubmissionConditionsValidator @@ -103,9 +112,9 @@ services: # Event subscriber. - webform.event_subscriber: - class: Drupal\webform\EventSubscriber\WebformSubscriber - arguments: ['@current_user', '@config.factory', '@renderer', '@redirect.destination', '@webform.token_manager'] + webform.exception_html_subscriber: + class: Drupal\webform\EventSubscriber\WebformExceptionHtmlSubscriber + arguments: ['@http_kernel', '@logger.channel.php', '@redirect.destination', '@router.no_access_checks', '@current_user', '@config.factory', '@renderer', '@messenger', '@webform.token_manager'] tags: - { name: event_subscriber } diff --git a/web/modules/webform/webform.tokens.inc b/web/modules/webform/webform.tokens.inc index 93a5db780a..6cccecbef9 100644 --- a/web/modules/webform/webform.tokens.inc +++ b/web/modules/webform/webform.tokens.inc @@ -11,11 +11,12 @@ use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Markup; use Drupal\user\Entity\User; -use Drupal\webform\Plugin\WebformElement\WebformComputedBase; -use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\Plugin\WebformElementManagerInterface; use Drupal\webform\Plugin\WebformElementEntityReferenceInterface; +use Drupal\webform\Plugin\WebformElement\WebformComputedBase; +use Drupal\webform\Utility\WebformDateHelper; use Drupal\webform\WebformSubmissionInterface; +use Drupal\Core\Url; /** * Implements hook_token_info(). @@ -24,16 +25,6 @@ function webform_token_info() { $types = []; $tokens = []; - /****************************************************************************/ - // Authenticated user. - /****************************************************************************/ - - $types['webform-authenticated-user'] = [ - 'name' => t('Webform authenticated user'), - 'description' => t('Tokens related to the currently authenticated user.'), - 'type' => 'user', - ]; - /****************************************************************************/ // Webform submission. /****************************************************************************/ @@ -93,7 +84,7 @@ function webform_token_info() { 'name' => t('In draft'), 'description' => t('Is the webform submission in draft.'), ]; - $webform['label'] = [ + $webform_submission['label'] = [ 'name' => t('Label'), 'description' => t('The label of the webform submission.'), ]; @@ -154,35 +145,35 @@ function webform_token_info() { ]; $webform_submission['values'] = [ 'name' => t('Submission values'), - 'description' => Markup::create((string) t('Webform tokens from submitted data.') . - t("Replace the '?' with...") . '<br />' . - '<ul>' . - '<li>element_key</li>' . - '<li>element_key:format:items</li>' . - '<li>element_key:delta</li>' . - '<li>element_key:sub_element_key</li>' . - '<li>element_key:delta:sub_element_key</li>' . - '<li>element_key:sub_element_key:format</li>' . - '<li>element_key:delta:sub_element_key:format</li>' . - '<li>element_key:delta:format</li>' . - '<li>element_key:delta:format:html</li>' . - '<li>element_key:entity:*</li>' . - '<li>element_key:entity:*</li>' . - '<li>element_key:delta:entity:*</li>' . - '<li>element_key:delta:entity:field_name:*</li>' . - '<li>element_key:sub_element_key:entity:*</li>' . - '<li>element_key:sub_element_key:entity:field_name:*</li>' . - '<li>element_key:delta:sub_element_key:entity:*</li>' . - '</ul>' . - t("All items after the 'element_key' are optional.") . '<br />' . - t("The 'delta' is the numeric index for specific value") . '<br />' . - t("The 'sub_element_key' is a composite element's sub element key.") . '<br />' . - t("The 'format' can be 'value', 'raw', or custom format specifically associated with the element") . '<br />' . - t("The 'items' can be 'comma', 'semicolon', 'and', 'ol', 'ul', or custom delimiter") . '<br />' . - t("The 'entity:*' applies to the referenced entity") . '<br />' . - t("Add 'html' at the end of the token to return HTML markup instead of plain text.") . '<br />' . - t("Add 'clear' at the end of the token to return an empty value if element is missing.") . '<br />' . - t("For example, to display the Contact webform's 'Subject' element's value you would use the [webform_submission:values:subject] token.") + 'description' => Markup::create(t('Webform tokens from submitted data.') . + _webform_token_render_more(t('Learn about submission value tokens'), + t("Replace the '?' with…") . '<br />' . + '<ul>' . + '<li>element_key</li>' . + '<li>element_key:format:items</li>' . + '<li>element_key:delta</li>' . + '<li>element_key:sub_element_key</li>' . + '<li>element_key:delta:sub_element_key</li>' . + '<li>element_key:sub_element_key:format</li>' . + '<li>element_key:delta:sub_element_key:format</li>' . + '<li>element_key:delta:format</li>' . + '<li>element_key:delta:format:html</li>' . + '<li>element_key:entity:*</li>' . + '<li>element_key:delta:entity:*</li>' . + '<li>element_key:delta:entity:field_name:*</li>' . + '<li>element_key:sub_element_key:entity:*</li>' . + '<li>element_key:sub_element_key:entity:field_name:*</li>' . + '<li>element_key:delta:sub_element_key:entity:*</li>' . + '</ul>' . + t("All items after the 'element_key' are optional.") . '<br />' . + t("The 'delta' is the numeric index for specific value") . '<br />' . + t("The 'sub_element_key' is a composite element's sub element key.") . '<br />' . + t("The 'format' can be 'value', 'raw', or custom format specifically associated with the element") . '<br />' . + t("The 'items' can be 'comma', 'semicolon', 'and', 'ol', 'ul', or custom delimiter") . '<br />' . + t("The 'entity:*' applies to the referenced entity") . '<br />' . + t("Add 'html' at the end of the token to return HTML markup instead of plain text.") . '<br />' . + t("For example, to display the Contact webform's 'Subject' element's value you would use the [webform_submission:values:subject] token.") + ) ), 'dynamic' => TRUE, ]; @@ -216,13 +207,23 @@ function webform_token_info() { 'name' => t('Source entity'), 'description' => t('The source entity that the webform submission was submitted from.'), 'type' => 'entity', + 'dynamic' => TRUE, ]; $webform_submission['submitted-to'] = [ 'name' => t('Submitted to'), 'description' => t('The source entity or webform that the webform submission was submitted from.'), 'type' => 'entity', + 'dynamic' => TRUE, ]; + // Append link to token help to source-entity and submitted-to description. + if (\Drupal::moduleHandler()->moduleExists('token') && \Drupal::moduleHandler()->moduleExists('help')) { + $t_args = [':href' => Url::fromRoute('help.page', ['name' => 'token'])->toString()]; + $token_help = t('For a list of the currently available source entity related tokens, please see <a href=":href">token help</a>.', $t_args); + $webform_submission['source-entity']['description'] = Markup::create($webform_submission['source-entity']['description'] . '<br/>' . $token_help); + $webform_submission['submitted-to']['description'] = Markup::create($webform_submission['submitted-to']['description'] . '<br/>' . $token_help); + } + $tokens['webform_submission'] = $webform_submission; /****************************************************************************/ @@ -268,13 +269,15 @@ function webform_token_info() { ]; $webform['handler'] = [ 'name' => t('Handler response'), - 'description' => Markup::create((string) t('Webform handler response tokens.') . '<br/>' . - t("Replace the '?' with...") . '<br />' . - '<ul>' . - '<li>handler_id:state:key</li>' . - '<li>handler_id:state:key1:key2</li>' . - '</ul>' . - t("For example, to display a remote post's confirmation number you would use the [webform:handler:remote_post:completed:confirmation_number] token.") + 'description' => Markup::create(t('Webform handler response tokens.') . + _webform_token_render_more(t('Learn about handler response tokens'), + t("Replace the '?' with…") . '<br />' . + '<ul>' . + '<li>handler_id:state:key</li>' . + '<li>handler_id:state:key1:key2</li>' . + '</ul>' . + t("For example, to display a remote post's confirmation number you would use the [webform:handler:remote_post:completed:confirmation_number] token.") + ) ), 'dynamic' => TRUE, ]; @@ -289,7 +292,7 @@ function webform_token_info() { if ($roles) { $types['webform_role'] = [ 'name' => t('Webform roles'), - 'description' => t("Tokens related to user roles that can receive email. <em>This token is only available to a Webform email handler's 'To', 'CC', and 'BCC' email recipents.</em>"), + 'description' => t("Tokens related to user roles that can receive email. <em>This token is only available to a Webform email handler's 'To', 'CC', and 'BCC' email recipients.</em>"), 'needs-data' => 'webform_role', ]; @@ -330,20 +333,7 @@ function webform_tokens($type, $tokens, array $data, array $options, BubbleableM } $replacements = []; - if ($type == 'webform-authenticated-user') { - if (\Drupal::currentUser()->isAuthenticated()) { - $account = User::load(\Drupal::currentUser()->id()); - $bubbleable_metadata->addCacheableDependency($account); - $replacements += $token_service->generate('user', $tokens, ['user' => $account], $options, $bubbleable_metadata); - } - // Make sure to clear all tokens. - foreach ($tokens as $name => $original) { - if (!isset($replacements[$original])) { - $replacements[$original] = ''; - } - } - } - elseif ($type == 'webform_role' && !empty($data['webform_role'])) { + if ($type == 'webform_role' && !empty($data['webform_role'])) { $roles = $data['webform_role']; $any_role = in_array('authenticated', $roles) ? TRUE : FALSE; foreach ($tokens as $role_name => $original) { @@ -390,7 +380,7 @@ function webform_tokens($type, $tokens, array $data, array $options, BubbleableM $webform = $webform_submission->getWebform(); $bubbleable_metadata->addCacheableDependency($webform); - $source_entity = $webform_submission->getSourceEntity(); + $source_entity = $webform_submission->getSourceEntity(TRUE); if ($source_entity) { $bubbleable_metadata->addCacheableDependency($source_entity); } @@ -576,10 +566,10 @@ function webform_tokens($type, $tokens, array $data, array $options, BubbleableM if (($webform_tokens = $token_service->findWithPrefix($tokens, 'webform')) && ($webform = $webform_submission->getWebform())) { $replacements += $token_service->generate('webform', $webform_tokens, ['webform' => $webform], $options, $bubbleable_metadata); } - if (($source_entity_tokens = $token_service->findWithPrefix($tokens, 'source-entity')) && ($source_entity = $webform_submission->getSourceEntity())) { + if (($source_entity_tokens = $token_service->findWithPrefix($tokens, 'source-entity')) && ($source_entity = $webform_submission->getSourceEntity(TRUE))) { $replacements += $token_service->generate($source_entity->getEntityTypeId(), $source_entity_tokens, [$source_entity->getEntityTypeId() => $source_entity], $options, $bubbleable_metadata); } - if (($submitted_to_tokens = $token_service->findWithPrefix($tokens, 'submitted-to')) && ($submitted_to = $webform_submission->getSourceEntity() ?: $webform_submission->getWebform())) { + if (($submitted_to_tokens = $token_service->findWithPrefix($tokens, 'submitted-to')) && ($submitted_to = $webform_submission->getSourceEntity(TRUE) ?: $webform_submission->getWebform())) { $replacements += $token_service->generate($submitted_to->getEntityTypeId(), $submitted_to_tokens, [$submitted_to->getEntityTypeId() => $submitted_to], $options, $bubbleable_metadata); } @@ -687,6 +677,8 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo // [values:{element_key}:{format}:{items}] // [values:{element_key}:{format}:html] // [values:{element_key}:{format}:{items}:html] + // [values:{element_key}:{format}:urlencode] + // [values:{element_key}:{format}:{items}:urlencode] // [values:{element_key}:{delta}:{format}] // [values:{element_key}:{delta}:{sub-element}] $keys = explode(':', $value_token); @@ -698,30 +690,40 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo return _webform_token_get_submission_values($options, $webform_submission); } + // Set default options. + $options += [ + 'html' => FALSE, + ]; + + // Parse suffixes and set options. + $suffixes = [ + // Indicates the tokens should be formatted as HTML instead of plain text. + 'html', + ]; + foreach ($suffixes as $suffix) { + if ($keys && in_array($suffix, $keys)) { + $keys = array_diff($keys, [$suffix]); + $options[$suffix] = TRUE; + } + } + $element = $webform_submission->getWebform()->getElement($element_key, TRUE); // Exit if form element does not exist. if (!$element) { - return (in_array('clear', $keys)) ? '' : NULL; + return NULL; } $element_plugin = $element_manager->getElementInstance($element); - // Get value for a computed element. + // Always get value for a computed element. if ($element_plugin instanceof WebformComputedBase) { return $element_plugin->getValue($element, $webform_submission); } // Exit if no submission data and form element is not a container. if (!isset($submission_data[$element_key]) && !$element_plugin->isContainer($element)) { - return (in_array('clear', $keys)) ? '' : NULL; - } - - // Look for :html which indicates the tokens should be formatted as HTML - // instead of plain text. - if ($keys && in_array('html', $keys)) { - $keys = array_diff($keys, ['html']); - $options['html'] = TRUE; + return NULL; } // If multiple value element look for delta. @@ -742,6 +744,10 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo $composite_key = NULL; } + /****************************************************************************/ + // Get value. + /****************************************************************************/ + // Set entity reference chaining. if ($keys && $keys[0] == 'entity' && $element_plugin instanceof WebformElementEntityReferenceInterface) { // Remove entity from keys. @@ -759,12 +765,13 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo ]; $entity_token_name = (isset($entity_token_names[$entity_type])) ? $entity_token_names[$entity_type] : $entity_type; $entity_token = implode(':', $keys); - return Markup::create(\Drupal::token()->replace( + $token_value = Markup::create(\Drupal::token()->replace( "[$entity_token_name:$entity_token]", [$entity_token_name => $entity], $options, $bubbleable_metadata )); + return $token_value; } else { return ''; @@ -792,21 +799,20 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo if (is_array($token_value)) { // Note, tokens can't include CSS and JS libraries since they will // can be included in an email. - return \Drupal::service('renderer')->renderPlain($token_value); - } - elseif ($token_value instanceof MarkupInterface) { - return $token_value; + $token_value = \Drupal::service('renderer')->renderPlain($token_value); } elseif (isset($element['#format']) && $element['#format'] === 'raw') { // Make sure raw tokens are always rendered AS-IS. - return Markup::create((string) $token_value); + $token_value = Markup::create((string) $token_value); } - else { + elseif (!($token_value instanceof MarkupInterface)) { // All strings will be escaped as HtmlEscapedText. // @see \Drupal\Core\Utility\Token::replace // @see \Drupal\Component\Render\HtmlEscapedText - return (string) $token_value; + $token_value = (string) $token_value; } + + return $token_value; } /** @@ -821,12 +827,45 @@ function _webform_token_get_submission_value($value_token, array $options, Webfo * Webform submission values. */ function _webform_token_get_submission_values(array $options, WebformSubmissionInterface $webform_submission) { + static $rendering; + if ($rendering) { + $token = (!empty($options['html'])) ? '[webform_submission:values:html]' : '[webform_submission:values]'; + throw new \LogicException("Recursive rendering of $token detected."); + } + + $rendering = TRUE; + $submission_format = (!empty($options['html'])) ? 'html' : 'text'; /** @var \Drupal\webform\WebformSubmissionViewBuilderInterface $view_builder */ $view_builder = \Drupal::entityTypeManager()->getViewBuilder('webform_submission'); $form_elements = $webform_submission->getWebform()->getElementsInitialized(); $token_value = $view_builder->buildElements($form_elements, $webform_submission, $options, $submission_format); + // Note, tokens can't include CSS and JS libraries since they can be // included in an email. - return \Drupal::service('renderer')->renderPlain($token_value); + $value = \Drupal::service('renderer')->renderPlain($token_value); + + $rendering = FALSE; + + return $value; +} + +/** + * Render webform more element (slideouts) for token descriptions. + * + * @param string $more_title + * More title. + * @param string $more + * More content. + * + * @return string + * Rendered webform more element. + */ +function _webform_token_render_more($more_title, $more) { + $build = [ + '#type' => 'webform_more', + '#more' => $more, + '#more_title' => $more_title, + ]; + return (string) \Drupal::service('renderer')->renderPlain($build); } -- GitLab