diff --git a/config/automated_cron.settings.yml b/config/automated_cron.settings.yml new file mode 100644 index 000000000..2418a00e9 --- /dev/null +++ b/config/automated_cron.settings.yml @@ -0,0 +1,3 @@ +interval: 10800 +_core: + default_config_hash: fUksROt4FfkAU9BV4hV2XvhTBSS2nTNrZS4U7S-tKrs diff --git a/config/block.block.bartik_account_menu.yml b/config/block.block.bartik_account_menu.yml new file mode 100644 index 000000000..d39268335 --- /dev/null +++ b/config/block.block.bartik_account_menu.yml @@ -0,0 +1,26 @@ +uuid: ffee07ae-d5e2-4817-9fa4-2e32ed295d17 +langcode: en +status: true +dependencies: + config: + - system.menu.account + module: + - system + theme: + - bartik +_core: + default_config_hash: DweBpscQZdG0-fHkSpUzdYucrNH45G_KF7Z82V-oyQM +id: bartik_account_menu +theme: bartik +region: secondary_menu +weight: 0 +provider: null +plugin: 'system_menu_block:account' +settings: + id: 'system_menu_block:account' + label: 'User account menu' + provider: system + label_display: '0' + level: 1 + depth: 1 +visibility: { } diff --git a/config/block.block.bartik_branding.yml b/config/block.block.bartik_branding.yml new file mode 100644 index 000000000..474a3ef57 --- /dev/null +++ b/config/block.block.bartik_branding.yml @@ -0,0 +1,25 @@ +uuid: 81735ae1-10f7-4cf8-9f1d-e7eb51df8911 +langcode: en +status: true +dependencies: + module: + - system + theme: + - bartik +_core: + default_config_hash: NDwadleLD3YVSbDUaakxyYZyINYtkFtOVGShfq4kWy8 +id: bartik_branding +theme: bartik +region: header +weight: 0 +provider: null +plugin: system_branding_block +settings: + id: system_branding_block + label: 'Site branding' + provider: system + label_display: '0' + use_site_logo: true + use_site_name: true + use_site_slogan: true +visibility: { } diff --git a/config/block.block.bartik_breadcrumbs.yml b/config/block.block.bartik_breadcrumbs.yml new file mode 100644 index 000000000..149b3b958 --- /dev/null +++ b/config/block.block.bartik_breadcrumbs.yml @@ -0,0 +1,22 @@ +uuid: 790fc185-f306-4751-9491-ae1fd7bd530f +langcode: en +status: true +dependencies: + module: + - system + theme: + - bartik +_core: + default_config_hash: oXUb3JZR2WW5VOdw4HrhRicCsq51mCgLfRyvheG68ck +id: bartik_breadcrumbs +theme: bartik +region: breadcrumb +weight: 0 +provider: null +plugin: system_breadcrumb_block +settings: + id: system_breadcrumb_block + label: Breadcrumbs + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_content.yml b/config/block.block.bartik_content.yml new file mode 100644 index 000000000..11211a23f --- /dev/null +++ b/config/block.block.bartik_content.yml @@ -0,0 +1,22 @@ +uuid: d94caa06-9721-4a4d-8f1f-d8bc952fac02 +langcode: en +status: true +dependencies: + module: + - system + theme: + - bartik +_core: + default_config_hash: 9EoWV2Lot6FVSr50t4hoKgiz1LIXYWNG-IIPYsWxBqo +id: bartik_content +theme: bartik +region: content +weight: 0 +provider: null +plugin: system_main_block +settings: + id: system_main_block + label: 'Main page content' + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_footer.yml b/config/block.block.bartik_footer.yml new file mode 100644 index 000000000..716d0dcde --- /dev/null +++ b/config/block.block.bartik_footer.yml @@ -0,0 +1,26 @@ +uuid: 08958734-c949-4a6c-8990-bb08246d917c +langcode: en +status: true +dependencies: + config: + - system.menu.footer + module: + - system + theme: + - bartik +_core: + default_config_hash: kkISXE1LT2FJEoYnqCrCpeFB-2pmGmMxMklVk7rQcfg +id: bartik_footer +theme: bartik +region: footer_fifth +weight: 0 +provider: null +plugin: 'system_menu_block:footer' +settings: + id: 'system_menu_block:footer' + label: 'Footer menu' + provider: system + label_display: '0' + level: 1 + depth: 0 +visibility: { } diff --git a/config/block.block.bartik_help.yml b/config/block.block.bartik_help.yml new file mode 100644 index 000000000..fc9f4ffde --- /dev/null +++ b/config/block.block.bartik_help.yml @@ -0,0 +1,22 @@ +uuid: 88f10860-4669-44d4-b647-f309f73574c0 +langcode: en +status: true +dependencies: + module: + - help + theme: + - bartik +_core: + default_config_hash: 8I8iACSa0sKO3k3jlvUG1ge52rfcKX7USJAQYnzuBgg +id: bartik_help +theme: bartik +region: content +weight: -30 +provider: null +plugin: help_block +settings: + id: help_block + label: Help + provider: help + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_local_actions.yml b/config/block.block.bartik_local_actions.yml new file mode 100644 index 000000000..99a6cd1ef --- /dev/null +++ b/config/block.block.bartik_local_actions.yml @@ -0,0 +1,20 @@ +uuid: 20e9a6a7-568b-4258-81a4-4df808b5d8fa +langcode: en +status: true +dependencies: + theme: + - bartik +_core: + default_config_hash: 13GQpeITIJsp1kyPniXtWZfyFH87vb1xxJCHifL4UeE +id: bartik_local_actions +theme: bartik +region: content +weight: -20 +provider: null +plugin: local_actions_block +settings: + id: local_actions_block + label: 'Primary admin actions' + provider: core + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_local_tasks.yml b/config/block.block.bartik_local_tasks.yml new file mode 100644 index 000000000..128a77175 --- /dev/null +++ b/config/block.block.bartik_local_tasks.yml @@ -0,0 +1,22 @@ +uuid: 6f60f9c5-7594-4faa-8f03-b2223b0c6ac3 +langcode: en +status: true +dependencies: + theme: + - bartik +_core: + default_config_hash: X9I1OB0W3WlWtrK-CNcg6hNWwa8wficanpH8pYnDZDE +id: bartik_local_tasks +theme: bartik +region: content +weight: -40 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: Tabs + provider: core + label_display: '0' + primary: true + secondary: true +visibility: { } diff --git a/config/block.block.bartik_main_menu.yml b/config/block.block.bartik_main_menu.yml new file mode 100644 index 000000000..01459e04b --- /dev/null +++ b/config/block.block.bartik_main_menu.yml @@ -0,0 +1,26 @@ +uuid: 58ceede6-442d-4ae1-83e8-54a00111acbe +langcode: en +status: true +dependencies: + config: + - system.menu.main + module: + - system + theme: + - bartik +_core: + default_config_hash: rx9IrdDv7Ldc4kpalZAxdhIPZfYIeOMh1N-qKoQZwHo +id: bartik_main_menu +theme: bartik +region: primary_menu +weight: 0 +provider: null +plugin: 'system_menu_block:main' +settings: + id: 'system_menu_block:main' + label: 'Main navigation' + provider: system + label_display: '0' + level: 1 + depth: 1 +visibility: { } diff --git a/config/block.block.bartik_messages.yml b/config/block.block.bartik_messages.yml new file mode 100644 index 000000000..df1068d31 --- /dev/null +++ b/config/block.block.bartik_messages.yml @@ -0,0 +1,22 @@ +uuid: 87b3ff32-1843-4006-b956-51d494429547 +langcode: en +status: true +dependencies: + module: + - system + theme: + - bartik +_core: + default_config_hash: KHQIJ7Vfl25lTjzIc7qIvnuistt-Mw2O0kG4jCofmkI +id: bartik_messages +theme: bartik +region: highlighted +weight: 0 +provider: null +plugin: system_messages_block +settings: + id: system_messages_block + label: 'Status messages' + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_page_title.yml b/config/block.block.bartik_page_title.yml new file mode 100644 index 000000000..2017ee056 --- /dev/null +++ b/config/block.block.bartik_page_title.yml @@ -0,0 +1,20 @@ +uuid: bffa8217-37f2-4b5b-b45d-00585262c643 +langcode: en +status: true +dependencies: + theme: + - bartik +_core: + default_config_hash: 7rR9chwXvdM2H8OYMAYx9Zj3GGlPMrZp_M3ZA4thYTk +id: bartik_page_title +theme: bartik +region: content +weight: -50 +provider: null +plugin: page_title_block +settings: + id: page_title_block + label: 'Page title' + provider: core + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_powered.yml b/config/block.block.bartik_powered.yml new file mode 100644 index 000000000..5b121bee1 --- /dev/null +++ b/config/block.block.bartik_powered.yml @@ -0,0 +1,22 @@ +uuid: 89bfd4e2-b9ad-4b0a-a57f-a91e49f9c03e +langcode: en +status: true +dependencies: + module: + - system + theme: + - bartik +_core: + default_config_hash: jQQUUWN2Uxr5qZtc9zcJKBCxpKY8orN1u2HPqYYRQDI +id: bartik_powered +theme: bartik +region: footer_fifth +weight: 10 +provider: null +plugin: system_powered_by_block +settings: + id: system_powered_by_block + label: 'Powered by Drupal' + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.bartik_search.yml b/config/block.block.bartik_search.yml new file mode 100644 index 000000000..da7176ca9 --- /dev/null +++ b/config/block.block.bartik_search.yml @@ -0,0 +1,22 @@ +uuid: c0010a6e-c654-4cf7-aba6-f1feee5d72fb +langcode: en +status: true +dependencies: + module: + - search + theme: + - bartik +_core: + default_config_hash: za-39d5WDUg6XvbyqSnuVYEeq6QM4qKJxW8MnoAha5A +id: bartik_search +theme: bartik +region: sidebar_first +weight: -1 +provider: null +plugin: search_form_block +settings: + id: search_form_block + label: Search + provider: search + label_display: visible +visibility: { } diff --git a/config/block.block.bartik_tools.yml b/config/block.block.bartik_tools.yml new file mode 100644 index 000000000..f81b094c0 --- /dev/null +++ b/config/block.block.bartik_tools.yml @@ -0,0 +1,26 @@ +uuid: 1df3e393-27a0-49ba-8163-20607cf9af99 +langcode: en +status: true +dependencies: + config: + - system.menu.tools + module: + - system + theme: + - bartik +_core: + default_config_hash: NeHSoqm4XFqA7_0bDmR429ZZQt3LRbZMNRJTMsFyOfI +id: bartik_tools +theme: bartik +region: sidebar_first +weight: 0 +provider: null +plugin: 'system_menu_block:tools' +settings: + id: 'system_menu_block:tools' + label: Tools + provider: system + label_display: visible + level: 1 + depth: 0 +visibility: { } diff --git a/config/block.block.seven_breadcrumbs.yml b/config/block.block.seven_breadcrumbs.yml new file mode 100644 index 000000000..5882d0a07 --- /dev/null +++ b/config/block.block.seven_breadcrumbs.yml @@ -0,0 +1,22 @@ +uuid: 96d1b841-af6b-416e-9bd8-15da9b7cabc0 +langcode: en +status: true +dependencies: + module: + - system + theme: + - seven +_core: + default_config_hash: WWu2OQswgCztl9OeXjD1stexIEMZsSgPMYIdC-JHx9c +id: seven_breadcrumbs +theme: seven +region: breadcrumb +weight: 0 +provider: null +plugin: system_breadcrumb_block +settings: + id: system_breadcrumb_block + label: Breadcrumbs + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_content.yml b/config/block.block.seven_content.yml new file mode 100644 index 000000000..5b3e94a9f --- /dev/null +++ b/config/block.block.seven_content.yml @@ -0,0 +1,22 @@ +uuid: 8d6cc305-d049-4576-9a33-9eeba1c83dcd +langcode: en +status: true +dependencies: + module: + - system + theme: + - seven +_core: + default_config_hash: YRY68JWkaUiGeZlWMv1nzeIgDm0ZZwXYgpqUpLFzwAY +id: seven_content +theme: seven +region: content +weight: 0 +provider: null +plugin: system_main_block +settings: + id: system_main_block + label: 'Main page content' + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_help.yml b/config/block.block.seven_help.yml new file mode 100644 index 000000000..c05d3d0ad --- /dev/null +++ b/config/block.block.seven_help.yml @@ -0,0 +1,22 @@ +uuid: 516f7289-8622-4178-9a5c-362a7601fa71 +langcode: en +status: true +dependencies: + module: + - help + theme: + - seven +_core: + default_config_hash: NU5A_49mwLHfs5xFzMFrZ850w9pgUolxMS9NNF3vv4c +id: seven_help +theme: seven +region: help +weight: 0 +provider: null +plugin: help_block +settings: + id: help_block + label: Help + provider: help + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_local_actions.yml b/config/block.block.seven_local_actions.yml new file mode 100644 index 000000000..38bdb333d --- /dev/null +++ b/config/block.block.seven_local_actions.yml @@ -0,0 +1,20 @@ +uuid: 83c1ba6f-e2be-48f1-8d91-92d5191953d7 +langcode: en +status: true +dependencies: + theme: + - seven +_core: + default_config_hash: HHryZVJbeKi9WnuBGC8FOhBZmBnk2G1H6KxFuy-rC9A +id: seven_local_actions +theme: seven +region: content +weight: -10 +provider: null +plugin: local_actions_block +settings: + id: local_actions_block + label: 'Primary admin actions' + provider: core + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_login.yml b/config/block.block.seven_login.yml new file mode 100644 index 000000000..71ad00719 --- /dev/null +++ b/config/block.block.seven_login.yml @@ -0,0 +1,22 @@ +uuid: 9673746d-4ad9-4251-858a-0733ce2c5323 +langcode: en +status: true +dependencies: + module: + - user + theme: + - seven +_core: + default_config_hash: IItlF4SKHgxduIysVQdvirDJ_v3HGuAviOkidAOJYRE +id: seven_login +theme: seven +region: content +weight: 10 +provider: null +plugin: user_login_block +settings: + id: user_login_block + label: 'User login' + provider: user + label_display: visible +visibility: { } diff --git a/config/block.block.seven_messages.yml b/config/block.block.seven_messages.yml new file mode 100644 index 000000000..8319d1928 --- /dev/null +++ b/config/block.block.seven_messages.yml @@ -0,0 +1,22 @@ +uuid: e00960ed-a17d-4962-877b-55cd9d44e132 +langcode: en +status: true +dependencies: + module: + - system + theme: + - seven +_core: + default_config_hash: XJqWwLt1LDCnazcEN6QkJmCLjk4R0__-8s0OO9xeNjg +id: seven_messages +theme: seven +region: highlighted +weight: 0 +provider: null +plugin: system_messages_block +settings: + id: system_messages_block + label: 'Status messages' + provider: system + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_page_title.yml b/config/block.block.seven_page_title.yml new file mode 100644 index 000000000..fe61521bf --- /dev/null +++ b/config/block.block.seven_page_title.yml @@ -0,0 +1,20 @@ +uuid: cd4e959c-be81-4a5d-9404-f9293d825442 +langcode: en +status: true +dependencies: + theme: + - seven +_core: + default_config_hash: ZSpc3IoSaLd0PkB02nxjVPBMztIdsTdHek9SiGaqZ_c +id: seven_page_title +theme: seven +region: header +weight: -30 +provider: null +plugin: page_title_block +settings: + id: page_title_block + label: 'Page title' + provider: core + label_display: '0' +visibility: { } diff --git a/config/block.block.seven_primary_local_tasks.yml b/config/block.block.seven_primary_local_tasks.yml new file mode 100644 index 000000000..89043fb47 --- /dev/null +++ b/config/block.block.seven_primary_local_tasks.yml @@ -0,0 +1,22 @@ +uuid: b02aa64c-498f-4c43-a92d-e10b41e90911 +langcode: en +status: true +dependencies: + theme: + - seven +_core: + default_config_hash: ddy1OsBbWxjwEI8VL1viD4I69qcLHOkul4BxbTqLBTs +id: seven_primary_local_tasks +theme: seven +region: header +weight: 0 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: 'Primary tabs' + provider: core + label_display: '0' + primary: true + secondary: false +visibility: { } diff --git a/config/block.block.seven_secondary_local_tasks.yml b/config/block.block.seven_secondary_local_tasks.yml new file mode 100644 index 000000000..ee0cd4ce4 --- /dev/null +++ b/config/block.block.seven_secondary_local_tasks.yml @@ -0,0 +1,22 @@ +uuid: 33fbfe43-d655-4971-b4c3-2db131bc7db2 +langcode: en +status: true +dependencies: + theme: + - seven +_core: + default_config_hash: QeZBeCilQfeET3GeW6ZtJkEiwROADTZktFgKWwPieD4 +id: seven_secondary_local_tasks +theme: seven +region: pre_content +weight: 0 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: 'Secondary tabs' + provider: core + label_display: '0' + primary: false + secondary: true +visibility: { } diff --git a/config/block_content.type.basic.yml b/config/block_content.type.basic.yml new file mode 100644 index 000000000..0c3de0510 --- /dev/null +++ b/config/block_content.type.basic.yml @@ -0,0 +1,10 @@ +uuid: 92cd63d5-c687-4b1c-b272-b83e4d2949b8 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: zglzjmYxi0G0ag9MZ02y0LSJOdpWRwJxyP_OvFojFyo +id: basic +label: 'Basic block' +revision: 0 +description: 'A basic block contains a title and a body.' diff --git a/config/comment.type.comment.yml b/config/comment.type.comment.yml new file mode 100644 index 000000000..8dfde36c6 --- /dev/null +++ b/config/comment.type.comment.yml @@ -0,0 +1,10 @@ +uuid: 96870d84-f89f-46be-9c1c-9d28293191c4 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: bqZsN31T2n0UjcbyCpOPi9D2iO0sAOHR7FnEs9qMvaA +id: comment +label: 'Default comments' +target_entity_type_id: node +description: 'Allows commenting on content' diff --git a/config/contact.form.feedback.yml b/config/contact.form.feedback.yml new file mode 100644 index 000000000..dce40061f --- /dev/null +++ b/config/contact.form.feedback.yml @@ -0,0 +1,14 @@ +uuid: f4020e6f-cfd9-46bb-8978-1217f5615fe9 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: vymHlgJy26BuI5GGj9-IXjwR3dRC5C0tij4BpWJnoqw +id: feedback +label: 'Website feedback' +recipients: + - rob@microserve.io +reply: '' +weight: 0 +message: 'Your message has been sent.' +redirect: '' diff --git a/config/contact.form.personal.yml b/config/contact.form.personal.yml new file mode 100644 index 000000000..a9aabe132 --- /dev/null +++ b/config/contact.form.personal.yml @@ -0,0 +1,13 @@ +uuid: 34b9c342-4680-4d75-95f1-cf01bedc8524 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: jonvgt3CkUM2eMLTFwWfHileWWDC4YtXCuIlCahTk_I +id: personal +label: 'Personal contact form' +recipients: { } +reply: '' +weight: 0 +message: 'Your message has been sent.' +redirect: '' diff --git a/config/contact.settings.yml b/config/contact.settings.yml new file mode 100644 index 000000000..a2903f519 --- /dev/null +++ b/config/contact.settings.yml @@ -0,0 +1,7 @@ +default_form: feedback +flood: + limit: 5 + interval: 3600 +user_default_enabled: true +_core: + default_config_hash: U69DBeuvXuNVOC15rVNaBjDPK2fWFbo9v4takdYSSO8 diff --git a/config/core.base_field_override.node.page.promote.yml b/config/core.base_field_override.node.page.promote.yml new file mode 100644 index 000000000..66c719819 --- /dev/null +++ b/config/core.base_field_override.node.page.promote.yml @@ -0,0 +1,24 @@ +uuid: 183b015a-cd8c-42ba-a772-51f7f9796555 +langcode: en +status: true +dependencies: + config: + - node.type.page +_core: + default_config_hash: fPUEnm4T5zfZRr3ttDUqq7yCDd2uW3clWD-pvos4tlQ +id: node.page.promote +field_name: promote +entity_type: node +bundle: page +label: 'Promoted to front page' +description: '' +required: false +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: + on_label: 'On' + off_label: 'Off' +field_type: boolean diff --git a/config/core.date_format.fallback.yml b/config/core.date_format.fallback.yml new file mode 100644 index 000000000..9194af150 --- /dev/null +++ b/config/core.date_format.fallback.yml @@ -0,0 +1,10 @@ +uuid: 40223717-096b-4c11-a72f-a6cc324679d8 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: 7klS5IWXrwzVaPpYZFAs6wcx8U2FF1X73OfrtTsvuvE +id: fallback +label: 'Fallback date format' +locked: true +pattern: 'D, m/d/Y - H:i' diff --git a/config/core.date_format.html_date.yml b/config/core.date_format.html_date.yml new file mode 100644 index 000000000..39078d655 --- /dev/null +++ b/config/core.date_format.html_date.yml @@ -0,0 +1,10 @@ +uuid: faf44161-ca28-4731-983e-6f5900dd5343 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: EOQltUQPmgc6UQ2rcJ4Xi_leCEJj5ui0TR-12duS-Tk +id: html_date +label: 'HTML Date' +locked: true +pattern: Y-m-d diff --git a/config/core.date_format.html_datetime.yml b/config/core.date_format.html_datetime.yml new file mode 100644 index 000000000..6f3d680b9 --- /dev/null +++ b/config/core.date_format.html_datetime.yml @@ -0,0 +1,10 @@ +uuid: e16e4344-71c6-4b5f-86ea-0e9fc00d303b +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: jxfClwZIRXIdcvMrE--WkcZxDGUVoOIE3Sm2NRZlFuE +id: html_datetime +label: 'HTML Datetime' +locked: true +pattern: 'Y-m-d\TH:i:sO' diff --git a/config/core.date_format.html_month.yml b/config/core.date_format.html_month.yml new file mode 100644 index 000000000..dd5911065 --- /dev/null +++ b/config/core.date_format.html_month.yml @@ -0,0 +1,10 @@ +uuid: fecce250-81ee-4646-802e-22cbeb13d68b +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: Z7KuCUwM_WdTNvLcoltuX3_8d-s-8FZkTN6KgNwF0eM +id: html_month +label: 'HTML Month' +locked: true +pattern: Y-m diff --git a/config/core.date_format.html_time.yml b/config/core.date_format.html_time.yml new file mode 100644 index 000000000..b82105834 --- /dev/null +++ b/config/core.date_format.html_time.yml @@ -0,0 +1,10 @@ +uuid: 775b04d0-1cbb-4aff-bd75-d14e6e2e3ff3 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: M7yqicYkU36hRy5p9drAaGBBihhUD1OyujFrAaQ93ZE +id: html_time +label: 'HTML Time' +locked: true +pattern: 'H:i:s' diff --git a/config/core.date_format.html_week.yml b/config/core.date_format.html_week.yml new file mode 100644 index 000000000..c82c4413f --- /dev/null +++ b/config/core.date_format.html_week.yml @@ -0,0 +1,10 @@ +uuid: 7845dd68-dd31-44d0-8dfd-c85bd20d33b3 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: wKD4WsoV_wFgv2vgI4mcAAFSIzrye17ykzdwrnApkfY +id: html_week +label: 'HTML Week' +locked: true +pattern: Y-\WW diff --git a/config/core.date_format.html_year.yml b/config/core.date_format.html_year.yml new file mode 100644 index 000000000..9845a2586 --- /dev/null +++ b/config/core.date_format.html_year.yml @@ -0,0 +1,10 @@ +uuid: ab3539d8-879d-4b4a-adc6-b4cc6301c889 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: OjekiQuX9RbVQ2_8jOHBL94RgYLePqX7wpfNGgcQzrk +id: html_year +label: 'HTML Year' +locked: true +pattern: 'Y' diff --git a/config/core.date_format.html_yearless_date.yml b/config/core.date_format.html_yearless_date.yml new file mode 100644 index 000000000..d1cf5292d --- /dev/null +++ b/config/core.date_format.html_yearless_date.yml @@ -0,0 +1,10 @@ +uuid: 87c68e2f-ed7d-4c9f-9937-18571c4d6017 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: 5VpawMrKPEPCkoO4YpPa0TDFO2dgiIHfTziJtwlmUxc +id: html_yearless_date +label: 'HTML Yearless date' +locked: true +pattern: m-d diff --git a/config/core.date_format.long.yml b/config/core.date_format.long.yml new file mode 100644 index 000000000..965c8380e --- /dev/null +++ b/config/core.date_format.long.yml @@ -0,0 +1,10 @@ +uuid: 97b1b8d8-ebd7-457d-a68d-e8af20674d64 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: og8sWXhBuHbLMw3CoiBEZjgqSyhFBFmcbUW_wLcfNbo +id: long +label: 'Default long date' +locked: false +pattern: 'l, F j, Y - H:i' diff --git a/config/core.date_format.medium.yml b/config/core.date_format.medium.yml new file mode 100644 index 000000000..45d6c86c0 --- /dev/null +++ b/config/core.date_format.medium.yml @@ -0,0 +1,10 @@ +uuid: 619c46d1-82cb-416d-8309-3fcb3df30188 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: nzL5d024NjXIX_8TlT6uFAu973lmfkmHklJC-2i9rAE +id: medium +label: 'Default medium date' +locked: false +pattern: 'D, m/d/Y - H:i' diff --git a/config/core.date_format.short.yml b/config/core.date_format.short.yml new file mode 100644 index 000000000..15bc25be6 --- /dev/null +++ b/config/core.date_format.short.yml @@ -0,0 +1,10 @@ +uuid: 81dc7917-ec4f-4672-8f6c-735d7bf1b5c0 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: AlzeyytA8InBgxIG9H2UDJYs3CG98Zj6yRsDKmlbZwA +id: short +label: 'Default short date' +locked: false +pattern: 'm/d/Y - H:i' diff --git a/config/core.entity_form_display.block_content.basic.default.yml b/config/core.entity_form_display.block_content.basic.default.yml new file mode 100644 index 000000000..237fb09d6 --- /dev/null +++ b/config/core.entity_form_display.block_content.basic.default.yml @@ -0,0 +1,32 @@ +uuid: ec5ced30-37b9-44c0-96b4-255e9ec45ec7 +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + module: + - text +_core: + default_config_hash: kv6gwb6PJ8DcMSj5kgsd52qCIHl1xkxBDL5_AMm-g8k +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + type: text_textarea_with_summary + weight: -4 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + info: + type: string_textfield + weight: -5 + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/config/core.entity_form_display.comment.comment.default.yml b/config/core.entity_form_display.comment.comment.default.yml new file mode 100644 index 000000000..2013a9703 --- /dev/null +++ b/config/core.entity_form_display.comment.comment.default.yml @@ -0,0 +1,33 @@ +uuid: 0cc95319-0943-4c5b-8370-7259fc3c5fc0 +langcode: en +status: true +dependencies: + config: + - comment.type.comment + - field.field.comment.comment.comment_body + module: + - text +_core: + default_config_hash: ooVjelXSXbiQtp-hYb7LS44vR5UO-Kqu4vf484ggR8w +id: comment.comment.default +targetEntityType: comment +bundle: comment +mode: default +content: + author: + weight: -2 + comment_body: + type: text_textarea + weight: 11 + settings: + rows: 5 + placeholder: '' + third_party_settings: { } + subject: + type: string_textfield + weight: 10 + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/config/core.entity_form_display.node.article.default.yml b/config/core.entity_form_display.node.article.default.yml new file mode 100644 index 000000000..e18d0d1c2 --- /dev/null +++ b/config/core.entity_form_display.node.article.default.yml @@ -0,0 +1,86 @@ +uuid: e9f42aa0-6127-456d-ae80-07eaead6468f +langcode: en +status: true +dependencies: + config: + - field.field.node.article.body + - field.field.node.article.comment + - field.field.node.article.field_image + - field.field.node.article.field_tags + - image.style.thumbnail + - node.type.article + module: + - comment + - image + - path + - text +_core: + default_config_hash: 7CvVDwPYotAZFBa36mcpMKZOfVRRp_iGuPHpHaavI6E +id: node.article.default +targetEntityType: node +bundle: article +mode: default +content: + body: + type: text_textarea_with_summary + weight: 1 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + comment: + type: comment_default + weight: 20 + settings: { } + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + settings: { } + third_party_settings: { } + field_image: + type: image_image + weight: 4 + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + field_tags: + type: entity_reference_autocomplete_tags + weight: 3 + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 15 + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 16 + third_party_settings: { } + title: + type: string_textfield + weight: 0 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/config/core.entity_form_display.node.page.default.yml b/config/core.entity_form_display.node.page.default.yml new file mode 100644 index 000000000..b4b21d2f3 --- /dev/null +++ b/config/core.entity_form_display.node.page.default.yml @@ -0,0 +1,63 @@ +uuid: 7f41213f-da7d-4ba9-85b2-8d85f6a106a0 +langcode: en +status: true +dependencies: + config: + - field.field.node.page.body + - node.type.page + module: + - path + - text +_core: + default_config_hash: KSvzyFhdLuxniTsunUnUfpfMmod2l57GibtVjL8ymHM +id: node.page.default +targetEntityType: node +bundle: page +mode: default +content: + body: + type: text_textarea_with_summary + weight: 31 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 15 + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 16 + third_party_settings: { } + title: + type: string_textfield + weight: -5 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/config/core.entity_form_display.user.user.default.yml b/config/core.entity_form_display.user.user.default.yml new file mode 100644 index 000000000..e5fe601b3 --- /dev/null +++ b/config/core.entity_form_display.user.user.default.yml @@ -0,0 +1,33 @@ +uuid: 41229a43-eee1-430b-8fb1-17c45f8a6886 +langcode: en +status: true +dependencies: + config: + - field.field.user.user.user_picture + - image.style.thumbnail + module: + - image + - user +_core: + default_config_hash: LLAieeozVsoZDb-2PbFxRJpQqnKmpR7-4OoRJnduz-U +id: user.user.default +targetEntityType: user +bundle: user +mode: default +content: + account: + weight: -10 + contact: + weight: 5 + language: + weight: 0 + timezone: + weight: 6 + user_picture: + type: image_image + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + weight: -1 +hidden: { } diff --git a/config/core.entity_form_mode.user.register.yml b/config/core.entity_form_mode.user.register.yml new file mode 100644 index 000000000..415ee5b12 --- /dev/null +++ b/config/core.entity_form_mode.user.register.yml @@ -0,0 +1,12 @@ +uuid: 76d28558-ecbe-4908-acd3-1367ab0a1f36 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: flXhTcp55yLcyy7ZLOhPGKGZobZQJdkAFVWV3LseiuI +id: user.register +label: Register +targetEntityType: user +cache: true diff --git a/config/core.entity_view_display.block_content.basic.default.yml b/config/core.entity_view_display.block_content.basic.default.yml new file mode 100644 index 000000000..9895f399b --- /dev/null +++ b/config/core.entity_view_display.block_content.basic.default.yml @@ -0,0 +1,23 @@ +uuid: 3457b452-e1cc-481d-ac57-d4c327baf495 +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + module: + - text +_core: + default_config_hash: zCrrHAjrsQgWjBXBsshAZjND21czBy8sH_L5v_xDKSA +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + label: hidden + type: text_default + weight: 0 + settings: { } + third_party_settings: { } +hidden: { } diff --git a/config/core.entity_view_display.comment.comment.default.yml b/config/core.entity_view_display.comment.comment.default.yml new file mode 100644 index 000000000..0f1c06808 --- /dev/null +++ b/config/core.entity_view_display.comment.comment.default.yml @@ -0,0 +1,25 @@ +uuid: b7813145-c7cc-4bf4-8d5c-1659c4c0922a +langcode: en +status: true +dependencies: + config: + - comment.type.comment + - field.field.comment.comment.comment_body + module: + - text +_core: + default_config_hash: 1yBeJcGufCbnbSolmaYgTIXZWYUaO7kw6xszGA8TYs8 +id: comment.comment.default +targetEntityType: comment +bundle: comment +mode: default +content: + comment_body: + label: hidden + type: text_default + weight: 0 + settings: { } + third_party_settings: { } + links: + weight: 100 +hidden: { } diff --git a/config/core.entity_view_display.node.article.default.yml b/config/core.entity_view_display.node.article.default.yml new file mode 100644 index 000000000..c8c8d4d25 --- /dev/null +++ b/config/core.entity_view_display.node.article.default.yml @@ -0,0 +1,58 @@ +uuid: 92d5d9a7-8d05-4ecd-92e6-c49285c1f84d +langcode: en +status: true +dependencies: + config: + - core.entity_view_display.comment.comment.default + - field.field.node.article.body + - field.field.node.article.comment + - field.field.node.article.field_image + - field.field.node.article.field_tags + - image.style.large + - node.type.article + module: + - comment + - image + - text + - user +_core: + default_config_hash: 1x2VMLBhwpRPa-_Q_6J_ZN4QNazwCmP1dt01Zc-Iors +id: node.article.default +targetEntityType: node +bundle: article +mode: default +content: + body: + type: text_default + weight: 0 + settings: { } + third_party_settings: { } + label: hidden + comment: + type: comment_default + weight: 110 + label: above + settings: + view_mode: default + pager_id: 0 + third_party_settings: { } + field_image: + type: image + weight: -1 + settings: + image_style: large + image_link: '' + third_party_settings: { } + label: hidden + field_tags: + type: entity_reference_label + weight: 10 + label: above + settings: + link: true + third_party_settings: { } + links: + weight: 100 +hidden: + field_image: true + field_tags: true diff --git a/config/core.entity_view_display.node.article.rss.yml b/config/core.entity_view_display.node.article.rss.yml new file mode 100644 index 000000000..f6d165215 --- /dev/null +++ b/config/core.entity_view_display.node.article.rss.yml @@ -0,0 +1,27 @@ +uuid: afb826a5-df77-4ed2-ac9d-45a434b96da2 +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.rss + - field.field.node.article.body + - field.field.node.article.comment + - field.field.node.article.field_image + - field.field.node.article.field_tags + - node.type.article + module: + - user +_core: + default_config_hash: Q90X1FmFdOkTx8udckWPoAgleOstOerbgDnNpoP6PO4 +id: node.article.rss +targetEntityType: node +bundle: article +mode: rss +content: + links: + weight: 100 +hidden: + body: true + comment: true + field_image: true + field_tags: true diff --git a/config/core.entity_view_display.node.article.teaser.yml b/config/core.entity_view_display.node.article.teaser.yml new file mode 100644 index 000000000..e156e3acd --- /dev/null +++ b/config/core.entity_view_display.node.article.teaser.yml @@ -0,0 +1,51 @@ +uuid: 41ad0129-d626-4b84-8df7-6bd067f9615b +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.article.body + - field.field.node.article.comment + - field.field.node.article.field_image + - field.field.node.article.field_tags + - image.style.medium + - node.type.article + module: + - image + - text + - user +_core: + default_config_hash: U8ghxJXn8JE2p6Q2wLpPWAxpz4r_8crL1LxtapSrHAE +id: node.article.teaser +targetEntityType: node +bundle: article +mode: teaser +content: + body: + type: text_summary_or_trimmed + weight: 0 + settings: + trim_length: 600 + third_party_settings: { } + label: hidden + field_image: + type: image + weight: -1 + settings: + image_style: medium + image_link: content + third_party_settings: { } + label: hidden + field_tags: + type: entity_reference_label + weight: 10 + settings: + link: true + third_party_settings: { } + label: above + links: + weight: 100 +hidden: + comment: true + field_image: true + field_tags: true diff --git a/config/core.entity_view_display.node.page.default.yml b/config/core.entity_view_display.node.page.default.yml new file mode 100644 index 000000000..7e91005a5 --- /dev/null +++ b/config/core.entity_view_display.node.page.default.yml @@ -0,0 +1,26 @@ +uuid: a946f6d7-23d8-4c2c-a23e-92c894216afb +langcode: en +status: true +dependencies: + config: + - field.field.node.page.body + - node.type.page + module: + - text + - user +_core: + default_config_hash: oZ-7vpIJxjxL2up9B5KrJGD0lazQ9aN0P-fIPo6OrSU +id: node.page.default +targetEntityType: node +bundle: page +mode: default +content: + body: + label: hidden + type: text_default + weight: 100 + settings: { } + third_party_settings: { } + links: + weight: 101 +hidden: { } diff --git a/config/core.entity_view_display.node.page.teaser.yml b/config/core.entity_view_display.node.page.teaser.yml new file mode 100644 index 000000000..712014dd7 --- /dev/null +++ b/config/core.entity_view_display.node.page.teaser.yml @@ -0,0 +1,28 @@ +uuid: dcc0ee9a-0be0-43ed-be45-fa3707c2000f +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.page.body + - node.type.page + module: + - text + - user +_core: + default_config_hash: rN2zrScZAJ9xB16hm_Deb9QJKy6JXnn0PdI2n5Z3KRs +id: node.page.teaser +targetEntityType: node +bundle: page +mode: teaser +content: + body: + label: hidden + type: text_summary_or_trimmed + weight: 100 + settings: + trim_length: 600 + third_party_settings: { } + links: + weight: 101 +hidden: { } diff --git a/config/core.entity_view_display.user.user.compact.yml b/config/core.entity_view_display.user.user.compact.yml new file mode 100644 index 000000000..e17a67138 --- /dev/null +++ b/config/core.entity_view_display.user.user.compact.yml @@ -0,0 +1,28 @@ +uuid: cdb893fb-9113-4660-bb15-f49524f806ea +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.user.compact + - field.field.user.user.user_picture + - image.style.thumbnail + module: + - image + - user +_core: + default_config_hash: C3k_McOy8bL8rTnIjspy5OfFdgqV1z6OdGZaI-tO5eM +id: user.user.compact +targetEntityType: user +bundle: user +mode: compact +content: + user_picture: + type: image + weight: 0 + settings: + image_style: thumbnail + image_link: content + third_party_settings: { } + label: hidden +hidden: + member_for: true diff --git a/config/core.entity_view_display.user.user.default.yml b/config/core.entity_view_display.user.user.default.yml new file mode 100644 index 000000000..2d56f7a8f --- /dev/null +++ b/config/core.entity_view_display.user.user.default.yml @@ -0,0 +1,28 @@ +uuid: 6ef2d955-198d-4b62-b541-145df31c5d76 +langcode: en +status: true +dependencies: + config: + - field.field.user.user.user_picture + - image.style.thumbnail + module: + - image + - user +_core: + default_config_hash: L2mtwGWH_7wDRCMIR4r_Iu_jmvQ10DV1L8ht8iNZ5qY +id: user.user.default +targetEntityType: user +bundle: user +mode: default +content: + member_for: + weight: 5 + user_picture: + type: image + weight: 0 + settings: + image_style: thumbnail + image_link: content + third_party_settings: { } + label: hidden +hidden: { } diff --git a/config/core.entity_view_mode.block_content.full.yml b/config/core.entity_view_mode.block_content.full.yml new file mode 100644 index 000000000..2f6aef616 --- /dev/null +++ b/config/core.entity_view_mode.block_content.full.yml @@ -0,0 +1,12 @@ +uuid: 814cab74-05a7-4e62-bfe3-de20d1bb82a6 +langcode: en +status: false +dependencies: + module: + - block_content +_core: + default_config_hash: 4tedlMuvQjDOdvHdw86_e-2Rt78aR7TGFMfOK8Ejppg +id: block_content.full +label: Full +targetEntityType: block_content +cache: true diff --git a/config/core.entity_view_mode.comment.full.yml b/config/core.entity_view_mode.comment.full.yml new file mode 100644 index 000000000..266faa1f4 --- /dev/null +++ b/config/core.entity_view_mode.comment.full.yml @@ -0,0 +1,12 @@ +uuid: d7d87ed6-2a31-4f92-88d3-fe8ff35da883 +langcode: en +status: false +dependencies: + module: + - comment +_core: + default_config_hash: K7eNlfU7NEUajz01wItywZklr2oaPgL6s1_97fmDXLA +id: comment.full +label: 'Full comment' +targetEntityType: comment +cache: true diff --git a/config/core.entity_view_mode.node.full.yml b/config/core.entity_view_mode.node.full.yml new file mode 100644 index 000000000..396765817 --- /dev/null +++ b/config/core.entity_view_mode.node.full.yml @@ -0,0 +1,12 @@ +uuid: f9e6d18e-8005-4be1-959a-9ebc492dd394 +langcode: en +status: false +dependencies: + module: + - node +_core: + default_config_hash: ElrtInxGjZd7GaapJ5O9n-ugi2hG2IxFivtgn0tHOsk +id: node.full +label: 'Full content' +targetEntityType: node +cache: true diff --git a/config/core.entity_view_mode.node.rss.yml b/config/core.entity_view_mode.node.rss.yml new file mode 100644 index 000000000..aea9e3b94 --- /dev/null +++ b/config/core.entity_view_mode.node.rss.yml @@ -0,0 +1,12 @@ +uuid: d621410a-8f24-4d02-b5dc-afddc0b016f5 +langcode: en +status: false +dependencies: + module: + - node +_core: + default_config_hash: vlYzr-rp2f9NMp-Qlr4sFjlqRq-90mco5-afLNGwCrU +id: node.rss +label: RSS +targetEntityType: node +cache: true diff --git a/config/core.entity_view_mode.node.search_index.yml b/config/core.entity_view_mode.node.search_index.yml new file mode 100644 index 000000000..c12f2a48f --- /dev/null +++ b/config/core.entity_view_mode.node.search_index.yml @@ -0,0 +1,12 @@ +uuid: 8037604a-076b-4b74-b755-28cb8da29412 +langcode: en +status: false +dependencies: + module: + - node +_core: + default_config_hash: fVFfJv_GzBRE-wpRHbfD5a3VjnhbEOXG6lvRd3uaccY +id: node.search_index +label: 'Search index' +targetEntityType: node +cache: true diff --git a/config/core.entity_view_mode.node.search_result.yml b/config/core.entity_view_mode.node.search_result.yml new file mode 100644 index 000000000..d9dbd62de --- /dev/null +++ b/config/core.entity_view_mode.node.search_result.yml @@ -0,0 +1,12 @@ +uuid: 96951798-ad4f-4fca-9949-5946c80e207b +langcode: en +status: false +dependencies: + module: + - node +_core: + default_config_hash: 6GCOQ-jP2RbdbHA5YWQ6bT8CfGbqrBYKOSC_XY4E3ZM +id: node.search_result +label: 'Search result highlighting input' +targetEntityType: node +cache: true diff --git a/config/core.entity_view_mode.node.teaser.yml b/config/core.entity_view_mode.node.teaser.yml new file mode 100644 index 000000000..0e4a502e8 --- /dev/null +++ b/config/core.entity_view_mode.node.teaser.yml @@ -0,0 +1,12 @@ +uuid: 46fde0d4-9122-4d34-8807-b01ac36a2013 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: Mz9qWr1kUYK0mjRAGDsr5XS6PvtZ24en_7ndt-pyWe4 +id: node.teaser +label: Teaser +targetEntityType: node +cache: true diff --git a/config/core.entity_view_mode.taxonomy_term.full.yml b/config/core.entity_view_mode.taxonomy_term.full.yml new file mode 100644 index 000000000..dfe58d41d --- /dev/null +++ b/config/core.entity_view_mode.taxonomy_term.full.yml @@ -0,0 +1,12 @@ +uuid: aac94942-18de-470d-8ef1-9a13098150e1 +langcode: en +status: true +dependencies: + module: + - taxonomy +_core: + default_config_hash: '-PPKjsNQPvoIDjOuUAvlLocYD976MNjb9Zpgyz5_BWE' +id: taxonomy_term.full +label: 'Taxonomy term page' +targetEntityType: taxonomy_term +cache: true diff --git a/config/core.entity_view_mode.user.compact.yml b/config/core.entity_view_mode.user.compact.yml new file mode 100644 index 000000000..59cef8bc0 --- /dev/null +++ b/config/core.entity_view_mode.user.compact.yml @@ -0,0 +1,12 @@ +uuid: 862418f4-73de-41ad-aa5a-c4411647579a +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: 71CSAr_LNPcgu6D6jI4INl1KATkahmeyUFBETAWya8g +id: user.compact +label: Compact +targetEntityType: user +cache: true diff --git a/config/core.entity_view_mode.user.full.yml b/config/core.entity_view_mode.user.full.yml new file mode 100644 index 000000000..d50e7b6e3 --- /dev/null +++ b/config/core.entity_view_mode.user.full.yml @@ -0,0 +1,12 @@ +uuid: f1e0c4fd-c1a7-4f89-9114-79f8959c8c3b +langcode: en +status: false +dependencies: + module: + - user +_core: + default_config_hash: mQIF_foYjmnVSr9MpcD4CTaJE_FpO1AyDd_DskztGhM +id: user.full +label: 'User account' +targetEntityType: user +cache: true diff --git a/config/core.extension.yml b/config/core.extension.yml new file mode 100644 index 000000000..a310a267f --- /dev/null +++ b/config/core.extension.yml @@ -0,0 +1,51 @@ +module: + automated_cron: 0 + block: 0 + block_content: 0 + breakpoint: 0 + ckeditor: 0 + color: 0 + comment: 0 + config: 0 + contact: 0 + contextual: 0 + datetime: 0 + dblog: 0 + dynamic_page_cache: 0 + editor: 0 + field: 0 + field_ui: 0 + file: 0 + filter: 0 + help: 0 + history: 0 + image: 0 + link: 0 + menu_ui: 0 + node: 0 + options: 0 + page_cache: 0 + path: 0 + quickedit: 0 + rdf: 0 + search: 0 + shortcut: 0 + system: 0 + taxonomy: 0 + text: 0 + toolbar: 0 + tour: 0 + update: 0 + user: 0 + views_ui: 0 + webform: 0 + menu_link_content: 1 + views: 10 + standard: 1000 +theme: + stable: 0 + classy: 0 + bartik: 0 + seven: 0 +_core: + default_config_hash: m2GVq11UAOVuNgj8x0t5fMOPujpvQQ_zxLoaly1BMEU diff --git a/config/core.menu.static_menu_link_overrides.yml b/config/core.menu.static_menu_link_overrides.yml new file mode 100644 index 000000000..86242582c --- /dev/null +++ b/config/core.menu.static_menu_link_overrides.yml @@ -0,0 +1,9 @@ +definitions: + contact__site_page: + enabled: true + menu_name: footer + parent: '' + weight: 0 + expanded: false +_core: + default_config_hash: jdY7AU0tU-QsjmiOw3W8vwpYMb-By--_MSFgbqKUTYM diff --git a/config/dblog.settings.yml b/config/dblog.settings.yml new file mode 100644 index 000000000..cf06e3fa7 --- /dev/null +++ b/config/dblog.settings.yml @@ -0,0 +1,3 @@ +row_limit: 1000 +_core: + default_config_hash: e883aGsrt1wFrsydlYU584PZONCSfRy0DtkZ9KzHb58 diff --git a/config/editor.editor.basic_html.yml b/config/editor.editor.basic_html.yml new file mode 100644 index 000000000..00c94058a --- /dev/null +++ b/config/editor.editor.basic_html.yml @@ -0,0 +1,55 @@ +uuid: 36b8ebd4-782b-428f-9dd9-92fd047bbe29 +langcode: en +status: true +dependencies: + config: + - filter.format.basic_html + module: + - ckeditor +_core: + default_config_hash: AqlPmO16LvJI4D0Ih6u4GFQIzqr5OnLgAUSjcUGWk2g +format: basic_html +editor: ckeditor +settings: + toolbar: + rows: + - + - + name: Formatting + items: + - Bold + - Italic + - + name: Linking + items: + - DrupalLink + - DrupalUnlink + - + name: Lists + items: + - BulletedList + - NumberedList + - + name: Media + items: + - Blockquote + - DrupalImage + - + name: 'Block Formatting' + items: + - Format + - + name: Tools + items: + - Source + plugins: + stylescombo: + styles: '' +image_upload: + status: true + scheme: public + directory: inline-images + max_size: '' + max_dimensions: + width: 0 + height: 0 diff --git a/config/editor.editor.full_html.yml b/config/editor.editor.full_html.yml new file mode 100644 index 000000000..72fc2141e --- /dev/null +++ b/config/editor.editor.full_html.yml @@ -0,0 +1,63 @@ +uuid: c06d6362-4b35-48fe-b564-d33663bf5379 +langcode: en +status: true +dependencies: + config: + - filter.format.full_html + module: + - ckeditor +_core: + default_config_hash: 967ijj7p6i7rwrYl7r08WQFeCY_c23YAh0h8u-w_CXM +format: full_html +editor: ckeditor +settings: + toolbar: + rows: + - + - + name: Formatting + items: + - Bold + - Italic + - Strike + - Superscript + - Subscript + - '-' + - RemoveFormat + - + name: Linking + items: + - DrupalLink + - DrupalUnlink + - + name: Lists + items: + - BulletedList + - NumberedList + - + name: Media + items: + - Blockquote + - DrupalImage + - Table + - HorizontalRule + - + name: 'Block Formatting' + items: + - Format + - + name: Tools + items: + - ShowBlocks + - Source + plugins: + stylescombo: + styles: '' +image_upload: + status: true + scheme: public + directory: inline-images + max_size: '' + max_dimensions: + width: 0 + height: 0 diff --git a/config/field.field.block_content.basic.body.yml b/config/field.field.block_content.basic.body.yml new file mode 100644 index 000000000..992bdd3fe --- /dev/null +++ b/config/field.field.block_content.basic.body.yml @@ -0,0 +1,24 @@ +uuid: a1692174-923b-4112-a20d-fa7c52519000 +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.body + module: + - text +_core: + default_config_hash: R__6wc-rMfFMO8d7jcgqnqiA92j8spKhcc5MiqecrMc +id: block_content.basic.body +field_name: body +entity_type: block_content +bundle: basic +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: false +field_type: text_with_summary diff --git a/config/field.field.comment.comment.comment_body.yml b/config/field.field.comment.comment.comment_body.yml new file mode 100644 index 000000000..9ea64fb98 --- /dev/null +++ b/config/field.field.comment.comment.comment_body.yml @@ -0,0 +1,23 @@ +uuid: d0559440-ff94-40c4-bbb1-bf9d41e78a69 +langcode: en +status: true +dependencies: + config: + - comment.type.comment + - field.storage.comment.comment_body + module: + - text +_core: + default_config_hash: TmAKjNrJ7RR60YpqvJq_QqEewYe_S8Kd23n8VRCqiWs +id: comment.comment.comment_body +field_name: comment_body +entity_type: comment +bundle: comment +label: Comment +description: '' +required: true +translatable: true +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/config/field.field.node.article.body.yml b/config/field.field.node.article.body.yml new file mode 100644 index 000000000..34cdef909 --- /dev/null +++ b/config/field.field.node.article.body.yml @@ -0,0 +1,24 @@ +uuid: 6576038c-8ad0-49ad-8b0c-465674fdb62d +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.article + module: + - text +_core: + default_config_hash: Ay3b2hq42cpQTFB_lNu8S2ZxuVIY6-dlBsc7vLeJ-YY +id: node.article.body +field_name: body +entity_type: node +bundle: article +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +field_type: text_with_summary diff --git a/config/field.field.node.article.comment.yml b/config/field.field.node.article.comment.yml new file mode 100644 index 000000000..8cdd06e51 --- /dev/null +++ b/config/field.field.node.article.comment.yml @@ -0,0 +1,35 @@ +uuid: f539a4c9-f9ef-4bcc-aa3c-a6410d2d711a +langcode: en +status: true +dependencies: + config: + - field.storage.node.comment + - node.type.article + module: + - comment +_core: + default_config_hash: szSOp-8eNsMcuh8usDre1x76RhDKaPhNUuwzRX5cgv4 +id: node.article.comment +field_name: comment +entity_type: node +bundle: article +label: Comments +description: '' +required: true +translatable: true +default_value: + - + status: 2 + cid: 0 + last_comment_name: null + last_comment_timestamp: 0 + last_comment_uid: 0 + comment_count: 0 +default_value_callback: '' +settings: + default_mode: 1 + per_page: 50 + form_location: true + anonymous: 0 + preview: 1 +field_type: comment diff --git a/config/field.field.node.article.field_image.yml b/config/field.field.node.article.field_image.yml new file mode 100644 index 000000000..7b1cd5b72 --- /dev/null +++ b/config/field.field.node.article.field_image.yml @@ -0,0 +1,40 @@ +uuid: 03c959b8-e42f-4ba8-80cd-81edc38463ce +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_image + - node.type.article + module: + - image +_core: + default_config_hash: tgJzhA7Swh4M_gWU5FwFe5lPxPj5rebpMbvhpdNrERs +id: node.article.field_image +field_name: field_image +entity_type: node +bundle: article +label: Image +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + title_field: false + alt_field_required: true + title_field_required: false + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/config/field.field.node.article.field_tags.yml b/config/field.field.node.article.field_tags.yml new file mode 100644 index 000000000..7069949be --- /dev/null +++ b/config/field.field.node.article.field_tags.yml @@ -0,0 +1,29 @@ +uuid: 6a006011-7846-4207-808b-56ebb7470fae +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_tags + - node.type.article + - taxonomy.vocabulary.tags +_core: + default_config_hash: QdUgf_beeoaPiyKorFv0q1fcJpWH_uZTqe_xoVJacrw +id: node.article.field_tags +field_name: field_tags +entity_type: node +bundle: article +label: Tags +description: 'Enter a comma-separated list. For example: Amsterdam, Mexico City, "Cleveland, Ohio"' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + handler: 'default:taxonomy_term' + handler_settings: + target_bundles: + tags: tags + sort: + field: _none + auto_create: true +field_type: entity_reference diff --git a/config/field.field.node.page.body.yml b/config/field.field.node.page.body.yml new file mode 100644 index 000000000..fbbc367c7 --- /dev/null +++ b/config/field.field.node.page.body.yml @@ -0,0 +1,24 @@ +uuid: dc647a1e-f922-4817-843f-a954dad5b94d +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.page + module: + - text +_core: + default_config_hash: rUop-8b6hvxxDYbv-KobTfNIBNbPY9qOPl8f6kBNSpw +id: node.page.body +field_name: body +entity_type: node +bundle: page +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +field_type: text_with_summary diff --git a/config/field.field.user.user.user_picture.yml b/config/field.field.user.user.user_picture.yml new file mode 100644 index 000000000..5c9bdd8cc --- /dev/null +++ b/config/field.field.user.user.user_picture.yml @@ -0,0 +1,40 @@ +uuid: c41d5903-fe47-41b0-b6f4-9f0496ed6ac2 +langcode: en +status: true +dependencies: + config: + - field.storage.user.user_picture + module: + - image + - user +_core: + default_config_hash: cL7i1kgJvlJa6H00f0E_fZ2KdD1ag0ASpLts0K-KNII +id: user.user.user_picture +field_name: user_picture +entity_type: user +bundle: user +label: Picture +description: 'Your virtual face or picture.' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_extensions: 'png gif jpg jpeg' + file_directory: 'pictures/[date:custom:Y]-[date:custom:m]' + max_filesize: '30 KB' + alt_field: false + title_field: false + max_resolution: 85x85 + min_resolution: '' + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + alt_field_required: false + title_field_required: false + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/config/field.settings.yml b/config/field.settings.yml new file mode 100644 index 000000000..95c042d77 --- /dev/null +++ b/config/field.settings.yml @@ -0,0 +1,3 @@ +purge_batch_size: 50 +_core: + default_config_hash: nJk0TAQBzlNo52ehiHI7bIEPLGi0BYqZvPdEn7Chfu0 diff --git a/config/field.storage.block_content.body.yml b/config/field.storage.block_content.body.yml new file mode 100644 index 000000000..9f7e2dcbf --- /dev/null +++ b/config/field.storage.block_content.body.yml @@ -0,0 +1,21 @@ +uuid: 19c408f3-0a1b-4ff0-8457-766bb3352a8a +langcode: en +status: true +dependencies: + module: + - block_content + - text +_core: + default_config_hash: eS0snV_L3dx9shtWRTzm5eblwOJ7qKWC9IE-4GMTDFc +id: block_content.body +field_name: body +entity_type: block_content +type: text_with_summary +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/config/field.storage.comment.comment_body.yml b/config/field.storage.comment.comment_body.yml new file mode 100644 index 000000000..e615467a7 --- /dev/null +++ b/config/field.storage.comment.comment_body.yml @@ -0,0 +1,21 @@ +uuid: 95371055-4b43-415c-a38e-45ada2b51347 +langcode: en +status: true +dependencies: + module: + - comment + - text +_core: + default_config_hash: swYoCch_hY8QO5uwr4FURplfnUCUlpPB4idF8WGVCpw +id: comment.comment_body +field_name: comment_body +entity_type: comment +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/config/field.storage.node.body.yml b/config/field.storage.node.body.yml new file mode 100644 index 000000000..5bf7e4f2c --- /dev/null +++ b/config/field.storage.node.body.yml @@ -0,0 +1,21 @@ +uuid: 014d778c-868f-4a2a-ae4c-5d37bc391ebc +langcode: en +status: true +dependencies: + module: + - node + - text +_core: + default_config_hash: EBUo7qOWqaiZaQ_RC9sLY5IoDKphS34v77VIHSACmVY +id: node.body +field_name: body +entity_type: node +type: text_with_summary +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/config/field.storage.node.comment.yml b/config/field.storage.node.comment.yml new file mode 100644 index 000000000..0c563352e --- /dev/null +++ b/config/field.storage.node.comment.yml @@ -0,0 +1,22 @@ +uuid: 6fcbede1-23a1-40e2-9af1-500a9801044e +langcode: en +status: true +dependencies: + module: + - comment + - node +_core: + default_config_hash: ktCna9xmWvYZIUfOCUyDQvedn5RtnS4CRmEIwNmvYjc +id: node.comment +field_name: comment +entity_type: node +type: comment +settings: + comment_type: comment +module: comment +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/field.storage.node.field_image.yml b/config/field.storage.node.field_image.yml new file mode 100644 index 000000000..0297c1993 --- /dev/null +++ b/config/field.storage.node.field_image.yml @@ -0,0 +1,34 @@ +uuid: 633456f5-0b5e-4f18-91ff-949df0ea3a96 +langcode: en +status: true +dependencies: + module: + - file + - image + - node +_core: + default_config_hash: SkXIPKZYiIMMtnBmfnxk58RYfbZ8cHSw5NZPY_JByME +id: node.field_image +field_name: field_image +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: + target_id: + - target_id +persist_with_no_fields: false +custom_storage: false diff --git a/config/field.storage.node.field_tags.yml b/config/field.storage.node.field_tags.yml new file mode 100644 index 000000000..bb70ec0f2 --- /dev/null +++ b/config/field.storage.node.field_tags.yml @@ -0,0 +1,22 @@ +uuid: 6e4dd87a-3334-493f-b4f1-2223a4744691 +langcode: en +status: true +dependencies: + module: + - node + - taxonomy +_core: + default_config_hash: WpOE_bs8Bs_HY2ns7n2r__de-xno0-Bxkqep5-MsHAs +id: node.field_tags +field_name: field_tags +entity_type: node +type: entity_reference +settings: + target_type: taxonomy_term +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/field.storage.user.user_picture.yml b/config/field.storage.user.user_picture.yml new file mode 100644 index 000000000..5a8c17c84 --- /dev/null +++ b/config/field.storage.user.user_picture.yml @@ -0,0 +1,34 @@ +uuid: a2ce1f02-e3ce-487a-98f6-5892e5d54df0 +langcode: en +status: true +dependencies: + module: + - file + - image + - user +_core: + default_config_hash: 6k-VBFilDLuzgSOT-77CFgHFlcd5D-kqRixtH89EShU +id: user.user_picture +field_name: user_picture +entity_type: user +type: image +settings: + uri_scheme: public + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: + target_id: + - target_id +persist_with_no_fields: false +custom_storage: false diff --git a/config/field_ui.settings.yml b/config/field_ui.settings.yml new file mode 100644 index 000000000..274396991 --- /dev/null +++ b/config/field_ui.settings.yml @@ -0,0 +1,3 @@ +field_prefix: field_ +_core: + default_config_hash: Q1nMi90W6YQxKzZAgJQw7Ag9U4JrsEUwkomF0lhvbIM diff --git a/config/file.settings.yml b/config/file.settings.yml new file mode 100644 index 000000000..0094526d3 --- /dev/null +++ b/config/file.settings.yml @@ -0,0 +1,7 @@ +description: + type: textfield + length: 128 +icon: + directory: core/modules/file/icons +_core: + default_config_hash: 8LI-1XgwLt9hYRns_7c81S632d6JhdqXKs4vDheaG6E diff --git a/config/filter.format.basic_html.yml b/config/filter.format.basic_html.yml new file mode 100644 index 000000000..2bc40b940 --- /dev/null +++ b/config/filter.format.basic_html.yml @@ -0,0 +1,45 @@ +uuid: 841c527e-3ffd-4ada-8694-a79b012d982d +langcode: en +status: true +dependencies: + module: + - editor +_core: + default_config_hash: P8ddpAIKtawJDi5SzOwCzVnnNYqONewSTJ6Xn0dW_aQ +name: 'Basic HTML' +format: basic_html +weight: 0 +filters: + filter_html: + id: filter_html + provider: filter + status: true + weight: -10 + settings: + allowed_html: '

    1. ' + filter_html_help: false + filter_html_nofollow: false + filter_align: + id: filter_align + provider: filter + status: true + weight: 7 + settings: { } + filter_caption: + id: filter_caption + provider: filter + status: true + weight: 8 + settings: { } + filter_html_image_secure: + id: filter_html_image_secure + provider: filter + status: true + weight: 9 + settings: { } + editor_file_reference: + id: editor_file_reference + provider: editor + status: true + weight: 11 + settings: { } diff --git a/config/filter.format.full_html.yml b/config/filter.format.full_html.yml new file mode 100644 index 000000000..a527ef95e --- /dev/null +++ b/config/filter.format.full_html.yml @@ -0,0 +1,36 @@ +uuid: 435556be-3294-4daa-9232-b5b914d389bc +langcode: en +status: true +dependencies: + module: + - editor +_core: + default_config_hash: hewPmBgni9jlDK_IjLxUx1HsTbinK-hdl0lOwjbteIY +name: 'Full HTML' +format: full_html +weight: 1 +filters: + filter_align: + id: filter_align + provider: filter + status: true + weight: 8 + settings: { } + filter_caption: + id: filter_caption + provider: filter + status: true + weight: 9 + settings: { } + filter_htmlcorrector: + id: filter_htmlcorrector + provider: filter + status: true + weight: 10 + settings: { } + editor_file_reference: + id: editor_file_reference + provider: editor + status: true + weight: 11 + settings: { } diff --git a/config/filter.format.plain_text.yml b/config/filter.format.plain_text.yml new file mode 100644 index 000000000..b661e0a1c --- /dev/null +++ b/config/filter.format.plain_text.yml @@ -0,0 +1,29 @@ +uuid: 605ab207-5e9b-4ced-a263-4e9eca9aa97c +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: NIKBt6kw_uPhNI0qtR2DnRf7mSOgAQdx7Q94SKMjXbQ +name: 'Plain text' +format: plain_text +weight: 10 +filters: + filter_html_escape: + id: filter_html_escape + provider: filter + status: true + weight: -10 + settings: { } + filter_url: + id: filter_url + provider: filter + status: true + weight: 0 + settings: + filter_url_length: 72 + filter_autop: + id: filter_autop + provider: filter + status: true + weight: 0 + settings: { } diff --git a/config/filter.format.restricted_html.yml b/config/filter.format.restricted_html.yml new file mode 100644 index 000000000..828e2065e --- /dev/null +++ b/config/filter.format.restricted_html.yml @@ -0,0 +1,32 @@ +uuid: c9b2e0b5-a1fa-49d2-9b76-049d4ca0ef15 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: KUjJ8Ti_ZJSlhGM88E_mhJP-8mmQRNUB6RFof615Kt0 +name: 'Restricted HTML' +format: restricted_html +weight: 0 +filters: + filter_html: + id: filter_html + provider: filter + status: true + weight: -10 + settings: + allowed_html: '

        1. ' + filter_html_help: true + filter_html_nofollow: false + filter_autop: + id: filter_autop + provider: filter + status: true + weight: 0 + settings: { } + filter_url: + id: filter_url + provider: filter + status: true + weight: 0 + settings: + filter_url_length: 72 diff --git a/config/filter.settings.yml b/config/filter.settings.yml new file mode 100644 index 000000000..dfcfed362 --- /dev/null +++ b/config/filter.settings.yml @@ -0,0 +1,4 @@ +fallback_format: plain_text +always_show_fallback_choice: false +_core: + default_config_hash: FiPjM3WdB__ruFA7B6TLwni_UcZbmek5G4b2dxQItxA diff --git a/config/image.settings.yml b/config/image.settings.yml new file mode 100644 index 000000000..52ee4a8f4 --- /dev/null +++ b/config/image.settings.yml @@ -0,0 +1,5 @@ +preview_image: core/modules/image/sample.png +allow_insecure_derivatives: false +suppress_itok_output: false +_core: + default_config_hash: k-yDFHbqNfpe-Srg4sdCSqaosCl2D8uwyEY5esF8gEw diff --git a/config/image.style.large.yml b/config/image.style.large.yml new file mode 100644 index 000000000..94f76d91e --- /dev/null +++ b/config/image.style.large.yml @@ -0,0 +1,17 @@ +uuid: d59dd04a-e0bd-434d-af10-974a53570d77 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: J2n0RpFzS0-bgSyxjs6rSdgxB1rb-bTAgqywNx_964M +name: large +label: 'Large (480×480)' +effects: + ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d: + uuid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d + id: image_scale + weight: 0 + data: + width: 480 + height: 480 + upscale: false diff --git a/config/image.style.medium.yml b/config/image.style.medium.yml new file mode 100644 index 000000000..79705f135 --- /dev/null +++ b/config/image.style.medium.yml @@ -0,0 +1,17 @@ +uuid: d4da4cb5-87c7-4209-9287-49ca4120d85d +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: Y9NmnZHQq20ASSyTNA6JnwtWrJJiSajOehGDtmUFdM0 +name: medium +label: 'Medium (220×220)' +effects: + bddf0d06-42f9-4c75-a700-a33cafa25ea0: + uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0 + id: image_scale + weight: 0 + data: + width: 220 + height: 220 + upscale: false diff --git a/config/image.style.thumbnail.yml b/config/image.style.thumbnail.yml new file mode 100644 index 000000000..bd545391a --- /dev/null +++ b/config/image.style.thumbnail.yml @@ -0,0 +1,17 @@ +uuid: d7dec8a9-abe4-4f16-b4ce-a9808f65ec30 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: cCiWdBHgLwj5omG35lsKc4LkW4MBdmcctkVop4ol5x0 +name: thumbnail +label: 'Thumbnail (100×100)' +effects: + 1cfec298-8620-4749-b100-ccb6c4500779: + uuid: 1cfec298-8620-4749-b100-ccb6c4500779 + id: image_scale + weight: 0 + data: + width: 100 + height: 100 + upscale: false diff --git a/config/menu_ui.settings.yml b/config/menu_ui.settings.yml new file mode 100644 index 000000000..c9fe099e7 --- /dev/null +++ b/config/menu_ui.settings.yml @@ -0,0 +1,3 @@ +override_parent_selector: false +_core: + default_config_hash: SqMarzIjxC3F8dZo9FEOxfqDKD_sdW1tbcFTV1BA2zU diff --git a/config/node.settings.yml b/config/node.settings.yml new file mode 100644 index 000000000..1657c1cb3 --- /dev/null +++ b/config/node.settings.yml @@ -0,0 +1,3 @@ +use_admin_theme: true +_core: + default_config_hash: 2OMXCScXUOLSYID9-phjO4q36nnnaMWNUlDxEqZzG1U diff --git a/config/node.type.article.yml b/config/node.type.article.yml new file mode 100644 index 000000000..16498da8e --- /dev/null +++ b/config/node.type.article.yml @@ -0,0 +1,13 @@ +uuid: 0e83f07c-43ea-44c5-9ebb-aeb87cb84948 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: AeW1SEDgb1OTQACAWGhzvMknMYAJlcZu0jljfeU3oso +name: Article +type: article +description: 'Use articles for time-sensitive content like news, press releases or blog posts.' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: true diff --git a/config/node.type.page.yml b/config/node.type.page.yml new file mode 100644 index 000000000..92cd25d87 --- /dev/null +++ b/config/node.type.page.yml @@ -0,0 +1,13 @@ +uuid: 04543344-f25f-4911-a81c-ad8d600c5f7c +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: KuyA4NHPXcmKAjRtwa0vQc2ZcyrUJy6IlS2TAyMNRbc +name: 'Basic page' +type: page +description: 'Use basic pages for your static content, such as an ''About us'' page.' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: false diff --git a/config/rdf.mapping.comment.comment.yml b/config/rdf.mapping.comment.comment.yml new file mode 100644 index 000000000..9c2ddbf13 --- /dev/null +++ b/config/rdf.mapping.comment.comment.yml @@ -0,0 +1,36 @@ +uuid: d8f9f575-0973-4c35-9964-327ced52f9ee +langcode: en +status: true +dependencies: + config: + - comment.type.comment + module: + - comment +_core: + default_config_hash: uETe6XupRGKDForx2MpY0pMOEu6CzGgdCAZZOKkbgmk +id: comment.comment +targetEntityType: comment +bundle: comment +types: + - 'schema:Comment' +fieldMappings: + subject: + properties: + - 'schema:name' + created: + properties: + - 'schema:dateCreated' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + changed: + properties: + - 'schema:dateModified' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + comment_body: + properties: + - 'schema:text' + uid: + properties: + - 'schema:author' + mapping_type: rel diff --git a/config/rdf.mapping.node.article.yml b/config/rdf.mapping.node.article.yml new file mode 100644 index 000000000..31a69b456 --- /dev/null +++ b/config/rdf.mapping.node.article.yml @@ -0,0 +1,52 @@ +uuid: b296c8b6-7b88-4349-be2e-be99a9dc380b +langcode: en +status: true +dependencies: + config: + - node.type.article + module: + - node +_core: + default_config_hash: IdobJe379eDudt7-bXFfJjF7pDqFl-kYxVFtpWrgkro +id: node.article +targetEntityType: node +bundle: article +types: + - 'schema:Article' +fieldMappings: + title: + properties: + - 'schema:name' + created: + properties: + - 'schema:dateCreated' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + changed: + properties: + - 'schema:dateModified' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + body: + properties: + - 'schema:text' + uid: + properties: + - 'schema:author' + comment: + properties: + - 'schema:comment' + mapping_type: rel + comment_count: + properties: + - 'schema:interactionCount' + datatype_callback: + callable: 'Drupal\rdf\SchemaOrgDataConverter::interactionCount' + arguments: + interaction_type: UserComments + field_image: + properties: + - 'schema:image' + field_tags: + properties: + - 'schema:about' diff --git a/config/rdf.mapping.node.page.yml b/config/rdf.mapping.node.page.yml new file mode 100644 index 000000000..c221ec412 --- /dev/null +++ b/config/rdf.mapping.node.page.yml @@ -0,0 +1,43 @@ +uuid: 4d42f82b-8b52-4109-a214-416e85ab6cf7 +langcode: en +status: true +dependencies: + config: + - node.type.page + module: + - node +_core: + default_config_hash: 32LxRnl4Wesvzqyfp7HnD0-U9-wxrLBn76pqY5XGNAE +id: node.page +targetEntityType: node +bundle: page +types: + - 'schema:WebPage' +fieldMappings: + title: + properties: + - 'schema:name' + created: + properties: + - 'schema:dateCreated' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + changed: + properties: + - 'schema:dateModified' + datatype_callback: + callable: 'Drupal\rdf\CommonDataConverter::dateIso8601Value' + body: + properties: + - 'schema:text' + uid: + properties: + - 'schema:author' + mapping_type: rel + comment_count: + properties: + - 'schema:interactionCount' + datatype_callback: + callable: 'Drupal\rdf\SchemaOrgDataConverter::interactionCount' + arguments: + interaction_type: UserComments diff --git a/config/rdf.mapping.taxonomy_term.tags.yml b/config/rdf.mapping.taxonomy_term.tags.yml new file mode 100644 index 000000000..961df2be9 --- /dev/null +++ b/config/rdf.mapping.taxonomy_term.tags.yml @@ -0,0 +1,22 @@ +uuid: f961e08b-6661-4ced-87d0-4cf25c69ff52 +langcode: en +status: true +dependencies: + config: + - taxonomy.vocabulary.tags + module: + - taxonomy +_core: + default_config_hash: o5duwyS1CTHx3tYOZhuu91kspe8VQjQsnwZjAJv9njk +id: taxonomy_term.tags +targetEntityType: taxonomy_term +bundle: tags +types: + - 'schema:Thing' +fieldMappings: + name: + properties: + - 'schema:name' + description: + properties: + - 'schema:description' diff --git a/config/rdf.mapping.user.user.yml b/config/rdf.mapping.user.user.yml new file mode 100644 index 000000000..e7c78b603 --- /dev/null +++ b/config/rdf.mapping.user.user.yml @@ -0,0 +1,17 @@ +uuid: fcafe819-91f1-4a17-9a66-cd78ff81adbf +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: TGTlmpYAtXxjtYMFA_A0vosE2c4R5MCQwLviIA5HUM0 +id: user.user +targetEntityType: user +bundle: user +types: + - 'schema:Person' +fieldMappings: + name: + properties: + - 'schema:name' diff --git a/config/search.page.node_search.yml b/config/search.page.node_search.yml new file mode 100644 index 000000000..3e7de2707 --- /dev/null +++ b/config/search.page.node_search.yml @@ -0,0 +1,15 @@ +uuid: 2c6256ec-4508-4943-b7fe-60f49521a880 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: 97tvtzGOa8_flb22CzSjgtm_YkiGMHvEBO-6q2K9V_U +id: node_search +label: Content +path: node +weight: -10 +plugin: node_search +configuration: + rankings: { } diff --git a/config/search.page.user_search.yml b/config/search.page.user_search.yml new file mode 100644 index 000000000..c28222a5f --- /dev/null +++ b/config/search.page.user_search.yml @@ -0,0 +1,14 @@ +uuid: a4eda7d2-b94f-4e70-b310-d275f6fc4535 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: k3aUaZXGDuhkek2TZIee0PApOPTvYZLadziekdyHA5A +id: user_search +label: Users +path: user +weight: 0 +plugin: user_search +configuration: { } diff --git a/config/search.settings.yml b/config/search.settings.yml new file mode 100644 index 000000000..52301dd92 --- /dev/null +++ b/config/search.settings.yml @@ -0,0 +1,22 @@ +and_or_limit: 7 +default_page: node_search +index: + cron_limit: 100 + overlap_cjk: true + minimum_word_size: 3 + tag_weights: + h1: 25 + h2: 18 + h3: 15 + h4: 14 + h5: 9 + h6: 6 + u: 3 + b: 3 + i: 3 + strong: 3 + em: 3 + a: 10 +logging: false +_core: + default_config_hash: hvVxL1G-ZCxaq32IZws0YsfuhvaDiQE_np-0g35KjUk diff --git a/config/seven.settings.yml b/config/seven.settings.yml new file mode 100644 index 000000000..6bba58bc7 --- /dev/null +++ b/config/seven.settings.yml @@ -0,0 +1,3 @@ +third_party_settings: + shortcut: + module_link: true diff --git a/config/shortcut.set.default.yml b/config/shortcut.set.default.yml new file mode 100644 index 000000000..231675ba3 --- /dev/null +++ b/config/shortcut.set.default.yml @@ -0,0 +1,8 @@ +uuid: af5f93a2-d58a-41cd-8507-139d6b4e15d7 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: U5VlGjd_SfV0Qm_EfnaynOfc549cNscFAx48JfYoMRI +id: default +label: Default diff --git a/config/system.action.comment_publish_action.yml b/config/system.action.comment_publish_action.yml new file mode 100644 index 000000000..4330bc3cb --- /dev/null +++ b/config/system.action.comment_publish_action.yml @@ -0,0 +1,13 @@ +uuid: d7f22709-82ff-4a67-8b36-954b09c5a83b +langcode: en +status: true +dependencies: + module: + - comment +_core: + default_config_hash: f6w_Pd30AyyDIoMkD4QpPMM2-I8FTEHICchFVOpjiCI +id: comment_publish_action +label: 'Publish comment' +type: comment +plugin: comment_publish_action +configuration: { } diff --git a/config/system.action.comment_save_action.yml b/config/system.action.comment_save_action.yml new file mode 100644 index 000000000..214ebc884 --- /dev/null +++ b/config/system.action.comment_save_action.yml @@ -0,0 +1,13 @@ +uuid: 5a6017c4-4679-4277-987e-b1b047d16aa0 +langcode: en +status: true +dependencies: + module: + - comment +_core: + default_config_hash: TSkg_gUZvk-39yXFrIFnKjsxuEzO0qBH82TdDxOMMr4 +id: comment_save_action +label: 'Save comment' +type: comment +plugin: comment_save_action +configuration: { } diff --git a/config/system.action.comment_unpublish_action.yml b/config/system.action.comment_unpublish_action.yml new file mode 100644 index 000000000..f9be991c4 --- /dev/null +++ b/config/system.action.comment_unpublish_action.yml @@ -0,0 +1,13 @@ +uuid: 6074e4c9-927a-4c0e-9689-af2a7fae1495 +langcode: en +status: true +dependencies: + module: + - comment +_core: + default_config_hash: MLin6S_PsJ7Oo480DxS46D46IM8tqFkkWOLyH7TGByI +id: comment_unpublish_action +label: 'Unpublish comment' +type: comment +plugin: comment_unpublish_action +configuration: { } diff --git a/config/system.action.node_delete_action.yml b/config/system.action.node_delete_action.yml new file mode 100644 index 000000000..ef9246290 --- /dev/null +++ b/config/system.action.node_delete_action.yml @@ -0,0 +1,13 @@ +uuid: f6d560e7-b37d-42a4-8142-17462066e6da +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: Zx0jD1Klh5tZaGJy8uOeR_2MCu9FDM4xg7TaUJUEbkI +id: node_delete_action +label: 'Delete content' +type: node +plugin: node_delete_action +configuration: { } diff --git a/config/system.action.node_make_sticky_action.yml b/config/system.action.node_make_sticky_action.yml new file mode 100644 index 000000000..ea92479b1 --- /dev/null +++ b/config/system.action.node_make_sticky_action.yml @@ -0,0 +1,13 @@ +uuid: 89bd49b5-58c2-4319-a588-b7df4199c81a +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: sOb26JSy3fGpWkvR0WYN6_hMqj_6d1rvbvrkzp1yya0 +id: node_make_sticky_action +label: 'Make content sticky' +type: node +plugin: node_make_sticky_action +configuration: { } diff --git a/config/system.action.node_make_unsticky_action.yml b/config/system.action.node_make_unsticky_action.yml new file mode 100644 index 000000000..6d5fe721a --- /dev/null +++ b/config/system.action.node_make_unsticky_action.yml @@ -0,0 +1,13 @@ +uuid: e229ecde-b2b7-4f29-9382-9ddb93621ba0 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: lDM9mvIGAu8Sw8rt-uCO4Sr7yX5VPrDPxYcawkbKd6k +id: node_make_unsticky_action +label: 'Make content unsticky' +type: node +plugin: node_make_unsticky_action +configuration: { } diff --git a/config/system.action.node_promote_action.yml b/config/system.action.node_promote_action.yml new file mode 100644 index 000000000..c43648bec --- /dev/null +++ b/config/system.action.node_promote_action.yml @@ -0,0 +1,13 @@ +uuid: 8ed37bd3-cc24-4884-b900-796909f1bc08 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: N0RDBTqiK4dKoN4p4oW2j0SGWycdHyALUe9M-Ofp89U +id: node_promote_action +label: 'Promote content to front page' +type: node +plugin: node_promote_action +configuration: { } diff --git a/config/system.action.node_publish_action.yml b/config/system.action.node_publish_action.yml new file mode 100644 index 000000000..2bacc3544 --- /dev/null +++ b/config/system.action.node_publish_action.yml @@ -0,0 +1,13 @@ +uuid: ccf2f1dd-6307-4d31-b923-21ab21de2cf7 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: lsQkbo4njZ-Q_oGKCPGXGWFjWF1I7QpgA6m-t9rcRoA +id: node_publish_action +label: 'Publish content' +type: node +plugin: node_publish_action +configuration: { } diff --git a/config/system.action.node_save_action.yml b/config/system.action.node_save_action.yml new file mode 100644 index 000000000..432aca4c5 --- /dev/null +++ b/config/system.action.node_save_action.yml @@ -0,0 +1,13 @@ +uuid: ef5ab361-b847-442a-9a61-738086684bb2 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: U9HspszLxcw6pZmRacFa6yDbbheyMN-We4fPbrWWHGg +id: node_save_action +label: 'Save content' +type: node +plugin: node_save_action +configuration: { } diff --git a/config/system.action.node_unpromote_action.yml b/config/system.action.node_unpromote_action.yml new file mode 100644 index 000000000..42cfb2035 --- /dev/null +++ b/config/system.action.node_unpromote_action.yml @@ -0,0 +1,13 @@ +uuid: a2adc1b3-23d3-4cca-a3d1-8985be748678 +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: JBptjnfuOMtsdKygklXxoOgeOCTMtQxlkymjnnj-cC0 +id: node_unpromote_action +label: 'Remove content from front page' +type: node +plugin: node_unpromote_action +configuration: { } diff --git a/config/system.action.node_unpublish_action.yml b/config/system.action.node_unpublish_action.yml new file mode 100644 index 000000000..d5c6b7768 --- /dev/null +++ b/config/system.action.node_unpublish_action.yml @@ -0,0 +1,13 @@ +uuid: 17e6da7a-4ec3-4d73-bca8-a346d3d4861b +langcode: en +status: true +dependencies: + module: + - node +_core: + default_config_hash: gGQXiSspwGl0lyOS6w_HCPpvGAPDciqDNLFwWOydVtI +id: node_unpublish_action +label: 'Unpublish content' +type: node +plugin: node_unpublish_action +configuration: { } diff --git a/config/system.action.user_add_role_action.administrator.yml b/config/system.action.user_add_role_action.administrator.yml new file mode 100644 index 000000000..33bf94a92 --- /dev/null +++ b/config/system.action.user_add_role_action.administrator.yml @@ -0,0 +1,14 @@ +uuid: f90e19a4-7ac6-4a19-93c7-0b379c7ba7e1 +langcode: en +status: true +dependencies: + config: + - user.role.administrator + module: + - user +id: user_add_role_action.administrator +label: 'Add the Administrator role to the selected users' +type: user +plugin: user_add_role_action +configuration: + rid: administrator diff --git a/config/system.action.user_block_user_action.yml b/config/system.action.user_block_user_action.yml new file mode 100644 index 000000000..57736f4f6 --- /dev/null +++ b/config/system.action.user_block_user_action.yml @@ -0,0 +1,13 @@ +uuid: 41afe630-d1f5-493e-a2d9-c818738a0728 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: DyypzTfThX10FFQw-399qPfEbLLyrhXgQrKPVsmAoJ4 +id: user_block_user_action +label: 'Block the selected user(s)' +type: user +plugin: user_block_user_action +configuration: { } diff --git a/config/system.action.user_cancel_user_action.yml b/config/system.action.user_cancel_user_action.yml new file mode 100644 index 000000000..515640b87 --- /dev/null +++ b/config/system.action.user_cancel_user_action.yml @@ -0,0 +1,13 @@ +uuid: a78f489a-3046-4f0b-bb3c-c77ef0184f66 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: nvrL9bFilzBvm2bjO9rQnFDpBA7dBBUjShSSt6NS-DU +id: user_cancel_user_action +label: 'Cancel the selected user account(s)' +type: user +plugin: user_cancel_user_action +configuration: { } diff --git a/config/system.action.user_remove_role_action.administrator.yml b/config/system.action.user_remove_role_action.administrator.yml new file mode 100644 index 000000000..39dbeb360 --- /dev/null +++ b/config/system.action.user_remove_role_action.administrator.yml @@ -0,0 +1,14 @@ +uuid: 8ff62441-6418-4b5d-8da5-fd4571d0d20e +langcode: en +status: true +dependencies: + config: + - user.role.administrator + module: + - user +id: user_remove_role_action.administrator +label: 'Remove the Administrator role from the selected users' +type: user +plugin: user_remove_role_action +configuration: + rid: administrator diff --git a/config/system.action.user_unblock_user_action.yml b/config/system.action.user_unblock_user_action.yml new file mode 100644 index 000000000..6458151c6 --- /dev/null +++ b/config/system.action.user_unblock_user_action.yml @@ -0,0 +1,13 @@ +uuid: c33a2efd-51a9-4cc2-a88f-6c9183289ade +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: SPsUXsR3Rc8d1y3gewzaAKWa1ncea_ywXX3f7LTn7k0 +id: user_unblock_user_action +label: 'Unblock the selected user(s)' +type: user +plugin: user_unblock_user_action +configuration: { } diff --git a/config/system.action.webform_submission_delete_action.yml b/config/system.action.webform_submission_delete_action.yml new file mode 100644 index 000000000..63ec31249 --- /dev/null +++ b/config/system.action.webform_submission_delete_action.yml @@ -0,0 +1,13 @@ +uuid: 6b209940-a31f-4ae1-9399-53dd18291d3f +langcode: en +status: true +dependencies: + module: + - webform +_core: + default_config_hash: TBnl4vapW7sy5bRi7TcF-ueJnvz7aZNLif95ifvhfTQ +id: webform_submission_delete_action +label: 'Delete submission' +type: webform_submission +plugin: webform_submission_delete_action +configuration: { } diff --git a/config/system.action.webform_submission_make_sticky_action.yml b/config/system.action.webform_submission_make_sticky_action.yml new file mode 100644 index 000000000..190492c9b --- /dev/null +++ b/config/system.action.webform_submission_make_sticky_action.yml @@ -0,0 +1,13 @@ +uuid: dcd81de9-a35a-48a0-a151-ed058985a5db +langcode: en +status: true +dependencies: + module: + - webform +_core: + default_config_hash: 8GSfG176mjTvczykLSKKzdI6HfM7nGnZGo54f0hRe5E +id: webform_submission_make_sticky_action +label: 'Star/Flag submission' +type: webform_submission +plugin: webform_submission_make_sticky_action +configuration: { } diff --git a/config/system.action.webform_submission_make_unsticky_action.yml b/config/system.action.webform_submission_make_unsticky_action.yml new file mode 100644 index 000000000..1797e3787 --- /dev/null +++ b/config/system.action.webform_submission_make_unsticky_action.yml @@ -0,0 +1,13 @@ +uuid: 901f48e2-9159-4729-92f6-8f8df760ca11 +langcode: en +status: true +dependencies: + module: + - webform +_core: + default_config_hash: n4gTFiUsdp7gw6yWUlDbKFEasZLCgXWWCmm7eJejay0 +id: webform_submission_make_unsticky_action +label: 'Unstar/unflag submission' +type: webform_submission +plugin: webform_submission_make_unsticky_action +configuration: { } diff --git a/config/system.authorize.yml b/config/system.authorize.yml new file mode 100644 index 000000000..c469caee1 --- /dev/null +++ b/config/system.authorize.yml @@ -0,0 +1,3 @@ +filetransfer_default: null +_core: + default_config_hash: z63ds8M4zPrylEgFRkRcOlfcsXWwfITzjD4cj1kRdfg diff --git a/config/system.cron.yml b/config/system.cron.yml new file mode 100644 index 000000000..a44f232fa --- /dev/null +++ b/config/system.cron.yml @@ -0,0 +1,5 @@ +threshold: + requirements_warning: 172800 + requirements_error: 1209600 +_core: + default_config_hash: 05U0n1_8zHYzxEFSWjyHCWuJyhdez2a6Z_aTIXin04E diff --git a/config/system.date.yml b/config/system.date.yml new file mode 100644 index 000000000..78fd3659a --- /dev/null +++ b/config/system.date.yml @@ -0,0 +1,11 @@ +country: + default: GB +first_day: 0 +timezone: + default: UTC + user: + configurable: true + warn: false + default: 0 +_core: + default_config_hash: V9UurX2GPT05NWKG9f2GWQqFG2TRG8vczidwjpy7Woo diff --git a/config/system.diff.yml b/config/system.diff.yml new file mode 100644 index 000000000..c43dd916d --- /dev/null +++ b/config/system.diff.yml @@ -0,0 +1,5 @@ +context: + lines_leading: 2 + lines_trailing: 2 +_core: + default_config_hash: 1WanmaEhxW_vM8_5Ktsdntj8MaO9UBHXg0lN603PsWM diff --git a/config/system.file.yml b/config/system.file.yml new file mode 100644 index 000000000..39ee4cb9b --- /dev/null +++ b/config/system.file.yml @@ -0,0 +1,7 @@ +allow_insecure_uploads: false +default_scheme: public +path: + temporary: /tmp +temporary_maximum_age: 21600 +_core: + default_config_hash: t48gCU9DzYfjb3bAOIqHLzhL0ChBlXh6_5B5Pyo9t8g diff --git a/config/system.image.gd.yml b/config/system.image.gd.yml new file mode 100644 index 000000000..b676907e4 --- /dev/null +++ b/config/system.image.gd.yml @@ -0,0 +1,3 @@ +jpeg_quality: 75 +_core: + default_config_hash: eNXaHfkJJUThHeF0nvkoXyPLRrKYGxgHRjORvT4F5rQ diff --git a/config/system.image.yml b/config/system.image.yml new file mode 100644 index 000000000..990d3dc79 --- /dev/null +++ b/config/system.image.yml @@ -0,0 +1,3 @@ +toolkit: gd +_core: + default_config_hash: durWHaKeBaq4d9Wpi4RqwADj1OufDepcnJuhVLmKN24 diff --git a/config/system.logging.yml b/config/system.logging.yml new file mode 100644 index 000000000..e95b28a48 --- /dev/null +++ b/config/system.logging.yml @@ -0,0 +1,3 @@ +error_level: hide +_core: + default_config_hash: u3-njszl92FaxjrCMiq0yDcjAfcdx72w1zT1O9dx6aA diff --git a/config/system.mail.yml b/config/system.mail.yml new file mode 100644 index 000000000..da8293cad --- /dev/null +++ b/config/system.mail.yml @@ -0,0 +1,5 @@ +interface: + default: php_mail + webform: webform_php_mail +_core: + default_config_hash: rYgt7uhPafP2ngaN_ZUPFuyI4KdE0zU868zLNSlzKoE diff --git a/config/system.maintenance.yml b/config/system.maintenance.yml new file mode 100644 index 000000000..79501fb1d --- /dev/null +++ b/config/system.maintenance.yml @@ -0,0 +1,4 @@ +message: '@site is currently under maintenance. We should be back shortly. Thank you for your patience.' +langcode: en +_core: + default_config_hash: Z5MXifrF77GEAgx0GQ6iWT8wStjFuY8BD9OruofWTJ8 diff --git a/config/system.menu.account.yml b/config/system.menu.account.yml new file mode 100644 index 000000000..b9a3f5726 --- /dev/null +++ b/config/system.menu.account.yml @@ -0,0 +1,10 @@ +uuid: 0d8b4986-dd89-43b5-83fb-558ec1a2c82c +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: M_Bh81osDyUQ4wV0GgU_NdBNqkzM87sLxjaCdFj9mnw +id: account +label: 'User account menu' +description: 'Links related to the active user account' +locked: true diff --git a/config/system.menu.admin.yml b/config/system.menu.admin.yml new file mode 100644 index 000000000..cd65ac290 --- /dev/null +++ b/config/system.menu.admin.yml @@ -0,0 +1,10 @@ +uuid: de3cee21-b586-4fd2-b9f7-c9aef4b46252 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: sapEi2YDGoI9yQIT_WgIV2vUdQ6DScH0V3fAyTadAL0 +id: admin +label: Administration +description: 'Administrative task links' +locked: true diff --git a/config/system.menu.footer.yml b/config/system.menu.footer.yml new file mode 100644 index 000000000..8325ee4b2 --- /dev/null +++ b/config/system.menu.footer.yml @@ -0,0 +1,10 @@ +uuid: 4881bebb-34f7-47af-a7ad-1d665af6130d +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: 7yrlW5z9zdg2eBucB2GPqXKSMQfH9lSRSO4DbWF7AFc +id: footer +label: Footer +description: 'Site information links' +locked: true diff --git a/config/system.menu.main.yml b/config/system.menu.main.yml new file mode 100644 index 000000000..83901006a --- /dev/null +++ b/config/system.menu.main.yml @@ -0,0 +1,10 @@ +uuid: 1da9f880-74e8-49ca-a009-9cda46de2679 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: Q2Ra3jfoIVk0f3SjxJX61byRQFVBAbpzYDQOiY-kno8 +id: main +label: 'Main navigation' +description: 'Site section links' +locked: true diff --git a/config/system.menu.tools.yml b/config/system.menu.tools.yml new file mode 100644 index 000000000..a1a2f97ab --- /dev/null +++ b/config/system.menu.tools.yml @@ -0,0 +1,10 @@ +uuid: 028c83dd-3937-4f35-90a8-4aca6c521e20 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: BCM-vV1zzRaLHN18dqAR_CuGOj8AFJvTx7BKl_8Gcxc +id: tools +label: Tools +description: 'User tool links, often added by modules' +locked: true diff --git a/config/system.performance.yml b/config/system.performance.yml new file mode 100644 index 000000000..d4f85d555 --- /dev/null +++ b/config/system.performance.yml @@ -0,0 +1,17 @@ +cache: + page: + max_age: 0 +css: + preprocess: true + gzip: true +fast_404: + enabled: true + paths: '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i' + exclude_paths: '/\/(?:styles|imagecache)\//' + html: '404 Not Found

          Not Found

          The requested URL "@path" was not found on this server.

          ' +js: + preprocess: true + gzip: true +stale_file_threshold: 2592000 +_core: + default_config_hash: b2cssrj-lOmATIbdehfCqfCFgVR0qCdxxWhwqa2KBVQ diff --git a/config/system.rss.yml b/config/system.rss.yml new file mode 100644 index 000000000..858f127d3 --- /dev/null +++ b/config/system.rss.yml @@ -0,0 +1,8 @@ +channel: + description: '' +items: + limit: 10 + view_mode: rss +langcode: en +_core: + default_config_hash: TlH7NNk46phfxu1mSUfwg1C0YqaGsUCeD4l9JQnQlDU diff --git a/config/system.site.yml b/config/system.site.yml new file mode 100644 index 000000000..e16e3977a --- /dev/null +++ b/config/system.site.yml @@ -0,0 +1,14 @@ +uuid: ea3e5742-9adb-4e6c-aec5-289c5cd2b42d +name: 'Drupalcamp Bristol 2017' +mail: rob@microserve.io +slogan: '' +page: + 403: '' + 404: '' + front: /node +admin_compact_mode: false +weight_select_max: 100 +langcode: en +default_langcode: en +_core: + default_config_hash: AyT9s8OUcclfALRE_imByOMgtZ19eOlqdF6zI3p7yqo diff --git a/config/system.theme.global.yml b/config/system.theme.global.yml new file mode 100644 index 000000000..ad3e46c64 --- /dev/null +++ b/config/system.theme.global.yml @@ -0,0 +1,16 @@ +favicon: + mimetype: image/vnd.microsoft.icon + path: '' + url: '' + use_default: true +features: + comment_user_picture: true + comment_user_verification: true + favicon: true + node_user_picture: true +logo: + path: '' + url: '' + use_default: true +_core: + default_config_hash: 9rAU4Pku7eMBQxauQqAgjzlcicFZ2As6zEa6zvTlCB8 diff --git a/config/system.theme.yml b/config/system.theme.yml new file mode 100644 index 000000000..8764240e3 --- /dev/null +++ b/config/system.theme.yml @@ -0,0 +1,4 @@ +admin: seven +default: bartik +_core: + default_config_hash: fOjer9hADYYnbCJVZMFZIIM1azTFWyg84ZkFDHfAbUg diff --git a/config/taxonomy.settings.yml b/config/taxonomy.settings.yml new file mode 100644 index 000000000..c98a316b1 --- /dev/null +++ b/config/taxonomy.settings.yml @@ -0,0 +1,5 @@ +maintain_index_table: true +override_selector: false +terms_per_page_admin: 100 +_core: + default_config_hash: zKpaWT6cJc1tVQQaTqatGELaCqU_oyRym6zTl27Yias diff --git a/config/taxonomy.vocabulary.tags.yml b/config/taxonomy.vocabulary.tags.yml new file mode 100644 index 000000000..4b051124f --- /dev/null +++ b/config/taxonomy.vocabulary.tags.yml @@ -0,0 +1,11 @@ +uuid: 77d6503a-3289-4187-9a06-90fd5d146ced +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: lO5ziR5dVI1PpEeHZsSOfQ-Y7NWihSDKW8-MMf6uoms +name: Tags +vid: tags +description: 'Use tags to group articles on similar topics into categories.' +hierarchy: 0 +weight: 0 diff --git a/config/text.settings.yml b/config/text.settings.yml new file mode 100644 index 000000000..ef751a5ce --- /dev/null +++ b/config/text.settings.yml @@ -0,0 +1,3 @@ +default_summary_length: 600 +_core: + default_config_hash: Bkewb77RBOK3_aXMPsp8p87gbc03NvmC5gBLzPl7hVA diff --git a/config/tour.tour.views-ui.yml b/config/tour.tour.views-ui.yml new file mode 100644 index 000000000..e5e6b43e2 --- /dev/null +++ b/config/tour.tour.views-ui.yml @@ -0,0 +1,97 @@ +uuid: 06fd3bd2-4edf-4ede-80bd-e0d637e9111d +langcode: en +status: true +dependencies: + module: + - views_ui +_core: + default_config_hash: c-HMyZwgeP8H6AITDcq4hznePkd7azpe8xl_WTnx2O0 +id: views-ui +label: 'View edit page' +module: views_ui +routes: + - + route_name: entity.view.edit_form + - + route_name: entity.view.edit_display_form +tips: + views-main: + id: views-main + plugin: text + label: 'Manage view settings' + body: 'View or edit the configuration.' + weight: 1 + views-ui-displays: + id: views-ui-displays + plugin: text + label: 'Displays in this view' + body: 'A display is a way of outputting the results, e.g., as a page or a block. A view can contain multiple displays, which are listed here. The active display is highlighted.' + weight: 2 + attributes: + data-id: views-display-top + views-ui-view-admin: + id: views-ui-view-admin + plugin: text + label: 'View administration' + body: 'Perform administrative tasks, including adding a description and creating a clone. Click the drop-down button to view the available options.' + weight: 3 + location: left + attributes: + data-id: views-display-extra-actions + views-ui-format: + id: views-ui-format + plugin: text + label: 'Output format' + body: 'Choose how to output results. E.g., choose Content to output each item completely, using your configured display settings. Or choose Fields, which allows you to output only specific fields for each result. Additional formats can be added by installing modules to extend Drupal''s base functionality.' + weight: 4 + attributes: + data-class: views-ui-display-tab-bucket.format + views-ui-fields: + id: views-ui-fields + plugin: text + label: Fields + body: 'If this view uses fields, they are listed here. You can click on a field to configure it.' + weight: 5 + attributes: + data-class: views-ui-display-tab-bucket.field + views-ui-filter: + id: views-ui-filter + plugin: text + label: 'Filter your view' + body: 'Add filters to limit the results in the output. E.g., to only show content that is published, you would add a filter for Published and select Yes.' + weight: 6 + attributes: + data-class: views-ui-display-tab-bucket.filter + views-ui-filter-operations: + id: views-ui-filter-operations + plugin: text + label: 'Filter actions' + body: 'Add, rearrange or remove filters.' + weight: 7 + attributes: + data-class: 'views-ui-display-tab-bucket.filter .dropbutton-widget' + views-ui-sorts: + id: views-ui-sorts + plugin: text + label: 'Sort Criteria' + body: 'Control the order in which the results are output. Click on an active sort rule to configure it.' + weight: 8 + attributes: + data-class: views-ui-display-tab-bucket.sort + views-ui-sorts-operations: + id: views-ui-sorts-operations + plugin: text + label: 'Sort actions' + body: 'Add, rearrange or remove sorting rules.' + weight: 9 + attributes: + data-class: 'views-ui-display-tab-bucket.sort .dropbutton-widget' + views-ui-preview: + id: views-ui-preview + plugin: text + label: Preview + body: 'Show a preview of the view output.' + weight: 10 + location: left + attributes: + data-id: preview-submit diff --git a/config/update.settings.yml b/config/update.settings.yml new file mode 100644 index 000000000..a12c15526 --- /dev/null +++ b/config/update.settings.yml @@ -0,0 +1,12 @@ +check: + disabled_extensions: false + interval_days: 1 +fetch: + url: '' + max_attempts: 2 + timeout: 30 +notification: + emails: { } + threshold: security +_core: + default_config_hash: 2QzULf0zovJQx3J06Y9rufzzfi-CY2CTTlEfJJh2Qyw diff --git a/config/user.flood.yml b/config/user.flood.yml new file mode 100644 index 000000000..f165c9525 --- /dev/null +++ b/config/user.flood.yml @@ -0,0 +1,7 @@ +uid_only: false +ip_limit: 50 +ip_window: 3600 +user_limit: 5 +user_window: 21600 +_core: + default_config_hash: UYfMzeP1S8jKm9PSvxf7nQNe8DsNS-3bc2WSNNXBQWs diff --git a/config/user.mail.yml b/config/user.mail.yml new file mode 100644 index 000000000..918084494 --- /dev/null +++ b/config/user.mail.yml @@ -0,0 +1,30 @@ +cancel_confirm: + body: "[user:display-name],\n\nA request to cancel your account has been made at [site:name].\n\nYou may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:\n\n[user:cancel-url]\n\nNOTE: The cancellation of your account is not reversible.\n\nThis link expires in one day and nothing will happen if it is not used.\n\n-- [site:name] team" + subject: 'Account cancellation request for [user:display-name] at [site:name]' +password_reset: + body: "[user:display-name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n-- [site:name] team" + subject: 'Replacement login information for [user:display-name] at [site:name]' +register_admin_created: + body: "[user:display-name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team" + subject: 'An administrator created an account for you at [site:name]' +register_no_approval_required: + body: "[user:display-name],\n\nThank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n-- [site:name] team" + subject: 'Account details for [user:display-name] at [site:name]' +register_pending_approval: + body: "[user:display-name],\n\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\n\n\n-- [site:name] team" + subject: 'Account details for [user:display-name] at [site:name] (pending admin approval)' +register_pending_approval_admin: + body: "[user:display-name] has applied for an account.\n\n[user:edit-url]" + subject: 'Account details for [user:display-name] at [site:name] (pending admin approval)' +status_activated: + body: "[user:display-name],\n\nYour account at [site:name] has been activated.\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:account-name]\npassword: Your password\n\n-- [site:name] team" + subject: 'Account details for [user:display-name] at [site:name] (approved)' +status_blocked: + body: "[user:display-name],\n\nYour account on [site:name] has been blocked.\n\n-- [site:name] team" + subject: 'Account details for [user:display-name] at [site:name] (blocked)' +status_canceled: + body: "[user:display-name],\n\nYour account on [site:name] has been canceled.\n\n-- [site:name] team" + subject: 'Account details for [user:display-name] at [site:name] (canceled)' +langcode: en +_core: + default_config_hash: m4J3ROov32OEquRYGLbx3SpdDGuqx9l_zJtNvihqdCg diff --git a/config/user.role.administrator.yml b/config/user.role.administrator.yml new file mode 100644 index 000000000..ad3962310 --- /dev/null +++ b/config/user.role.administrator.yml @@ -0,0 +1,11 @@ +uuid: 18d2ca2a-1f55-4fae-bf3f-bf4f865dd9ad +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: Om6FEO7vZZMkPIbVvfxtdkWerQ2PvQM4sWUd6Q3ZnfI +id: administrator +label: Administrator +weight: 2 +is_admin: true +permissions: { } diff --git a/config/user.role.anonymous.yml b/config/user.role.anonymous.yml new file mode 100644 index 000000000..7c9c5cc86 --- /dev/null +++ b/config/user.role.anonymous.yml @@ -0,0 +1,16 @@ +uuid: 293c9ec7-d20a-4dd1-bd82-070c62ef6e1b +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: j5zLMOdJBqC0bMvSdth5UebkprJB8g_2FXHqhfpJzow +id: anonymous +label: 'Anonymous user' +weight: 0 +is_admin: false +permissions: + - 'access content' + - 'use text format restricted_html' + - 'access comments' + - 'access site-wide contact form' + - 'search content' diff --git a/config/user.role.authenticated.yml b/config/user.role.authenticated.yml new file mode 100644 index 000000000..70aec3b70 --- /dev/null +++ b/config/user.role.authenticated.yml @@ -0,0 +1,19 @@ +uuid: 0deecc4b-fdc9-4f71-a806-c463feeca2a9 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: dJ0L2DNSj5q6XVZAGsuVDpJTh5UeYkIPwKrUOOpr8YI +id: authenticated +label: 'Authenticated user' +weight: 1 +is_admin: false +permissions: + - 'access content' + - 'use text format basic_html' + - 'access comments' + - 'post comments' + - 'skip comment approval' + - 'access site-wide contact form' + - 'access shortcuts' + - 'search content' diff --git a/config/user.settings.yml b/config/user.settings.yml new file mode 100644 index 000000000..7627c6ba8 --- /dev/null +++ b/config/user.settings.yml @@ -0,0 +1,18 @@ +anonymous: Anonymous +verify_mail: true +notify: + cancel_confirm: true + password_reset: true + status_activated: true + status_blocked: false + status_canceled: false + register_admin_created: true + register_no_approval_required: true + register_pending_approval: true +register: visitors_admin_approval +cancel_method: user_cancel_block +password_reset_timeout: 86400 +password_strength: true +langcode: en +_core: + default_config_hash: r4kwhOM0IWXVMUZDz744Yc16EOh37ZhYbA8kGOhSmLk diff --git a/config/views.settings.yml b/config/views.settings.yml new file mode 100644 index 000000000..fbda2e63f --- /dev/null +++ b/config/views.settings.yml @@ -0,0 +1,48 @@ +display_extenders: { } +skip_cache: false +sql_signature: false +ui: + show: + additional_queries: false + advanced_column: false + master_display: false + performance_statistics: false + preview_information: true + sql_query: + enabled: false + where: above + display_embed: false + always_live_preview: true + exposed_filter_any_label: old_any +field_rewrite_elements: + div: DIV + span: SPAN + h1: H1 + h2: H2 + h3: H3 + h4: H4 + h5: H5 + h6: H6 + p: P + header: HEADER + footer: FOOTER + article: ARTICLE + section: SECTION + aside: ASIDE + details: DETAILS + blockquote: BLOCKQUOTE + figure: FIGURE + address: ADDRESS + code: CODE + pre: PRE + var: VAR + samp: SAMP + kbd: KBD + strong: STRONG + em: EM + del: DEL + ins: INS + q: Q + s: S +_core: + default_config_hash: RaRd9EIcwA4u3qCSRLL8EnCicbda1kV__ASmVbyehvQ diff --git a/config/views.view.archive.yml b/config/views.view.archive.yml new file mode 100644 index 000000000..ea607357c --- /dev/null +++ b/config/views.view.archive.yml @@ -0,0 +1,243 @@ +uuid: aa817fb9-eece-4d5b-ba08-2a1a3c38434b +langcode: en +status: false +dependencies: + config: + - core.entity_view_mode.node.teaser + module: + - node + - user +_core: + default_config_hash: SRH1EhxAiIRj01P9xYv0h_LfIfWxjll0Yq-eDfjziFI +id: archive +label: Archive +module: node +description: 'All content, by month.' +tag: default +base_table: node_field_data +base_field: nid +core: '8' +display: + default: + id: default + display_title: Master + display_plugin: default + position: 0 + display_options: + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + title: 'Monthly archive' + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + 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: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + entity_type: node + entity_field: created + arguments: + created_year_month: + id: created_year_month + table: node_field_data + field: created_year_month + default_action: summary + exception: + title_enable: true + title_enable: true + title: '{{ arguments.created_year_month }}' + default_argument_type: fixed + summary: + sort_order: desc + format: default_summary + summary_options: + override: true + items_per_page: 30 + specify_validation: true + plugin_id: date_year_month + entity_type: node + filters: + status: + id: status + table: node_field_data + field: status + value: '1' + group: 0 + expose: + operator: '0' + plugin_id: boolean + entity_type: node + entity_field: status + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + 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: { } + plugin_id: language + entity_type: node + entity_field: langcode + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: 'entity:node' + options: + view_mode: teaser + header: { } + footer: { } + empty: { } + relationships: { } + fields: { } + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + block_1: + id: block_1 + display_title: Block + display_plugin: block + position: 1 + display_options: + query: + type: views_query + options: { } + defaults: + arguments: false + arguments: + created_year_month: + id: created_year_month + table: node_field_data + field: created_year_month + default_action: summary + exception: + title_enable: true + title_enable: true + title: '{{ arguments.created_year_month }}' + default_argument_type: fixed + summary: + format: default_summary + summary_options: + items_per_page: 30 + specify_validation: true + plugin_id: date_year_month + entity_type: node + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 2 + display_options: + query: + type: views_query + options: { } + path: archive + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.block_content.yml b/config/views.view.block_content.yml new file mode 100644 index 000000000..31c93ce98 --- /dev/null +++ b/config/views.view.block_content.yml @@ -0,0 +1,500 @@ +uuid: c2849925-d6f9-494a-afba-4775edc6d8a1 +langcode: en +status: true +dependencies: + module: + - block_content + - user +_core: + default_config_hash: msYnighLW1IbiBfyQbwbQwb2cU-5LECiFryt60k0Nfk +id: block_content +label: 'Custom block library' +module: views +description: 'Find and manage custom blocks.' +tag: default +base_table: block_content_field_data +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'administer blocks' + 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: mini + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + tags: + previous: '‹ Previous' + next: 'Next ›' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + info: info + type: type + changed: changed + operations: operations + info: + info: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + operations: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + info: + id: info + table: block_content_field_data + field: info + relationship: none + group_type: group + admin_label: '' + label: 'Block description' + 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: 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: null + entity_field: info + plugin_id: field + type: + id: type + table: block_content_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Block type' + 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: false + 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: block_content + entity_field: type + plugin_id: field + changed: + id: changed + table: block_content_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + label: Updated + 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 + entity_type: block_content + entity_field: changed + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: field + operations: + id: operations + table: block_content + 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: block_content + plugin_id: entity_operations + filters: + info: + id: info + table: block_content_field_data + field: info + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: info_op + label: 'Block description' + description: '' + use_operator: false + operator: info_op + identifier: info + 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: block_content + entity_field: info + plugin_id: string + type: + id: type + table: block_content_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: 'Block type' + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: block_content + entity_field: type + plugin_id: bundle + sorts: { } + title: 'Custom block library' + 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: 'There are no custom blocks available. ' + plugin_id: text_custom + block_content_listing_empty: + admin_label: '' + empty: true + field: block_content_listing_empty + group_type: group + id: block_content_listing_empty + label: '' + relationship: none + table: block_content + plugin_id: block_content_listing_empty + entity_type: block_content + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: admin/structure/block/block-content + menu: + type: tab + title: 'Custom block library' + description: '' + parent: block.admin_display + weight: 0 + context: '0' + menu_name: admin + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } diff --git a/config/views.view.comments_recent.yml b/config/views.view.comments_recent.yml new file mode 100644 index 000000000..0f8c6eb48 --- /dev/null +++ b/config/views.view.comments_recent.yml @@ -0,0 +1,267 @@ +uuid: 576b2942-ab50-4ae8-8b3c-e93df5a51640 +langcode: en +status: true +dependencies: + module: + - comment + - node + - user +_core: + default_config_hash: HDMjKTBWeuHvsV6i6VvULmfhiSjNzIH1hCoy5GuZD0E +id: comments_recent +label: 'Recent comments' +module: views +description: 'Recent comments.' +tag: default +base_table: comment_field_data +base_field: cid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access comments' + cache: + type: tag + query: + type: views_query + exposed_form: + type: basic + pager: + type: some + options: + items_per_page: 10 + offset: 0 + style: + type: html_list + options: + grouping: { } + row_class: '' + default_row_class: true + type: ul + wrapper_class: item-list + class: '' + row: + type: fields + options: + default_field_elements: true + inline: + subject: subject + changed: changed + separator: ' ' + hide_empty: false + relationships: + node: + field: node + id: node + table: comment_field_data + required: true + plugin_id: standard + fields: + subject: + id: subject + table: comment_field_data + field: subject + relationship: none + type: string + settings: + link_to_entity: true + plugin_id: field + 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: false + ellipsis: false + 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 + entity_type: comment + entity_field: subject + changed: + id: changed + table: comment_field_data + field: changed + relationship: none + plugin_id: field + 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 + type: timestamp_ago + settings: + future_format: '@interval hence' + past_format: '@interval ago' + granularity: 2 + entity_type: comment + entity_field: changed + filters: + status: + value: '1' + table: comment_field_data + field: status + id: status + plugin_id: boolean + expose: + operator: '' + group: 1 + entity_type: comment + entity_field: status + status_node: + value: '1' + table: node_field_data + field: status + relationship: node + id: status_node + plugin_id: boolean + expose: + operator: '' + group: 1 + entity_type: node + entity_field: status + sorts: + created: + id: created + table: comment_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + plugin_id: date + entity_type: comment + entity_field: created + cid: + id: cid + table: comment_field_data + field: cid + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + plugin_id: field + entity_type: comment + entity_field: cid + title: 'Recent comments' + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + label: '' + empty: true + content: 'No comments available.' + tokenize: false + plugin_id: text_custom + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 1 + display_options: + block_description: 'Recent comments' + block_category: 'Lists (Views)' + allow: + items_per_page: true + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.content.yml b/config/views.view.content.yml new file mode 100644 index 000000000..5dbe087aa --- /dev/null +++ b/config/views.view.content.yml @@ -0,0 +1,607 @@ +uuid: b99eaf0f-18b8-412c-81d0-04ac8485f3ab +langcode: en +status: true +dependencies: + module: + - node + - user +_core: + default_config_hash: fUDNcSYKNw6cTMPsX3G9VhsUGov5yUe6_TBHbcnZHyo +id: content +label: Content +module: node +description: 'Find and manage content.' +tag: default +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_options: + access: + type: perm + options: + perm: 'access content overview' + cache: + type: tag + query: + type: views_query + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + 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 + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' + columns: + node_bulk_form: node_bulk_form + title: title + type: type + name: name + status: status + changed: changed + edit_node: edit_node + delete_node: delete_node + dropbutton: dropbutton + timestamp: title + info: + node_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + title: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + edit_node: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + delete_node: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + dropbutton: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + timestamp: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: true + row: + type: fields + fields: + node_bulk_form: + id: node_bulk_form + table: node + field: node_bulk_form + label: '' + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: node_bulk_form + entity_type: node + title: + id: title + table: node_field_data + field: title + label: Title + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: node + entity_field: title + type: string + settings: + link_to_entity: true + plugin_id: field + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Content type' + 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: false + 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: node + entity_field: type + plugin_id: field + name: + id: name + table: users_field_data + field: name + relationship: uid + label: Author + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: node_field_data + field: status + label: Status + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + plugin_id: field + entity_type: node + entity_field: status + changed: + id: changed + table: node_field_data + field: changed + label: Updated + exclude: false + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: node + entity_field: changed + operations: + id: operations + table: node + 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 + plugin_id: entity_operations + filters: + status_extra: + id: status_extra + table: node_field_data + field: status_extra + operator: '=' + value: false + plugin_id: node_status + group: 1 + entity_type: node + status: + id: status + table: node_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Published status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: node + entity_field: status + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: type_op + label: 'Content type' + description: '' + use_operator: false + operator: type_op + identifier: type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + plugin_id: bundle + entity_type: node + entity_field: type + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + 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: { } + plugin_id: string + entity_type: node + entity_field: title + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + plugin_id: language + entity_type: node + entity_field: langcode + sorts: { } + title: Content + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: true + content: 'No content available.' + plugin_id: text_custom + arguments: { } + relationships: + uid: + id: uid + table: node_field_data + field: uid + admin_label: author + required: true + plugin_id: standard + show_admin_links: false + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + page_1: + display_options: + path: admin/content/node + menu: + type: 'default tab' + title: Content + description: '' + menu_name: admin + weight: -10 + context: '' + tab_options: + type: normal + title: Content + description: 'Find and manage content' + menu_name: admin + weight: -10 + display_extenders: { } + display_plugin: page + display_title: Page + id: page_1 + position: 1 + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } diff --git a/config/views.view.content_recent.yml b/config/views.view.content_recent.yml new file mode 100644 index 000000000..2053a4f73 --- /dev/null +++ b/config/views.view.content_recent.yml @@ -0,0 +1,319 @@ +uuid: 63a99814-7e46-4505-a389-c4fcde0d6050 +langcode: en +status: true +dependencies: + module: + - node + - user +_core: + default_config_hash: tDcmzGfShE0XJbfwJzyA6lJTUYC6X4iVu887dgoFfDg +id: content_recent +label: 'Recent content' +module: node +description: 'Recent content.' +tag: default +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + 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: some + options: + items_per_page: 10 + offset: 0 + style: + type: html_list + options: + grouping: { } + row_class: '' + default_row_class: true + type: ul + wrapper_class: item-list + class: '' + row: + type: fields + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + exclude: false + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + 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_alter_empty: true + type: string + settings: + link_to_entity: true + plugin_id: field + changed: + id: changed + table: node_field_data + field: changed + 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: value + type: timestamp_ago + settings: { } + 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: node + entity_field: changed + plugin_id: field + filters: + status_extra: + id: status_extra + table: node_field_data + field: status_extra + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: false + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + 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 + plugin_id: node_status + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + 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: langcode + plugin_id: language + sorts: + changed: + id: changed + table: node_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + entity_type: node + entity_field: changed + plugin_id: date + title: 'Recent content' + 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 content available.' + plugin_id: text_custom + relationships: + uid: + id: uid + table: node_field_data + field: uid + relationship: none + group_type: group + admin_label: author + required: true + entity_type: node + entity_field: uid + plugin_id: standard + arguments: { } + display_extenders: { } + use_more: false + use_more_always: false + use_more_text: More + link_url: '' + link_display: '0' + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + block_1: + display_plugin: block + id: block_1 + display_title: Block + position: 1 + display_options: + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.files.yml b/config/views.view.files.yml new file mode 100644 index 000000000..a7d98f1b8 --- /dev/null +++ b/config/views.view.files.yml @@ -0,0 +1,1124 @@ +uuid: b5399554-d73f-4c85-b3c9-ea5d90a5d0fd +langcode: en +status: true +dependencies: + module: + - file + - user +_core: + default_config_hash: rRWxAX-IQkJ5viACQRovRkC784EyCl9493GU_G23n2Q +id: files +label: Files +module: file +description: 'Find and manage files.' +tag: default +base_table: file_managed +base_field: fid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access files 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: Filter + 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: mini + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: 0 + tags: + previous: '‹ Previous' + next: 'Next ›' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + fid: fid + filename: filename + filemime: filemime + filesize: filesize + status: status + created: created + changed: changed + count: count + info: + fid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + filename: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + filemime: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + filesize: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + status: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + count: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + default: changed + empty_table: true + row: + type: fields + fields: + fid: + id: fid + table: file_managed + field: fid + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + label: Fid + exclude: true + 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_alter_empty: true + plugin_id: field + entity_type: file + entity_field: fid + filename: + id: filename + table: file_managed + field: filename + relationship: none + group_type: group + admin_label: '' + label: Name + 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: false + ellipsis: false + 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: file_link + 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 + plugin_id: field + entity_type: file + entity_field: filename + filemime: + id: filemime + table: file_managed + field: filemime + relationship: none + group_type: group + admin_label: '' + label: 'MIME type' + 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 + type: file_filemime + plugin_id: field + entity_type: file + entity_field: filemime + filesize: + id: filesize + table: file_managed + field: filesize + relationship: none + group_type: group + admin_label: '' + label: Size + 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 + type: file_size + plugin_id: field + entity_type: file + entity_field: filesize + status: + id: status + table: file_managed + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + 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 + type: boolean + settings: + format: custom + format_custom_false: Temporary + format_custom_true: Permanent + plugin_id: field + entity_type: file + entity_field: status + created: + id: created + table: file_managed + field: created + relationship: none + group_type: group + admin_label: '' + label: 'Upload date' + 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 + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: file + entity_field: created + changed: + id: changed + table: file_managed + field: changed + relationship: none + group_type: group + admin_label: '' + label: 'Changed date' + 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 + type: timestamp + settings: + date_format: medium + custom_date_format: '' + timezone: '' + plugin_id: field + entity_type: file + entity_field: changed + count: + id: count + table: file_usage + field: count + relationship: fid + group_type: sum + admin_label: '' + label: 'Used in' + exclude: false + alter: + alter_text: false + text: '' + make_link: true + path: 'admin/content/files/usage/{{ fid }}' + 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 + set_precision: false + precision: 0 + decimal: . + separator: ',' + format_plural: true + format_plural_string: "1 place\x03@count places" + prefix: '' + suffix: '' + plugin_id: numeric + filters: + filename: + id: filename + table: file_managed + field: filename + relationship: none + group_type: group + admin_label: '' + operator: word + value: '' + group: 1 + exposed: true + expose: + operator_id: filemime_op + label: Filename + description: '' + use_operator: false + operator: filename_op + identifier: filename + 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: { } + plugin_id: string + entity_type: file + entity_field: filename + filemime: + id: filemime + table: file_managed + field: filemime + relationship: none + group_type: group + admin_label: '' + operator: word + value: '' + group: 1 + exposed: true + expose: + operator_id: filemime_op + label: 'MIME type' + description: '' + use_operator: false + operator: filemime_op + identifier: filemime + 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: { } + plugin_id: string + entity_type: file + entity_field: filemime + status: + id: status + table: file_managed + field: status + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: status_op + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + plugin_id: file_status + entity_type: file + entity_field: status + sorts: { } + title: Files + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + empty: true + content: 'No files available.' + plugin_id: text_custom + relationships: + fid: + id: fid + table: file_managed + field: fid + relationship: none + group_type: group + admin_label: 'File usage' + required: true + arguments: { } + group_by: true + show_admin_links: true + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: 'Files overview' + position: 1 + display_options: + path: admin/content/files + menu: + type: tab + title: Files + description: '' + menu_name: admin + weight: 0 + context: '' + display_description: '' + defaults: + pager: true + relationships: false + relationships: + fid: + id: fid + table: file_managed + field: fid + relationship: none + group_type: group + admin_label: 'File usage' + required: false + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } + page_2: + display_plugin: page + id: page_2 + display_title: 'File usage' + position: 2 + display_options: + display_description: '' + path: admin/content/files/usage/% + empty: { } + defaults: + empty: false + pager: false + filters: false + filter_groups: false + fields: false + group_by: false + title: false + arguments: false + style: false + row: false + relationships: false + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: 0 + tags: + previous: '‹ Previous' + next: 'Next ›' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + filters: { } + filter_groups: + operator: AND + groups: { } + fields: + entity_label: + id: entity_label + table: file_usage + field: entity_label + relationship: none + group_type: group + admin_label: '' + label: Entity + 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 + link_to_entity: true + plugin_id: entity_label + type: + id: type + table: file_usage + field: type + relationship: none + group_type: group + admin_label: '' + label: 'Entity type' + 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 + plugin_id: standard + module: + id: module + table: file_usage + field: module + relationship: none + group_type: group + admin_label: '' + label: 'Registering module' + 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 + plugin_id: standard + count: + id: count + table: file_usage + field: count + relationship: none + group_type: group + admin_label: '' + label: 'Use count' + 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 + set_precision: false + precision: 0 + decimal: . + separator: ',' + format_plural: false + format_plural_string: "1\x03@count" + prefix: '' + suffix: '' + plugin_id: numeric + group_by: false + title: 'File usage' + arguments: + fid: + id: fid + table: file_managed + field: fid + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: all + title_enable: false + title: All + title_enable: true + title: 'File usage information for {{ arguments.fid }}' + 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 + plugin_id: file_fid + entity_type: file + entity_field: fid + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + entity_label: entity_label + type: type + module: module + count: count + info: + entity_label: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + type: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-medium + module: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + count: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: entity_label + empty_table: true + row: + type: fields + options: { } + relationships: + fid: + id: fid + table: file_managed + field: fid + relationship: none + group_type: group + admin_label: 'File usage' + required: true + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } diff --git a/config/views.view.frontpage.yml b/config/views.view.frontpage.yml new file mode 100644 index 000000000..5efc8d7b0 --- /dev/null +++ b/config/views.view.frontpage.yml @@ -0,0 +1,305 @@ +uuid: 8d02af14-ac1d-4e26-8d61-30e868a89581 +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.rss + - core.entity_view_mode.node.teaser + module: + - node + - user +_core: + default_config_hash: GNfx9kN8-0_KgQ-xf3fjdXCCzZwXCI8T0IPFW8SIe7g +id: frontpage +label: Frontpage +module: node +description: 'All content promoted to the front page.' +tag: default +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + empty: + area_text_custom: + admin_label: '' + content: 'No front page content has been created yet.' + empty: true + field: area_text_custom + group_type: group + id: area_text_custom + label: '' + relationship: none + table: views + tokenize: false + plugin_id: text_custom + node_listing_empty: + admin_label: '' + empty: true + field: node_listing_empty + group_type: group + id: node_listing_empty + label: '' + relationship: none + table: node + plugin_id: node_listing_empty + entity_type: node + title: + id: title + table: views + field: title + relationship: none + group_type: group + admin_label: '' + label: '' + empty: true + title: 'Welcome to [site:name]' + plugin_id: title + 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 + filters: + promote: + admin_label: '' + expose: + description: '' + identifier: '' + label: '' + multiple: false + operator: '' + operator_id: '' + remember: false + remember_roles: + authenticated: authenticated + required: false + use_operator: false + exposed: false + field: promote + group: 1 + group_info: + default_group: All + default_group_multiple: { } + description: '' + group_items: { } + identifier: '' + label: '' + multiple: false + optional: true + remember: false + widget: select + group_type: group + id: promote + is_grouped: false + operator: '=' + relationship: none + table: node_field_data + value: '1' + plugin_id: boolean + entity_type: node + entity_field: promote + status: + expose: + operator: '' + field: status + group: 1 + id: status + table: node_field_data + value: '1' + plugin_id: boolean + entity_type: node + entity_field: status + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + 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: { } + plugin_id: language + entity_type: node + entity_field: langcode + pager: + type: full + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + quantity: 9 + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + row: + type: 'entity:node' + options: + view_mode: teaser + sorts: + sticky: + admin_label: '' + expose: + label: '' + exposed: false + field: sticky + group_type: group + id: sticky + order: DESC + relationship: none + table: node_field_data + plugin_id: boolean + entity_type: node + entity_field: sticky + created: + field: created + id: created + order: DESC + table: node_field_data + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + entity_type: node + entity_field: created + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + title: '' + header: { } + footer: { } + relationships: { } + fields: { } + arguments: { } + display_extenders: { } + display_plugin: default + display_title: Master + id: default + position: 0 + cache_metadata: + contexts: + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + feed_1: + display_plugin: feed + id: feed_1 + display_title: Feed + position: 2 + display_options: + sitename_title: true + path: rss.xml + displays: + page_1: page_1 + default: '' + pager: + type: some + options: + items_per_page: 10 + offset: 0 + style: + type: rss + options: + description: '' + grouping: { } + uses_fields: false + row: + type: node_rss + options: + relationship: none + view_mode: rss + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + page_1: + display_options: + path: node + display_extenders: { } + display_plugin: page + display_title: Page + id: page_1 + position: 1 + cache_metadata: + contexts: + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.glossary.yml b/config/views.view.glossary.yml new file mode 100644 index 000000000..16c103e18 --- /dev/null +++ b/config/views.view.glossary.yml @@ -0,0 +1,459 @@ +uuid: 8c002f1c-8c0c-4f70-99ab-06db75a1bf5c +langcode: en +status: false +dependencies: + config: + - system.menu.main + module: + - node + - user +_core: + default_config_hash: '-pCFOVfbt__jaohFffTLnVavh4OBC6CjREAnchiRomQ' +id: glossary +label: Glossary +module: node +description: 'All content, by letter.' +tag: default +base_table: node_field_data +base_field: nid +core: '8' +display: + default: + id: default + display_title: Master + display_plugin: default + position: 0 + display_options: + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + use_ajax: true + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + 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: mini + options: + items_per_page: 36 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + fields: + title: + id: title + table: node_field_data + field: title + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: Title + 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 + entity_type: node + entity_field: title + name: + id: name + table: users_field_data + field: name + label: Author + relationship: uid + plugin_id: field + type: user_name + group_type: group + admin_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 + entity_type: user + entity_field: name + changed: + id: changed + table: node_field_data + field: changed + label: 'Last update' + type: timestamp + settings: + date_format: long + custom_date_format: '' + timezone: '' + plugin_id: field + relationship: none + group_type: group + admin_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 + entity_type: node + entity_field: changed + arguments: + title: + id: title + table: node_field_data + field: title + default_action: default + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: default_summary + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + summary_options: { } + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + relationships: + uid: + id: uid + table: node_field_data + field: uid + plugin_id: standard + relationship: none + group_type: group + admin_label: author + required: false + style: + type: table + options: + columns: + title: title + name: name + changed: changed + default: title + info: + title: + sortable: true + separator: '' + name: + sortable: true + separator: '' + changed: + sortable: true + separator: '' + override: true + sticky: false + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + order: asc + summary: '' + empty_table: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + header: { } + footer: { } + empty: { } + sorts: { } + filters: + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + 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: { } + plugin_id: language + entity_type: node + entity_field: langcode + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + attachment_1: + id: attachment_1 + display_title: Attachment + display_plugin: attachment + position: 2 + display_options: + query: + type: views_query + options: { } + pager: + type: none + options: + offset: 0 + items_per_page: 0 + defaults: + arguments: false + arguments: + title: + id: title + table: node_field_data + field: title + default_action: summary + exception: + title_enable: true + default_argument_type: fixed + default_argument_options: + argument: a + summary: + format: unformatted_summary + summary_options: + items_per_page: 25 + inline: true + separator: ' | ' + specify_validation: true + glossary: true + limit: 1 + case: upper + path_case: lower + transform_dash: false + plugin_id: string + relationship: none + group_type: group + admin_label: '' + title_enable: false + title: '' + default_argument_skip_url: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + entity_type: node + entity_field: title + displays: + default: default + page_1: page_1 + inherit_arguments: false + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + query: + type: views_query + options: { } + path: glossary + menu: + type: normal + title: Glossary + weight: 0 + menu_name: main + parent: '' + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: 0 + tags: { } diff --git a/config/views.view.taxonomy_term.yml b/config/views.view.taxonomy_term.yml new file mode 100644 index 000000000..c7b285c24 --- /dev/null +++ b/config/views.view.taxonomy_term.yml @@ -0,0 +1,314 @@ +uuid: 2e1815a1-4d08-4c35-9ac0-c0b094e62bc4 +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + module: + - node + - taxonomy + - user +_core: + default_config_hash: Gy5PaLdfFhtFSRqJxV-DBTF5AocKgNA2CsCNNEbvx4c +id: taxonomy_term +label: 'Taxonomy term' +module: taxonomy +description: 'Content belonging to a certain taxonomy term.' +tag: default +base_table: node_field_data +base_field: nid +core: '8' +display: + default: + id: default + display_title: Master + display_plugin: default + position: 0 + display_options: + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + 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: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: 0 + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + sorts: + sticky: + id: sticky + table: taxonomy_index + field: sticky + order: DESC + plugin_id: standard + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + created: + id: created + table: taxonomy_index + field: created + order: DESC + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + arguments: + tid: + id: tid + table: taxonomy_index + field: tid + relationship: none + group_type: group + admin_label: '' + default_action: 'not found' + exception: + value: '' + title_enable: false + title: All + title_enable: true + title: '{{ arguments.tid }}' + 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: true + validate: + type: 'entity:taxonomy_term' + fail: 'not found' + validate_options: + access: true + operation: view + multiple: 0 + bundles: { } + break_phrase: false + add_table: false + require_value: false + reduce_duplicates: false + plugin_id: taxonomy_index_tid + filters: + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: + '***LANGUAGE_language_content***': '***LANGUAGE_language_content***' + 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: { } + plugin_id: language + entity_type: node + entity_field: langcode + status: + id: status + table: taxonomy_index + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: boolean + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: 'entity:node' + options: + view_mode: teaser + header: + entity_taxonomy_term: + id: entity_taxonomy_term + table: views + field: entity_taxonomy_term + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: true + target: '{{ raw_arguments.tid }}' + view_mode: full + bypass_access: false + plugin_id: entity + footer: { } + empty: { } + relationships: { } + fields: { } + display_extenders: { } + link_url: '' + link_display: page_1 + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + feed_1: + id: feed_1 + display_title: Feed + display_plugin: feed + position: 2 + display_options: + query: + type: views_query + options: { } + pager: + type: some + options: + items_per_page: 10 + offset: 0 + path: taxonomy/term/%/feed + displays: + page_1: page_1 + default: '0' + style: + type: rss + options: + description: '' + grouping: { } + uses_fields: false + row: + type: node_rss + options: + relationship: none + view_mode: default + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } + page_1: + id: page_1 + display_title: Page + display_plugin: page + position: 1 + display_options: + query: + type: views_query + options: { } + path: taxonomy/term/% + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_interface' + - url + - url.query_args + - 'user.node_grants:view' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.user_admin_people.yml b/config/views.view.user_admin_people.yml new file mode 100644 index 000000000..62fb95376 --- /dev/null +++ b/config/views.view.user_admin_people.yml @@ -0,0 +1,915 @@ +uuid: 2d58bec3-f015-4f60-b6ff-64afdd235b24 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: 7kCByILhEFqBK9eFmxHNVbEq2z3-pl-cwWjDnzLn67I +id: user_admin_people +label: People +module: user +description: 'Find and manage people interacting with your site.' +tag: default +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'administer users' + cache: + type: tag + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Filter + reset_button: true + 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: 0 + tags: + previous: '‹ Previous' + next: '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' + 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 + summary: '' + columns: + user_bulk_form: user_bulk_form + name: name + status: status + rid: rid + created: created + access: access + edit_node: edit_node + dropbutton: dropbutton + info: + user_bulk_form: + align: '' + separator: '' + empty_column: false + responsive: '' + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + rid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: priority-low + created: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + access: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: priority-low + edit_node: + align: '' + separator: '' + empty_column: false + responsive: priority-low + dropbutton: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: created + empty_table: true + row: + type: fields + fields: + user_bulk_form: + id: user_bulk_form + table: users + field: user_bulk_form + relationship: none + group_type: group + admin_label: '' + label: 'Bulk update' + 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 + plugin_id: user_bulk_form + entity_type: user + name: + id: name + table: users_field_data + field: name + relationship: none + group_type: group + admin_label: '' + label: Username + 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 + plugin_id: field + type: user_name + entity_type: user + entity_field: name + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + 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 + plugin_id: field + type: boolean + settings: + format: custom + format_custom_true: Active + format_custom_false: Blocked + entity_type: user + entity_field: status + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + label: Roles + 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 + type: ul + separator: ', ' + plugin_id: user_roles + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + label: 'Member for' + 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 + type: timestamp_ago + settings: + future_format: '@interval' + past_format: '@interval' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: created + access: + id: access + table: users_field_data + field: access + relationship: none + group_type: group + admin_label: '' + label: 'Last access' + 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 + type: timestamp_ago + settings: + future_format: '@interval hence' + past_format: '@interval ago' + granularity: 2 + plugin_id: field + entity_type: user + entity_field: access + operations: + id: operations + table: users + 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: user + plugin_id: entity_operations + mail: + id: mail + table: users_field_data + field: mail + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: true + 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: basic_string + settings: { } + 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 + plugin_id: field + entity_type: user + entity_field: mail + filters: + combine: + id: combine + table: views + field: combine + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: combine_op + label: 'Name or email contains' + description: '' + use_operator: false + operator: combine_op + identifier: user + 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: { } + fields: + name: name + mail: mail + plugin_id: combine + roles_target_id: + id: roles_target_id + table: user__roles + field: roles_target_id + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: roles_target_id_op + label: Role + description: '' + use_operator: false + operator: roles_target_id_op + identifier: role + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + reduce_duplicates: false + plugin_id: user_roles + permission: + id: permission + table: user__roles + field: permission + relationship: none + group_type: group + admin_label: '' + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: permission_op + label: Permission + description: '' + use_operator: false + operator: permission_op + identifier: permission + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + 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: { } + reduce_duplicates: false + plugin_id: user_permissions + status: + id: status + table: users_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Active + operator: '=' + value: '1' + 2: + title: Blocked + operator: '=' + value: '0' + plugin_id: boolean + entity_type: user + entity_field: status + default_langcode: + id: default_langcode + table: users_field_data + field: default_langcode + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + 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: user + entity_field: default_langcode + plugin_id: boolean + uid_raw: + id: uid_raw + table: users_field_data + field: uid_raw + relationship: none + group_type: group + admin_label: '' + operator: '!=' + value: + min: '' + max: '' + value: '0' + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: numeric + entity_type: user + sorts: + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + plugin_id: date + entity_type: user + entity_field: created + title: People + 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 people available.' + plugin_id: text_custom + use_more: false + use_more_always: false + use_more_text: more + display_comment: '' + use_ajax: false + hide_attachment_summary: false + show_admin_links: true + group_by: false + link_url: '' + link_display: page_1 + css_class: '' + filter_groups: + operator: AND + groups: + 1: AND + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + path: admin/people/list + show_admin_links: false + menu: + type: 'default tab' + title: List + description: 'Find and manage people interacting with your site.' + menu_name: admin + weight: -10 + context: '' + tab_options: + type: normal + title: People + description: 'Manage user accounts, roles, and permissions.' + menu_name: admin + weight: 0 + defaults: + show_admin_links: false + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + max-age: 0 + tags: { } diff --git a/config/views.view.who_s_new.yml b/config/views.view.who_s_new.yml new file mode 100644 index 000000000..e62371676 --- /dev/null +++ b/config/views.view.who_s_new.yml @@ -0,0 +1,193 @@ +uuid: 5249b58d-78f2-455a-8716-a2b2e55283c9 +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: o_8fSWHZOVIbydZACGsSX09DtuJhqkRmNOzcVj-4bQI +id: who_s_new +label: 'Who''s new' +module: user +description: 'Shows a list of the newest user accounts on the site.' +tag: default +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + 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: some + options: + items_per_page: 5 + offset: 0 + style: + type: html_list + row: + type: fields + fields: + name: + id: name + table: users_field_data + field: name + label: '' + plugin_id: field + type: user_name + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + exclude: 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_alter_empty: true + entity_type: user + entity_field: name + filters: + status: + value: '1' + table: users_field_data + field: status + id: status + expose: + operator: '0' + group: 1 + plugin_id: boolean + entity_type: user + entity_field: status + access: + id: access + table: users_field_data + field: access + relationship: none + group_type: group + admin_label: '' + operator: '>' + value: + min: '' + max: '' + value: '1970-01-01' + type: date + group: 1 + exposed: false + expose: + operator_id: '0' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: date + entity_type: user + entity_field: access + sorts: + created: + id: created + table: users_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: false + expose: + label: '' + granularity: second + plugin_id: date + entity_type: user + entity_field: created + title: 'Who''s new' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } + block_1: + display_plugin: block + id: block_1 + display_title: 'Who''s new' + position: 1 + display_options: + display_description: 'A list of new users' + block_description: 'Who''s new' + block_category: User + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/views.view.who_s_online.yml b/config/views.view.who_s_online.yml new file mode 100644 index 000000000..4dd7a4d89 --- /dev/null +++ b/config/views.view.who_s_online.yml @@ -0,0 +1,222 @@ +uuid: 8579b87f-b784-4f21-8c45-17b9e65b8e2f +langcode: en +status: true +dependencies: + module: + - user +_core: + default_config_hash: m0vmYmhrzMR6S_IC0UlK0Cl-q5lEvL8-EbxbbcDtk34 +id: who_s_online +label: 'Who''s online block' +module: user +description: 'Shows the user names of the most recently active users, and the total number of active users.' +tag: default +base_table: users_field_data +base_field: uid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access user profiles' + 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: some + options: + items_per_page: 10 + offset: 0 + style: + type: html_list + options: + grouping: { } + row_class: '' + default_row_class: true + type: ul + wrapper_class: item-list + class: '' + row: + type: fields + fields: + name: + id: name + table: users_field_data + field: name + label: '' + plugin_id: field + type: user_name + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + relationship: none + group_type: group + admin_label: '' + exclude: 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_alter_empty: true + entity_type: user + entity_field: name + filters: + status: + value: '1' + table: users_field_data + field: status + id: status + expose: + operator: '0' + group: 1 + plugin_id: boolean + entity_type: user + entity_field: status + access: + id: access + table: users_field_data + field: access + relationship: none + group_type: group + admin_label: '' + operator: '>=' + value: + min: '' + max: '' + value: '-15 minutes' + type: offset + group: 1 + exposed: false + expose: + operator_id: access_op + label: 'Last access' + description: 'A user is considered online for this long after they have last viewed a page.' + use_operator: false + operator: access_op + identifier: access + 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: { } + plugin_id: date + entity_type: user + entity_field: access + sorts: + access: + id: access + table: users_field_data + field: access + order: DESC + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + plugin_id: date + entity_type: user + entity_field: access + title: 'Who''s online' + header: + result: + id: result + table: views + field: result + relationship: none + group_type: group + admin_label: '' + empty: false + content: 'There are currently @total users online.' + 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: 'There are currently 0 users online.' + plugin_id: text_custom + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } + who_s_online_block: + display_plugin: block + id: who_s_online_block + display_title: 'Who''s online' + position: 1 + display_options: + block_description: 'Who''s online' + display_description: 'A list of users that are currently logged in.' + display_extenders: { } + cache_metadata: + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - user.permissions + max-age: -1 + tags: { } diff --git a/config/webform.settings.yml b/config/webform.settings.yml new file mode 100644 index 000000000..9339dc2cf --- /dev/null +++ b/config/webform.settings.yml @@ -0,0 +1,91 @@ +settings: + default_page_base_path: form + default_form_closed_message: 'Sorry...This form is closed to new submissions.' + default_form_exception_message: 'Unable to display this webform. Please contact the site administrator.' + default_form_submit_label: Submit + default_form_submit_once: false + default_form_confidential_message: 'This form is confidential. You must
          Log out to submit it.' + default_form_disable_back: false + default_form_unsaved: false + default_form_novalidate: false + default_form_details_toggle: true + form_classes: "container-inline clearfix\nform--inline clearfix\nmessages messages--error\nmessages messages--warning\nmessages messages--status\n" + button_classes: '' + default_wizard_prev_button_label: '< Previous Page' + default_wizard_next_button_label: 'Next Page >' + default_wizard_start_label: Start + default_wizard_complete_label: Complete + default_preview_next_button_label: Preview + default_preview_prev_button_label: '< Previous' + default_preview_message: 'Please review your submission. Your submission is not complete until you press the "Submit" button!' + default_draft_button_label: 'Save Draft' + default_draft_saved_message: 'Submission saved. You may return to this form later and it will restore the current values.' + default_draft_loaded_message: 'A partially-completed form was found. Please complete the remaining portions.' + default_confirmation_message: 'New submission added to [webform:title].' + default_confirmation_back_label: 'Back to form' + confirmation_classes: "messages messages--error\nmessages messages--warning\nmessages messages--status\n" + confirmation_back_classes: "button\n" + default_limit_total_message: 'No more submissions are permitted.' + default_limit_user_message: 'No more submissions are permitted.' +assets: + css: '' + javascript: '' +export: + exporter: delimited + delimiter: ',' + multiple_delimiter: ; + excel: false + file_name: 'submission-[webform_submission:serial]' + header_format: label + header_prefix: true + header_prefix_label_delimiter: ': ' + header_prefix_key_delimiter: __ + composite_element_item_format: label + options_format: compact + options_item_format: label + entity_reference_format: link + likert_answers_format: label + signature_format: status +batch: + default_batch_export_size: 500 + default_batch_update_size: 500 + default_batch_delete_size: 500 +purge_settings: + cron_size: 100 +elements: + allowed_tags: admin + wrapper_classes: "container-inline clearfix\nform--inline clearfix\nmessages messages--error\nmessages messages--warning\nmessages messages--status\n" + classes: "container-inline clearfix\nform--inline clearfix\nmessages messages--error\nmessages messages--warning\nmessages messages--status\n" + default_description_display: '' + default_google_maps_api_key: '' + excluded_types: { } +file: + file_public: false + default_max_filesize: '' + 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_video_file_extensions: 'avi mov mp4 ogg wav webm' +format: { } +mail: + default_from_mail: '[site:mail]' + default_from_name: '[site:name]' + default_subject: 'Webform submission from: [webform_submission:source-entity]' + default_body_text: "Submitted on [webform_submission:created]\nSubmitted by: [webform_submission:user]\n\nSubmitted values are:\n[webform_submission:values]\n" + default_body_html: "

          Submitted on [webform_submission:created]

          \n

          Submitted by: [webform_submission:user]

          \n

          Submitted values are:

          \n[webform_submission:values]\n" +test: + types: "checkbox:\n - true\ncolor:\n - '#ffffcc'\n - '#ffffcc'\n - '#ccffff'\ndate:\n - '1942-06-18'\n - '1940-07-07'\n - '1943-02-25'\n - '1940-10-09'\ndatetime:\n - '1942-06-18'\n - '1940-07-07'\n - '1943-02-25'\n - '1940-10-09'\ndatelist:\n - '1942-06-18'\n - '1940-07-07'\n - '1943-02-25'\n - '1940-10-09'\nemail:\n - 'example@example.com'\n - 'test@test.com'\n - 'random@random.com'\nlanguage_select:\n - en\nmachine_name:\n - 'loremipsum'\n - 'oratione'\n - 'dixisset'\ntel:\n - '123-456-7890'\n - '098-765-4321'\ntextarea:\n - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Negat esse eam, inquit, propter se expetendam. Primum Theophrasti, Strato, physicum se voluit; Id mihi magnum videtur. Itaque mihi non satis videmini considerare quod iter sit naturae quaeque progressio. Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Est enim tanti philosophi tamque nobilis audacter sua decreta defendere.'\n - 'Huius, Lyco, oratione locuples, rebus ipsis ielunior. Duo Reges: constructio interrete. Sed haec in pueris; Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Sapiens autem semper beatus est et est aliquando in dolore; Immo videri fortasse. Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Et ille ridens: Video, inquit, quid agas;'\n - 'Quae cum dixisset, finem ille. Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat. Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus. Gloriosa ostentatio in constituendo summo bono. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Duarum enim vitarum nobis erunt instituta capienda. Comprehensum, quod cognitum non habet? Qui enim existimabit posse se miserum esse beatus non erit. Causa autem fuit huc veniendi ut quosdam hinc libros promerem. Nunc omni virtuti vitium contrario nomine opponitur.'\ntext_format:\n - value: '

          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Negat esse eam, inquit, propter se expetendam. Primum Theophrasti, Strato, physicum se voluit; Id mihi magnum videtur. Itaque mihi non satis videmini considerare quod iter sit naturae quaeque progressio. Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Est enim tanti philosophi tamque nobilis audacter sua decreta defendere.

          '\n - value: '

          Huius, Lyco, oratione locuples, rebus ipsis ielunior. Duo Reges: constructio interrete. Sed haec in pueris; Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Sapiens autem semper beatus est et est aliquando in dolore; Immo videri fortasse. Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Et ille ridens: Video, inquit, quid agas;

          '\n - value: '

          Quae cum dixisset, finem ille. Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat. Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus. Gloriosa ostentatio in constituendo summo bono. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Duarum enim vitarum nobis erunt instituta capienda. Comprehensum, quod cognitum non habet? Qui enim existimabit posse se miserum esse beatus non erit. Causa autem fuit huc veniendi ut quosdam hinc libros promerem. Nunc omni virtuti vitium contrario nomine opponitur.

          '\nurl:\n - 'http://example.com'\n - 'http://test.com'\nwebform_creditcard_number:\n - '4111111111111111'\nwebform_email_confirm:\n - 'example@example.com'\n - 'test@test.com'\n - 'random@random.com'\nwebform_email_multiple:\n - 'example@example.com, test@test.com, random@random.com'\nwebform_location:\n - value: 'The White House, 1600 Pennsylvania Ave NW, Washington, DC 20500, USA'\n - value: 'London SW1A 1AA, United Kingdom'\n - value: 'Moscow, Russia, 10307'\nwebform_time:\n - '09:00'\n - '17:00'\n" + names: "first_name:\n - 'John'\n - 'Paul'\n - 'Ringo'\n - 'George'\nlast_name:\n - 'Lennon'\n - 'McCartney'\n - 'Starr'\n - 'Harrison'\naddress:\n - '10 Main Street'\n - '11 Brook Alley Road. APT 1'\nzip:\n - '11111'\n - '12345'\n - '12345-6789'\nphone:\n - '123-456-7890'\n - '098-765-4321'\ncity:\n - 'Springfield'\n - 'Pleasantville'\n - 'Hill Valley'\nurl:\n - 'http://example.com'\n - 'http://test.com'\ndefault:\n - 'Loremipsum'\n - 'Oratione'\n - 'Dixisset'\n" +ui: + video_display: dialog + details_save: true + dialog_disabled: false + offcanvas_disabled: false + html_editor_disabled: false +library: + cdn: false +langcode: en +third_party_settings: { } +_core: + default_config_hash: SCMmRNOIlKXCV4kwgZt4A45-lnoRPqsAPKA8T4czegQ diff --git a/config/webform.webform.contact.yml b/config/webform.webform.contact.yml new file mode 100644 index 000000000..0f7ba20f6 --- /dev/null +++ b/config/webform.webform.contact.yml @@ -0,0 +1,144 @@ +uuid: 60584442-65c2-46ca-a933-a37cd5870890 +langcode: en +status: open +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: 60Y_Mb8Wnw4EJBuq9xuQ0Lsq9IJvjQQfTs-rw4gZ-z8 +open: null +close: null +uid: null +template: false +id: contact +title: Contact +description: 'Basic email contact webform.' +elements: "name:\n '#title': 'Your Name'\n '#type': textfield\n '#required': true\n '#default_value': '[webform-authenticated-user:display-name]'\nemail:\n '#title': 'Your Email'\n '#type': email\n '#required': true\n '#default_value': '[webform-authenticated-user:mail]'\nsubject:\n '#title': Subject\n '#type': textfield\n '#required': true\n '#test': 'Testing contact webform from [site:name]'\nmessage:\n '#title': Message\n '#type': textarea\n '#required': true\n '#test': 'Please ignore this email.'\n" +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: 'Send message' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: url_message + confirmation_title: '' + confirmation_message: 'Your message has been sent.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_confirmation: + id: email + label: 'Email confirmation' + handler_id: email_confirmation + status: true + weight: 1 + settings: + to_mail: '[webform_submission:values:email:raw]' + cc_mail: '' + bcc_mail: '' + from_mail: default + from_name: default + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false + email_notification: + id: email + label: 'Email notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:name:value]' + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/config/webform.webform_options.country_codes.yml b/config/webform.webform_options.country_codes.yml new file mode 100644 index 000000000..06f1580d1 --- /dev/null +++ b/config/webform.webform_options.country_codes.yml @@ -0,0 +1,12 @@ +uuid: 35dbb2f2-23b2-4503-84aa-5244c686813d +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: WKyw6_wHd1Ls-12ra-byuG23rJPOaCO9o6QrLYo0fbY +id: country_codes +label: 'Country codes' +options: '' diff --git a/config/webform.webform_options.country_names.yml b/config/webform.webform_options.country_names.yml new file mode 100644 index 000000000..88b166993 --- /dev/null +++ b/config/webform.webform_options.country_names.yml @@ -0,0 +1,12 @@ +uuid: 84b1b2d4-3080-4210-8b9f-aaa55f10c7c6 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: LcoGYtMhMwy6X5Wryrr_mUqNPzdSCVNcyqZULa_7qo0 +id: country_names +label: 'Country names' +options: '' diff --git a/config/webform.webform_options.creditcard_codes.yml b/config/webform.webform_options.creditcard_codes.yml new file mode 100644 index 000000000..76da43269 --- /dev/null +++ b/config/webform.webform_options.creditcard_codes.yml @@ -0,0 +1,12 @@ +uuid: 78388584-9e81-45ce-a2df-566b9ef31fb5 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: Ai9t0SUFQABCrwdqoi8LBqhaMzROfaFHGgsURpEWnm4 +id: creditcard_codes +label: 'Credit card codes' +options: "VI: 'Visa'\nMC: 'Mastercard'\nAE: 'American Express'\nDC: 'Discover'\n" diff --git a/config/webform.webform_options.days.yml b/config/webform.webform_options.days.yml new file mode 100644 index 000000000..8cea84c65 --- /dev/null +++ b/config/webform.webform_options.days.yml @@ -0,0 +1,12 @@ +uuid: 05a439aa-dfd7-45e3-9a1f-f539a5cab406 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: T744d_CMtx_CsVZD1MNNvyiSNlemzmFgmzVdBKtuKq8 +id: days +label: Days +options: "Sunday: Sunday\nMonday: Monday\nTuesday: Tuesday\nWednesday: Wednesday\nThursday: Thursday\nFriday: Friday\nSaturday: Saturday\n" diff --git a/config/webform.webform_options.education.yml b/config/webform.webform_options.education.yml new file mode 100644 index 000000000..4c3cdc7a1 --- /dev/null +++ b/config/webform.webform_options.education.yml @@ -0,0 +1,12 @@ +uuid: c8823ae1-7c12-4f29-8660-95fac4245aba +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: cidMez3wHXoTzTg_4KqylNWeDY2lYJ-DRLIAF7xq57M +id: education +label: Education +options: "High School: High School\nAssociate Degree: Associate Degree\nAssociate Degree: Associate Degree\nGraduate or Professional Degree: Graduate or Professional Degree\nSome College: Some College\n" diff --git a/config/webform.webform_options.employment_status.yml b/config/webform.webform_options.employment_status.yml new file mode 100644 index 000000000..9c69276e5 --- /dev/null +++ b/config/webform.webform_options.employment_status.yml @@ -0,0 +1,12 @@ +uuid: 74fa51f9-3238-448c-9861-1cf223c76b41 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: 6iKOLiv3WqdDwbLEYesK2txBl-1W8frLFe3PbT8-AaE +id: employment_status +label: 'Employment status' +options: "'Full Time': 'Full Time'\n'Part Time': 'Part Time'\n'Military': 'Military'\nUnemployed: Unemployed\nRetired: Retired\n" diff --git a/config/webform.webform_options.ethnicity.yml b/config/webform.webform_options.ethnicity.yml new file mode 100644 index 000000000..510481f7a --- /dev/null +++ b/config/webform.webform_options.ethnicity.yml @@ -0,0 +1,12 @@ +uuid: d1461d75-f0fb-4436-8192-3fb39b03f499 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: SSV6f9SV3qO5qcz99jSqj7-2eGNaD9RfOYDdMPUffew +id: ethnicity +label: Ethnicity +options: "Caucasian: Caucasian\n'Latino/Hispanic': 'Latino/Hispanic'\n'Middle Eastern': 'Middle Eastern'\nAfrican: African\nCaribbean: Caribbean\n'South Asian': 'South Asian'\n'East Asian': 'East Asian'\nMixed: Mixed\n" diff --git a/config/webform.webform_options.gender.yml b/config/webform.webform_options.gender.yml new file mode 100644 index 000000000..10a9f2279 --- /dev/null +++ b/config/webform.webform_options.gender.yml @@ -0,0 +1,12 @@ +uuid: 4ff2585a-2565-40ee-afee-e7dad387f793 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: 6WYQFjFAQkeNPRRklUX_i7UaJK_Uw3iNDU7ux2j3pCo +id: gender +label: Gender +options: "Male: Male\nFemale: Female\nTransgender: Transgender\n" diff --git a/config/webform.webform_options.industry.yml b/config/webform.webform_options.industry.yml new file mode 100644 index 000000000..a6f701dd5 --- /dev/null +++ b/config/webform.webform_options.industry.yml @@ -0,0 +1,12 @@ +uuid: 97d754c8-44c5-4988-a8cf-03889fc7a01b +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: AkLS04Kjzd3Z8UBlMVBMBYgJdpYZhF9ZVyPIUQs9dRk +id: industry +label: Industry +options: "Accounting/Finance: Accounting/Finance\nAdvertising/Public Relations: Advertising/Public Relations\nAerospace/Aviation: Aerospace/Aviation\nArts/Entertainment/Publishing: Arts/Entertainment/Publishing\nAutomotive: Automotive\nBanking/Mortgage: Banking/Mortgage\nBusiness Development: Business Development\nBusiness Opportunity: Business Opportunity\nClerical/Administrative: Clerical/Administrative\nConstruction/Facilities: Construction/Facilities\nConsumer Goods: Consumer Goods\nCustomer Service: Customer Service\nEducation/Training: Education/Training\nEnergy/Utilities: Energy/Utilities\nEngineering: Engineering\nGovernment/Military: Government/Military\nHealthcare: Healthcare\nHospitality/Travel: Hospitality/Travel\nHuman Resources: Human Resources\nInstallation/Maintenance: Installation/Maintenance\nInsurance: Insurance\nInternet: Internet\nLaw Enforcement/Security: Law Enforcement/Security\nLegal: Legal\nManagement/Executive: Management/Executive\nManufacturing/Operations: Manufacturing/Operations\nMarketing: Marketing\nNon-Profit/Volunteer: Non-Profit/Volunteer\nPharmaceutical/Biotech: Pharmaceutical/Biotech\nProfessional Services: Professional Services\nReal Estate: Real Estate\nRestaurant/Food Service: Restaurant/Food Service\nRetail: Retail\nSales: Sales\nScience/Research: Science/Research\nSkilled Labor: Skilled Labor\nTechnology: Technology\nTelecommunications: Telecommunications\nTransportation/Logistics: Transportation/Logistics\n" diff --git a/config/webform.webform_options.languages.yml b/config/webform.webform_options.languages.yml new file mode 100644 index 000000000..8ddd61205 --- /dev/null +++ b/config/webform.webform_options.languages.yml @@ -0,0 +1,12 @@ +uuid: 04d46bfe-7175-47f4-88eb-5f6551edca5e +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: vDwaQMB485SRDS5QUFJf8awBp0DdlOYSMnYsLiw1l5s +id: languages +label: Languages +options: '' diff --git a/config/webform.webform_options.likert_agreement.yml b/config/webform.webform_options.likert_agreement.yml new file mode 100644 index 000000000..3428c3e5a --- /dev/null +++ b/config/webform.webform_options.likert_agreement.yml @@ -0,0 +1,12 @@ +uuid: 7b32a5bc-f7d6-49b8-b9dc-a009db0b6773 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: kTJEQFpTapkGkf4XD51vPW2-7aZU7awzSenC65YDqGQ +id: likert_agreement +label: 'Likert: Agreement' +options: "1: Much Worse\n2: Somewhat Worse\n3: About the Same\n4: Somewhat Better\n5: Much Better\n" diff --git a/config/webform.webform_options.likert_comparison.yml b/config/webform.webform_options.likert_comparison.yml new file mode 100644 index 000000000..9c874d6da --- /dev/null +++ b/config/webform.webform_options.likert_comparison.yml @@ -0,0 +1,12 @@ +uuid: b9ab1a1c-f4d6-4293-83be-fbfda89f8c81 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: saeeZhVt98h_n7CVmx4DOg02g7x3cIdctN_E7BYzMgs +id: likert_comparison +label: 'Likert: Comparison' +options: "1: Strongly Disagree\n2: Disagree\n3: Neutral\n4: Agree\n5: Strongly Agree\n" diff --git a/config/webform.webform_options.likert_importance.yml b/config/webform.webform_options.likert_importance.yml new file mode 100644 index 000000000..ad293b7b6 --- /dev/null +++ b/config/webform.webform_options.likert_importance.yml @@ -0,0 +1,12 @@ +uuid: 7e1e9d63-84cb-44e3-b1f0-6241b3d556fe +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: UZmRfKwhC1I8mlY5XuVV3DF7MpWAbFxyynhNFR9kuOo +id: likert_importance +label: 'Likert: Importance' +options: "1: Not at all Important\n2: Somewhat Important\n3: Neutral\n4: Important\n5: Very Important\n" diff --git a/config/webform.webform_options.likert_quality.yml b/config/webform.webform_options.likert_quality.yml new file mode 100644 index 000000000..3ed0be13e --- /dev/null +++ b/config/webform.webform_options.likert_quality.yml @@ -0,0 +1,12 @@ +uuid: cbc5a7ea-8e12-4847-9c02-cf7f5e531169 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: kyCI0E0VJVJXi_Z61m6Mom95_ot1haYPehpPr_IxdcM +id: likert_quality +label: 'Likert: Quality' +options: "1: Poor\n2: Fair\n3: Good\n4: Very good\n5: Excellent\n" diff --git a/config/webform.webform_options.likert_satisfaction.yml b/config/webform.webform_options.likert_satisfaction.yml new file mode 100644 index 000000000..bf86027a9 --- /dev/null +++ b/config/webform.webform_options.likert_satisfaction.yml @@ -0,0 +1,12 @@ +uuid: 38e15e12-47d6-4a5e-9129-a665a1577528 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: neOMp5gtbo4DzHTQAmmvrsN8VkSA7LXCl5R1oXTqKX0 +id: likert_satisfaction +label: 'Likert: Satisfaction' +options: "1: Very Unsatisfied\n2: Unsatisfied\n3: Neutral\n4: Satisfied\n5: Very Satisfied\n" diff --git a/config/webform.webform_options.likert_ten_scale.yml b/config/webform.webform_options.likert_ten_scale.yml new file mode 100644 index 000000000..9a1f5dcc3 --- /dev/null +++ b/config/webform.webform_options.likert_ten_scale.yml @@ -0,0 +1,12 @@ +uuid: 778e26b2-ce54-4c8e-b55e-e5e796fb246b +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: VtI_OJow8fKXIo4jyhi2UF_WmE6MQf94_fR9qctlv7s +id: likert_ten_scale +label: 'Likert: Ten Scale' +options: "1: 1\n2: 2\n3: 3\n4: 4\n5: 5\n6: 6\n7: 7\n8: 8\n9: 9\n10: 10\n" diff --git a/config/webform.webform_options.likert_would_you.yml b/config/webform.webform_options.likert_would_you.yml new file mode 100644 index 000000000..8804ff1de --- /dev/null +++ b/config/webform.webform_options.likert_would_you.yml @@ -0,0 +1,12 @@ +uuid: 54f7c29d-83fb-43f8-9805-ca805a0258a3 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: 98M-DPZLTuMVYsG1XrXy1h90nf3al-jgdqGJrW8Elcg +id: likert_would_you +label: 'Likert: Would You' +options: "1: Definitely Not\n2: Probably Not\n3: Not Sure\n4: Probably\n5: Definitely\n" diff --git a/config/webform.webform_options.marital_status.yml b/config/webform.webform_options.marital_status.yml new file mode 100644 index 000000000..31ce5c495 --- /dev/null +++ b/config/webform.webform_options.marital_status.yml @@ -0,0 +1,12 @@ +uuid: 7ecbddd2-f260-4bc5-a456-1f0f5c2b6e89 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: 0saefTQh-QUbxCT1hBxeNaEN2g63K2R21rRXxGXQoCs +id: marital_status +label: 'Marital status' +options: "Single: Single\nMarried: Married\nDivorced: Divorced\nWidowed: Widowed\n" diff --git a/config/webform.webform_options.months.yml b/config/webform.webform_options.months.yml new file mode 100644 index 000000000..4677f8d06 --- /dev/null +++ b/config/webform.webform_options.months.yml @@ -0,0 +1,12 @@ +uuid: 6c19151e-ce9f-4b62-bc16-e3b5f5e6572e +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: Y0ajkmoLjJ568EZRzaBSy_vEejATn3EA2RTymPoGZ5U +id: months +label: Months +options: "January: January\nFebruary: February\nMarch: March\nApril: April\nMay: May\nJune: June\nJuly: July\nAugust: August\nSeptember: September\nOctober: October\nNovember: November\nDecember: December\n" diff --git a/config/webform.webform_options.phone_types.yml b/config/webform.webform_options.phone_types.yml new file mode 100644 index 000000000..cc5f70d88 --- /dev/null +++ b/config/webform.webform_options.phone_types.yml @@ -0,0 +1,12 @@ +uuid: e5c0aa22-b6b6-414d-9fd8-f228e07f197e +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: HTQyVQb-cumvmKyHdJ8uUqH8fKnU1YehyNSQ4le0AyA +id: phone_types +label: 'Phone type' +options: "Home: Home\nOffice: Office\nCell: Cell\n" diff --git a/config/webform.webform_options.relationship.yml b/config/webform.webform_options.relationship.yml new file mode 100644 index 000000000..bc89b4680 --- /dev/null +++ b/config/webform.webform_options.relationship.yml @@ -0,0 +1,12 @@ +uuid: 3d179e6d-65b5-4be2-8b48-afd70e5bc0eb +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: y8t4RZdGS6VERGR7rBmSPFIG0fO5OZb87IB6P5A6Avw +id: relationship +label: Relationship +options: "Parent: Parent\n'Significant Other': 'Significant Other'\nSibling: Sibling\nChild: Child\nFriend: Friend\n" diff --git a/config/webform.webform_options.size.yml b/config/webform.webform_options.size.yml new file mode 100644 index 000000000..083f969e6 --- /dev/null +++ b/config/webform.webform_options.size.yml @@ -0,0 +1,12 @@ +uuid: 02770b0d-4738-493c-90ea-4acf087968fd +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: Tq6nZxoRP6_RwY5Q6Z4ecRFadf4xCmFRgg6PqnBzJLc +id: size +label: Size +options: "Extra Small: Extra Small\nSmall: Small\nMedium: Medium\nLarge: Large\nExtra Large: Extra Large\n" diff --git a/config/webform.webform_options.state_codes.yml b/config/webform.webform_options.state_codes.yml new file mode 100644 index 000000000..a7cdc82c8 --- /dev/null +++ b/config/webform.webform_options.state_codes.yml @@ -0,0 +1,12 @@ +uuid: 5ffa2932-43bb-44ed-8687-f71f821ee70e +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: m3g0-QKCeVgtX-uSscYIDojtBBljuRJWq_212R4o-Pg +id: state_codes +label: 'State codes' +options: "AL: Alabama\nAK: Alaska\nAZ: Arizona\nAR: Arkansas\nCA: California\nCO: Colorado\nCT: Connecticut\nDE: Delaware\nDC: 'District of Columbia'\nFL: Florida\nGA: Georgia\nGU: Guam\nHI: Hawaii\nID: Idaho\nIL: Illinois\nIN: Indiana\nIA: Iowa\nKS: Kansas\nKY: Kentucky\nLA: Louisiana\nME: Maine\nMD: Maryland\nMA: Massachusetts\nMI: Michigan\nMN: Minnesota\nMS: Mississippi\nMO: Missouri\nMT: Montana\nNE: Nebraska\nNV: Nevada\nNH: 'New Hampshire'\nNJ: 'New Jersey'\nNM: 'New Mexico'\nNY: 'New York'\nNC: 'North Carolina'\nND: 'North Dakota'\nOH: Ohio\nOK: Oklahoma\nOR: Oregon\nPA: Pennsylvania\nRI: 'Rhode Island'\nSC: 'South Carolina'\nSD: 'South Dakota'\nTN: Tennessee\nTX: Texas\nUT: Utah\nVT: Vermont\nVA: Virginia\nWA: Washington\nWV: 'West Virginia'\nWI: Wisconsin\nWY: Wyoming\n" diff --git a/config/webform.webform_options.state_names.yml b/config/webform.webform_options.state_names.yml new file mode 100644 index 000000000..0348cebe4 --- /dev/null +++ b/config/webform.webform_options.state_names.yml @@ -0,0 +1,12 @@ +uuid: 933a9dd3-2dff-4bd4-aae0-522cf8d3df6f +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: I5ixKB0YwXXr8YCu-8oSY3lAuC7NEnBGuL0SHm2eRfY +id: state_names +label: 'State names' +options: "Alabama: Alabama\nAlaska: Alaska\nArizona: Arizona\nArkansas: Arkansas\nCalifornia: California\nColorado: Colorado\nConnecticut: Connecticut\nDelaware: Delaware\n'District of Columbia': 'District of Columbia'\nFlorida: Florida\nGeorgia: Georgia\nHawaii: Hawaii\nIdaho: Idaho\nIllinois: Illinois\nIndiana: Indiana\nIowa: Iowa\nKansas: Kansas\nKentucky: Kentucky\nLouisiana: Louisiana\nMaine: Maine\nMaryland: Maryland\nMassachusetts: Massachusetts\nMichigan: Michigan\nMinnesota: Minnesota\nMississippi: Mississippi\nMissouri: Missouri\nMontana: Montana\nNebraska: Nebraska\nNevada: Nevada\n'New Hampshire': 'New Hampshire'\n'New Jersey': 'New Jersey'\n'New Mexico': 'New Mexico'\n'New York': 'New York'\n'North Carolina': 'North Carolina'\n'North Dakota': 'North Dakota'\nOhio: Ohio\nOklahoma: Oklahoma\nOregon: Oregon\nPennsylvania: Pennsylvania\n'Rhode Island': 'Rhode Island'\n'South Carolina': 'South Carolina'\n'South Dakota': 'South Dakota'\nTennessee: Tennessee\nTexas: Texas\nUtah: Utah\nVermont: Vermont\nVirginia: Virginia\nWashington: Washington\n'West Virginia': 'West Virginia'\nWisconsin: Wisconsin\nWyoming: Wyoming\n" diff --git a/config/webform.webform_options.state_province_codes.yml b/config/webform.webform_options.state_province_codes.yml new file mode 100644 index 000000000..cc8133463 --- /dev/null +++ b/config/webform.webform_options.state_province_codes.yml @@ -0,0 +1,12 @@ +uuid: 562e8b4f-2b32-4386-9e1c-ba2c99bac55c +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: qHBCN68U1lClhKHP06dP4gjeZa3bZTSPbTSNIpW3Y98 +id: state_province_codes +label: 'State/Province codes' +options: "AL: Alabama\nAK: Alaska\nAS: 'American Samoa'\nAZ: Arizona\nAR: Arkansas\nAE: 'Armed Forces (Canada, Europe, Africa, or Middle East'\nAA: 'Armed Forces Americas'\nAP: 'Armed Forces Pacific'\nCA: California\nCO: Colorado\nCT: Connecticut\nDE: Delaware\nDC: 'District of Columbia'\nFM: 'Federate States of Micronesia'\nFL: Florida\nGA: Georgia\nGU: Guam\nHI: Hawaii\nID: Idaho\nIL: Illinois\nIN: Indiana\nIA: Iowa\nKS: Kansas\nKY: Kentucky\nLA: Louisiana\nME: Maine\nMH: 'Marshall Islands'\nMD: Maryland\nMA: Massachusetts\nMI: Michigan\nMN: Minnesota\nMS: Mississippi\nMO: Missouri\nMT: Montana\nNE: Nebraska\nNV: Nevada\nNH: 'New Hampshire'\nNJ: 'New Jersey'\nNM: 'New Mexico'\nNY: 'New York'\nNC: 'North Carolina'\nND: 'North Dakota'\nMP: 'Northern Mariana Islands'\nOH: Ohio\nOK: Oklahoma\nOR: Oregon\nPW: Palau\nPA: Pennsylvania\nPR: 'Puerto Rico'\nRI: 'Rhode Island'\nSC: 'South Carolina'\nSD: 'South Dakota'\nTN: Tennessee\nTX: Texas\nUT: Utah\nVT: Vermont\nVI: 'Virgin Islands'\nVA: Virginia\nWA: Washington\nWV: 'West Virginia'\nWI: Wisconsin\nWY: Wyoming\n" diff --git a/config/webform.webform_options.state_province_names.yml b/config/webform.webform_options.state_province_names.yml new file mode 100644 index 000000000..a5d60febc --- /dev/null +++ b/config/webform.webform_options.state_province_names.yml @@ -0,0 +1,12 @@ +uuid: 26217ffc-7fd8-4def-afc9-7c25ad76c57a +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: Dsbo0h8kGlwB0RgwOOPBhnQzwgoJXwsiqxznsQADipI +id: state_province_names +label: 'State/Province names' +options: "Alabama: Alabama\nAlaska: Alaska\n'American Samoa': 'American Samoa'\nArizona: Arizona\nArkansas: Arkansas\n'Armed Forces (Canada, Europe, Africa, or Middle East': 'Armed Forces (Canada, Europe, Africa, or Middle East'\n'Armed Forces Americas': 'Armed Forces Americas'\n'Armed Forces Pacific': 'Armed Forces Pacific'\nCalifornia: California\nColorado: Colorado\nConnecticut: Connecticut\nDelaware: Delaware\n'District of Columbia': 'District of Columbia'\n'Federate States of Micronesia': 'Federate States of Micronesia'\nFlorida: Florida\nGeorgia: Georgia\nGuam: Guam\nHawaii: Hawaii\nIdaho: Idaho\nIllinois: Illinois\nIndiana: Indiana\nIowa: Iowa\nKansas: Kansas\nKentucky: Kentucky\nLouisiana: Louisiana\nMaine: Maine\n'Marshall Islands': 'Marshall Islands'\nMaryland: Maryland\nMassachusetts: Massachusetts\nMichigan: Michigan\nMinnesota: Minnesota\nMississippi: Mississippi\nMissouri: Missouri\nMontana: Montana\nNebraska: Nebraska\nNevada: Nevada\n'New Hampshire': 'New Hampshire'\n'New Jersey': 'New Jersey'\n'New Mexico': 'New Mexico'\n'New York': 'New York'\n'North Carolina': 'North Carolina'\n'North Dakota': 'North Dakota'\n'Northern Mariana Islands': 'Northern Mariana Islands'\nOhio: Ohio\nOklahoma: Oklahoma\nOregon: Oregon\nPalau: Palau\nPennsylvania: Pennsylvania\n'Puerto Rico': 'Puerto Rico'\n'Rhode Island': 'Rhode Island'\n'South Carolina': 'South Carolina'\n'South Dakota': 'South Dakota'\nTennessee: Tennessee\nTexas: Texas\nUtah: Utah\nVermont: Vermont\n'Virgin Islands': 'Virgin Islands'\nVirginia: Virginia\nWashington: Washington\n'West Virginia': 'West Virginia'\nWisconsin: Wisconsin\nWyoming: Wyoming\n" diff --git a/config/webform.webform_options.time_zones.yml b/config/webform.webform_options.time_zones.yml new file mode 100644 index 000000000..a7471d16c --- /dev/null +++ b/config/webform.webform_options.time_zones.yml @@ -0,0 +1,12 @@ +uuid: da77edba-0abf-4e63-8178-4b6bc7324601 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: egUVOkBF8Rfzd-cuycbVPEBBfN8PpBQAyq_8iRd7Nns +id: time_zones +label: 'Time zones' +options: '' diff --git a/config/webform.webform_options.titles.yml b/config/webform.webform_options.titles.yml new file mode 100644 index 000000000..f177a6757 --- /dev/null +++ b/config/webform.webform_options.titles.yml @@ -0,0 +1,12 @@ +uuid: 61002592-35d1-4c60-a25c-60b0a6a80b8c +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: Onwyr2kH5zCeHvMfSSPDJIKUKrvX7x7tAr-7Uv_-k-c +id: titles +label: Titles +options: "Miss: Miss\nMs: Ms\nMr: Mr\nMrs: Mrs\nDr: Dr\n" diff --git a/config/webform.webform_options.yes_no.yml b/config/webform.webform_options.yes_no.yml new file mode 100644 index 000000000..8608961fc --- /dev/null +++ b/config/webform.webform_options.yes_no.yml @@ -0,0 +1,12 @@ +uuid: 5cf29d1c-f2c7-4663-9ba0-98924b012c27 +langcode: en +status: true +dependencies: + enforced: + module: + - webform +_core: + default_config_hash: KAEJv6Gks8rU-P0XcmUfjRuk-92T32qbkBCn1LQgKr8 +id: yes_no +label: Yes/No +options: "Yes: Yes\nNo: No\n" diff --git a/web/modules/contrib/webform/.gitignore b/web/modules/contrib/webform/.gitignore new file mode 100644 index 000000000..1500426c4 --- /dev/null +++ b/web/modules/contrib/webform/.gitignore @@ -0,0 +1,9 @@ +*.patch +interdiff-*.txt + +# Ignore all *.features.yml in 'modules' directory. +# NOTE: *.features.yml are not ignored with 'test/modules' directory. +webform.features.yml +modules/webform_examples/webform_examples.features.yml +modules/webform_templates/webform_templates.features.yml +modules/webform_node/webform_node.features.yml diff --git a/web/modules/contrib/webform/LICENSE.txt b/web/modules/contrib/webform/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/web/modules/contrib/webform/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/web/modules/contrib/webform/README.md b/web/modules/contrib/webform/README.md new file mode 100644 index 000000000..8f37644f7 --- /dev/null +++ b/web/modules/contrib/webform/README.md @@ -0,0 +1,112 @@ +Webform 8.x-5.x +--------------- + +### About this Module + +The Webform module is a form builder and submission manager for Drupal 8. + +The primary use case for this module is to: + +- **Build** a new webform or duplicate an existing template +- **Publish** the webform as a page, node, or block +- **Collect** submissions +- **Send** confirmations and notifications +- **Review** submissions online +- **Download** submissions as a CSV + + +### Goals + +- A comprehensive form and survey building solution for Drupal 8. +- A stable, maintainable, and tested API for building forms and handling submission. +- A pluggable/extensible API for custom form elements and submission handling. + + +### Demo + +> [Watch a demo](http://youtu.be/sQGsfQ_LZJ4) of the Webform module. + +> Evaluate this project online using [simplytest.me](https://simplytest.me/project/drupal/8.2.x?add[]=webform). + + +### Presentations, Blog Posts, & Articles + +- [Webform 8.x-5.x @ NJ DrupalCamp](https://www.drupalcampnj.org/program/sessions/building-webforms-drupal-8) +- [Getting NYU onto Webform](https://www.fourkitchens.com/blog/article/getting-nyu-yaml-form) +- [Webforms for Drupal 8](https://www.gaiaresources.com.au/yaml-forms-drupal-8/) +- [Creating Webform Handlers in Drupal 8](http://fivemilemedia.co.uk/blog/creating-yaml-form-handlers-drupal-8) +- [Les formulaires en Drupal 8](https://makina-corpus.com/blog/metier/2016/les-formulaires-en-drupal-8) + +### Installing the Webform Module + +1. Copy/upload the webform module to the modules directory of your Drupal + installation. + +2. Enable the 'Webform' module and desired sub-modules in 'Extend'. + (/admin/modules) + +3. Set up user permissions. (/admin/people/permissions#module-webform) + +4. Build a new webform (/admin/structure/webform) + or duplicate an existing template (/admin/structure/webform/templates). + +5. Publish your webform as a: + + - **Page:** By linking to the published webform. + (/webform/contact) + + - **Node:** By creating a new node that references the webform. + (/node/add/webform) + + - **Block:** By placing a Webform block on your site. + (/admin/structure/block) + +6. (optional) Install third party libraries(/admin/help/webform). + +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_. + + +### Project Status + +- [Webform Project Board](https://contribkanban.com/board/webform/8.x-5.x) +- [Webform 4.x features currently missing from the Webform module](https://www.drupal.org/node/2807571) + + +### Similar Modules + + +- **[Comparison of Webform Building Modules](https://www.drupal.org/node/2083353)** + Drupal has a lot of modules aimed at helping site builders and users add webforms + to their sites. The [Comparison of Webform Building Modules](https://www.drupal.org/node/2083353) + page includes rough comparisons of three of them for Drupal 8 and five of them + for Drupal 7. + +--- + +- **[Contact](https://www.drupal.org/documentation/modules/contact) + + [Contact Storage](https://www.drupal.org/project/contact_storage)** + The Contact module allows site visitors to send emails to other authenticated + users and to the site administrator. The Contact Storage module provides + storage for Contact messages which are fully-fledged entities in Drupal 8. + Many of its features are likely to be moved into Drupal Core. + +- **[Eform](https://www.drupal.org/project/eform)** + The EForm module enables you to create front-end webforms (fieldable entities), + which contain fields that you define! These webforms use the standard Drupal + fields. + [Is this module still needed?](https://www.drupal.org/node/2809179) + diff --git a/web/modules/contrib/webform/composer.json b/web/modules/contrib/webform/composer.json new file mode 100644 index 000000000..4d3658be3 --- /dev/null +++ b/web/modules/contrib/webform/composer.json @@ -0,0 +1,19 @@ +{ + "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" + } +} diff --git a/web/modules/contrib/webform/config/install/system.action.webform_submission_delete_action.yml b/web/modules/contrib/webform/config/install/system.action.webform_submission_delete_action.yml new file mode 100644 index 000000000..1c55babb7 --- /dev/null +++ b/web/modules/contrib/webform/config/install/system.action.webform_submission_delete_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - webform +id: webform_submission_delete_action +label: 'Delete submission' +type: webform_submission +plugin: webform_submission_delete_action +configuration: { } diff --git a/web/modules/contrib/webform/config/install/system.action.webform_submission_make_sticky_action.yml b/web/modules/contrib/webform/config/install/system.action.webform_submission_make_sticky_action.yml new file mode 100644 index 000000000..85ea0ab66 --- /dev/null +++ b/web/modules/contrib/webform/config/install/system.action.webform_submission_make_sticky_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - webform +id: webform_submission_make_sticky_action +label: 'Star/Flag submission' +type: webform_submission +plugin: webform_submission_make_sticky_action +configuration: { } diff --git a/web/modules/contrib/webform/config/install/system.action.webform_submission_make_unsticky_action.yml b/web/modules/contrib/webform/config/install/system.action.webform_submission_make_unsticky_action.yml new file mode 100644 index 000000000..880931e5c --- /dev/null +++ b/web/modules/contrib/webform/config/install/system.action.webform_submission_make_unsticky_action.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: + module: + - webform +id: webform_submission_make_unsticky_action +label: 'Unstar/unflag submission' +type: webform_submission +plugin: webform_submission_make_unsticky_action +configuration: { } diff --git a/web/modules/contrib/webform/config/install/webform.settings.yml b/web/modules/contrib/webform/config/install/webform.settings.yml new file mode 100644 index 000000000..67ff84487 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.settings.yml @@ -0,0 +1,208 @@ +settings: + default_page_base_path: form + default_form_closed_message: 'Sorry...This form is closed to new submissions.' + default_form_exception_message: 'Unable to display this webform. Please contact the site administrator.' + default_form_submit_label: Submit + default_form_submit_once: false + default_form_confidential_message: 'This form is confidential. You must
          Log out to submit it.' + default_form_disable_back: false + default_form_unsaved: false + default_form_novalidate: false + default_form_details_toggle: true + 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 >' + default_wizard_start_label: Start + default_wizard_complete_label: Complete + default_preview_next_button_label: Preview + default_preview_prev_button_label: '< Previous' + default_preview_message: 'Please review your submission. Your submission is not complete until you press the "Submit" button!' + default_draft_button_label: 'Save Draft' + default_draft_saved_message: 'Submission saved. You may return to this form later and it will restore the current values.' + default_draft_loaded_message: 'A partially-completed form was found. Please complete the remaining portions.' + default_confirmation_message: 'New submission added to [webform:title].' + default_confirmation_back_label: 'Back to form' + 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.' +assets: + css: '' + javascript: '' +export: + exporter: delimited + delimiter: ',' + multiple_delimiter: ; + excel: false + file_name: 'submission-[webform_submission:serial]' + header_format: label + header_prefix: true + header_prefix_label_delimiter: ': ' + header_prefix_key_delimiter: __ + composite_element_item_format: label + options_format: compact + options_item_format: label + entity_reference_format: link + likert_answers_format: label + signature_format: status +batch: + default_batch_export_size: 500 + default_batch_update_size: 500 + default_batch_delete_size: 500 +purge_settings: + cron_size: 100 +elements: + allowed_tags: admin + wrapper_classes: | + container-inline clearfix + form--inline clearfix + 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 + default_description_display: '' + default_google_maps_api_key: '' + excluded_types: { } +file: + file_public: false + default_max_filesize: '' + 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_video_file_extensions: 'avi mov mp4 ogg wav webm' +format: { } +mail: + default_from_mail: '[site:mail]' + default_from_name: '[site:name]' + default_subject: 'Webform submission from: [webform_submission:source-entity]' + default_body_text: | + Submitted on [webform_submission:created] + Submitted by: [webform_submission:user] + + Submitted values are: + [webform_submission:values] + default_body_html: | +

          Submitted on [webform_submission:created]

          +

          Submitted by: [webform_submission:user]

          +

          Submitted values are:

          + [webform_submission:values] +test: + types: | + checkbox: + - true + color: + - '#ffffcc' + - '#ffffcc' + - '#ccffff' + date: + - '1942-06-18' + - '1940-07-07' + - '1943-02-25' + - '1940-10-09' + datetime: + - '1942-06-18' + - '1940-07-07' + - '1943-02-25' + - '1940-10-09' + datelist: + - '1942-06-18' + - '1940-07-07' + - '1943-02-25' + - '1940-10-09' + email: + - 'example@example.com' + - 'test@test.com' + - 'random@random.com' + language_select: + - en + machine_name: + - 'loremipsum' + - 'oratione' + - 'dixisset' + tel: + - '123-456-7890' + - '098-765-4321' + textarea: + - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Negat esse eam, inquit, propter se expetendam. Primum Theophrasti, Strato, physicum se voluit; Id mihi magnum videtur. Itaque mihi non satis videmini considerare quod iter sit naturae quaeque progressio. Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Est enim tanti philosophi tamque nobilis audacter sua decreta defendere.' + - 'Huius, Lyco, oratione locuples, rebus ipsis ielunior. Duo Reges: constructio interrete. Sed haec in pueris; Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Sapiens autem semper beatus est et est aliquando in dolore; Immo videri fortasse. Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Et ille ridens: Video, inquit, quid agas;' + - 'Quae cum dixisset, finem ille. Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat. Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus. Gloriosa ostentatio in constituendo summo bono. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Duarum enim vitarum nobis erunt instituta capienda. Comprehensum, quod cognitum non habet? Qui enim existimabit posse se miserum esse beatus non erit. Causa autem fuit huc veniendi ut quosdam hinc libros promerem. Nunc omni virtuti vitium contrario nomine opponitur.' + text_format: + - value: '

          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Negat esse eam, inquit, propter se expetendam. Primum Theophrasti, Strato, physicum se voluit; Id mihi magnum videtur. Itaque mihi non satis videmini considerare quod iter sit naturae quaeque progressio. Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Est enim tanti philosophi tamque nobilis audacter sua decreta defendere.

          ' + - value: '

          Huius, Lyco, oratione locuples, rebus ipsis ielunior. Duo Reges: constructio interrete. Sed haec in pueris; Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Sapiens autem semper beatus est et est aliquando in dolore; Immo videri fortasse. Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Et ille ridens: Video, inquit, quid agas;

          ' + - value: '

          Quae cum dixisset, finem ille. Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat. Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus. Gloriosa ostentatio in constituendo summo bono. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Duarum enim vitarum nobis erunt instituta capienda. Comprehensum, quod cognitum non habet? Qui enim existimabit posse se miserum esse beatus non erit. Causa autem fuit huc veniendi ut quosdam hinc libros promerem. Nunc omni virtuti vitium contrario nomine opponitur.

          ' + url: + - 'http://example.com' + - 'http://test.com' + webform_creditcard_number: + - '4111111111111111' + webform_email_confirm: + - 'example@example.com' + - 'test@test.com' + - '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' + - 'Paul' + - 'Ringo' + - 'George' + last_name: + - 'Lennon' + - 'McCartney' + - 'Starr' + - 'Harrison' + address: + - '10 Main Street' + - '11 Brook Alley Road. APT 1' + zip: + - '11111' + - '12345' + - '12345-6789' + phone: + - '123-456-7890' + - '098-765-4321' + city: + - 'Springfield' + - 'Pleasantville' + - 'Hill Valley' + url: + - 'http://example.com' + - 'http://test.com' + default: + - 'Loremipsum' + - 'Oratione' + - 'Dixisset' +ui: + video_display: dialog + details_save: true + dialog_disabled: false + offcanvas_disabled: false + html_editor_disabled: false +library: + cdn: false +langcode: en +third_party_settings: { } diff --git a/web/modules/contrib/webform/config/install/webform.webform.contact.yml b/web/modules/contrib/webform/config/install/webform.webform.contact.yml new file mode 100644 index 000000000..08a0a9126 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform.contact.yml @@ -0,0 +1,161 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform +open: null +close: null +uid: null +template: false +id: contact +title: Contact +description: 'Basic email contact webform.' +elements: | + name: + '#title': 'Your Name' + '#type': textfield + '#required': true + '#default_value': '[webform-authenticated-user:display-name]' + email: + '#title': 'Your Email' + '#type': email + '#required': true + '#default_value': '[webform-authenticated-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.' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: 'Send message' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: url_message + confirmation_title: '' + confirmation_message: 'Your message has been sent.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_confirmation: + id: email + label: 'Email confirmation' + handler_id: email_confirmation + status: true + weight: 1 + settings: + to_mail: '[webform_submission:values:email:raw]' + cc_mail: '' + bcc_mail: '' + from_mail: default + from_name: default + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false + email_notification: + id: email + label: 'Email notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:name:value]' + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.country_codes.yml b/web/modules/contrib/webform/config/install/webform.webform_options.country_codes.yml new file mode 100644 index 000000000..cd2d32c53 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.country_codes.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: country_codes +label: 'Country codes' +options: '' diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.country_names.yml b/web/modules/contrib/webform/config/install/webform.webform_options.country_names.yml new file mode 100644 index 000000000..bebc9a0a3 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.country_names.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: country_names +label: 'Country names' +options: '' diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.creditcard_codes.yml b/web/modules/contrib/webform/config/install/webform.webform_options.creditcard_codes.yml new file mode 100644 index 000000000..66bab12d4 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.creditcard_codes.yml @@ -0,0 +1,13 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: creditcard_codes +label: 'Credit card codes' +options: | + VI: 'Visa' + MC: 'Mastercard' + AE: 'American Express' + DC: 'Discover' diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.days.yml b/web/modules/contrib/webform/config/install/webform.webform_options.days.yml new file mode 100644 index 000000000..b726d7482 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.days.yml @@ -0,0 +1,16 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: days +label: Days +options: | + Sunday: Sunday + Monday: Monday + Tuesday: Tuesday + Wednesday: Wednesday + Thursday: Thursday + Friday: Friday + Saturday: Saturday diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.education.yml b/web/modules/contrib/webform/config/install/webform.webform_options.education.yml new file mode 100644 index 000000000..6e59f8f4a --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.education.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: education +label: Education +options: | + High School: High School + Associate Degree: Associate Degree + Associate Degree: Associate Degree + Graduate or Professional Degree: Graduate or Professional Degree + Some College: Some College diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.employment_status.yml b/web/modules/contrib/webform/config/install/webform.webform_options.employment_status.yml new file mode 100644 index 000000000..bc50949e4 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.employment_status.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: employment_status +label: 'Employment status' +options: | + 'Full Time': 'Full Time' + 'Part Time': 'Part Time' + 'Military': 'Military' + Unemployed: Unemployed + Retired: Retired diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.ethnicity.yml b/web/modules/contrib/webform/config/install/webform.webform_options.ethnicity.yml new file mode 100644 index 000000000..689167ef1 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.ethnicity.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: ethnicity +label: Ethnicity +options: | + Caucasian: Caucasian + 'Latino/Hispanic': 'Latino/Hispanic' + 'Middle Eastern': 'Middle Eastern' + African: African + Caribbean: Caribbean + 'South Asian': 'South Asian' + 'East Asian': 'East Asian' + Mixed: Mixed diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.gender.yml b/web/modules/contrib/webform/config/install/webform.webform_options.gender.yml new file mode 100644 index 000000000..fd04b8e58 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.gender.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: gender +label: Gender +options: | + Male: Male + Female: Female + Transgender: Transgender diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.industry.yml b/web/modules/contrib/webform/config/install/webform.webform_options.industry.yml new file mode 100644 index 000000000..41d23e044 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.industry.yml @@ -0,0 +1,48 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: industry +label: Industry +options: | + Accounting/Finance: Accounting/Finance + Advertising/Public Relations: Advertising/Public Relations + Aerospace/Aviation: Aerospace/Aviation + Arts/Entertainment/Publishing: Arts/Entertainment/Publishing + Automotive: Automotive + Banking/Mortgage: Banking/Mortgage + Business Development: Business Development + Business Opportunity: Business Opportunity + Clerical/Administrative: Clerical/Administrative + Construction/Facilities: Construction/Facilities + Consumer Goods: Consumer Goods + Customer Service: Customer Service + Education/Training: Education/Training + Energy/Utilities: Energy/Utilities + Engineering: Engineering + Government/Military: Government/Military + Healthcare: Healthcare + Hospitality/Travel: Hospitality/Travel + Human Resources: Human Resources + Installation/Maintenance: Installation/Maintenance + Insurance: Insurance + Internet: Internet + Law Enforcement/Security: Law Enforcement/Security + Legal: Legal + Management/Executive: Management/Executive + Manufacturing/Operations: Manufacturing/Operations + Marketing: Marketing + Non-Profit/Volunteer: Non-Profit/Volunteer + Pharmaceutical/Biotech: Pharmaceutical/Biotech + Professional Services: Professional Services + Real Estate: Real Estate + Restaurant/Food Service: Restaurant/Food Service + Retail: Retail + Sales: Sales + Science/Research: Science/Research + Skilled Labor: Skilled Labor + Technology: Technology + Telecommunications: Telecommunications + Transportation/Logistics: Transportation/Logistics diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.languages.yml b/web/modules/contrib/webform/config/install/webform.webform_options.languages.yml new file mode 100644 index 000000000..e51541ec8 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.languages.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: languages +label: Languages +options: '' diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_agreement.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_agreement.yml new file mode 100644 index 000000000..b98fb49c7 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_agreement.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_agreement +label: 'Likert: Agreement' +options: | + 1: Much Worse + 2: Somewhat Worse + 3: About the Same + 4: Somewhat Better + 5: Much Better diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_comparison.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_comparison.yml new file mode 100644 index 000000000..b428549b8 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_comparison.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_comparison +label: 'Likert: Comparison' +options: | + 1: Strongly Disagree + 2: Disagree + 3: Neutral + 4: Agree + 5: Strongly Agree diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_importance.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_importance.yml new file mode 100644 index 000000000..9221cf57f --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_importance.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_importance +label: 'Likert: Importance' +options: | + 1: Not at all Important + 2: Somewhat Important + 3: Neutral + 4: Important + 5: Very Important diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_quality.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_quality.yml new file mode 100644 index 000000000..b1ec08ebb --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_quality.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_quality +label: 'Likert: Quality' +options: | + 1: Poor + 2: Fair + 3: Good + 4: Very good + 5: Excellent diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_satisfaction.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_satisfaction.yml new file mode 100644 index 000000000..99e4f2fec --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_satisfaction.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_satisfaction +label: 'Likert: Satisfaction' +options: | + 1: Very Unsatisfied + 2: Unsatisfied + 3: Neutral + 4: Satisfied + 5: Very Satisfied diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_ten_scale.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_ten_scale.yml new file mode 100644 index 000000000..253b6826e --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_ten_scale.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_ten_scale +label: 'Likert: Ten Scale' +options: | + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 10: 10 diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.likert_would_you.yml b/web/modules/contrib/webform/config/install/webform.webform_options.likert_would_you.yml new file mode 100644 index 000000000..1826c6f92 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.likert_would_you.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: likert_would_you +label: 'Likert: Would You' +options: | + 1: Definitely Not + 2: Probably Not + 3: Not Sure + 4: Probably + 5: Definitely diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.marital_status.yml b/web/modules/contrib/webform/config/install/webform.webform_options.marital_status.yml new file mode 100644 index 000000000..2e1a7ba40 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.marital_status.yml @@ -0,0 +1,13 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: marital_status +label: 'Marital status' +options: | + Single: Single + Married: Married + Divorced: Divorced + Widowed: Widowed diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.months.yml b/web/modules/contrib/webform/config/install/webform.webform_options.months.yml new file mode 100644 index 000000000..7b2350575 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.months.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: months +label: Months +options: | + January: January + February: February + March: March + April: April + May: May + June: June + July: July + August: August + September: September + October: October + November: November + December: December diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.phone_types.yml b/web/modules/contrib/webform/config/install/webform.webform_options.phone_types.yml new file mode 100644 index 000000000..d52991c09 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.phone_types.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: phone_types +label: 'Phone type' +options: | + Home: Home + Office: Office + Cell: Cell diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.relationship.yml b/web/modules/contrib/webform/config/install/webform.webform_options.relationship.yml new file mode 100644 index 000000000..0c549381a --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.relationship.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: relationship +label: Relationship +options: | + Parent: Parent + 'Significant Other': 'Significant Other' + Sibling: Sibling + Child: Child + Friend: Friend diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.size.yml b/web/modules/contrib/webform/config/install/webform.webform_options.size.yml new file mode 100644 index 000000000..387a612e7 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.size.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: size +label: Size +options: | + Extra Small: Extra Small + Small: Small + Medium: Medium + Large: Large + Extra Large: Extra Large diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.state_codes.yml b/web/modules/contrib/webform/config/install/webform.webform_options.state_codes.yml new file mode 100644 index 000000000..17fbd0dd1 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.state_codes.yml @@ -0,0 +1,61 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: state_codes +label: 'State codes' +options: | + AL: Alabama + AK: Alaska + AZ: Arizona + AR: Arkansas + CA: California + CO: Colorado + CT: Connecticut + DE: Delaware + DC: 'District of Columbia' + FL: Florida + GA: Georgia + GU: Guam + HI: Hawaii + ID: Idaho + IL: Illinois + IN: Indiana + IA: Iowa + KS: Kansas + KY: Kentucky + LA: Louisiana + ME: Maine + MD: Maryland + MA: Massachusetts + MI: Michigan + MN: Minnesota + MS: Mississippi + MO: Missouri + MT: Montana + NE: Nebraska + NV: Nevada + NH: 'New Hampshire' + NJ: 'New Jersey' + NM: 'New Mexico' + NY: 'New York' + NC: 'North Carolina' + ND: 'North Dakota' + OH: Ohio + OK: Oklahoma + OR: Oregon + PA: Pennsylvania + RI: 'Rhode Island' + SC: 'South Carolina' + SD: 'South Dakota' + TN: Tennessee + TX: Texas + UT: Utah + VT: Vermont + VA: Virginia + WA: Washington + WV: 'West Virginia' + WI: Wisconsin + WY: Wyoming diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.state_names.yml b/web/modules/contrib/webform/config/install/webform.webform_options.state_names.yml new file mode 100644 index 000000000..e83b844ec --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.state_names.yml @@ -0,0 +1,60 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: state_names +label: 'State names' +options: | + Alabama: Alabama + Alaska: Alaska + Arizona: Arizona + Arkansas: Arkansas + California: California + Colorado: Colorado + Connecticut: Connecticut + Delaware: Delaware + 'District of Columbia': 'District of Columbia' + Florida: Florida + Georgia: Georgia + Hawaii: Hawaii + Idaho: Idaho + Illinois: Illinois + Indiana: Indiana + Iowa: Iowa + Kansas: Kansas + Kentucky: Kentucky + Louisiana: Louisiana + Maine: Maine + Maryland: Maryland + Massachusetts: Massachusetts + Michigan: Michigan + Minnesota: Minnesota + Mississippi: Mississippi + Missouri: Missouri + Montana: Montana + Nebraska: Nebraska + Nevada: Nevada + 'New Hampshire': 'New Hampshire' + 'New Jersey': 'New Jersey' + 'New Mexico': 'New Mexico' + 'New York': 'New York' + 'North Carolina': 'North Carolina' + 'North Dakota': 'North Dakota' + Ohio: Ohio + Oklahoma: Oklahoma + Oregon: Oregon + Pennsylvania: Pennsylvania + 'Rhode Island': 'Rhode Island' + 'South Carolina': 'South Carolina' + 'South Dakota': 'South Dakota' + Tennessee: Tennessee + Texas: Texas + Utah: Utah + Vermont: Vermont + Virginia: Virginia + Washington: Washington + 'West Virginia': 'West Virginia' + Wisconsin: Wisconsin + Wyoming: Wyoming diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.state_province_codes.yml b/web/modules/contrib/webform/config/install/webform.webform_options.state_province_codes.yml new file mode 100644 index 000000000..278794537 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.state_province_codes.yml @@ -0,0 +1,71 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: state_province_codes +label: 'State/Province codes' +options: | + AL: Alabama + AK: Alaska + AS: 'American Samoa' + AZ: Arizona + AR: Arkansas + AE: 'Armed Forces (Canada, Europe, Africa, or Middle East' + AA: 'Armed Forces Americas' + AP: 'Armed Forces Pacific' + CA: California + CO: Colorado + CT: Connecticut + DE: Delaware + DC: 'District of Columbia' + FM: 'Federate States of Micronesia' + FL: Florida + GA: Georgia + GU: Guam + HI: Hawaii + ID: Idaho + IL: Illinois + IN: Indiana + IA: Iowa + KS: Kansas + KY: Kentucky + LA: Louisiana + ME: Maine + MH: 'Marshall Islands' + MD: Maryland + MA: Massachusetts + MI: Michigan + MN: Minnesota + MS: Mississippi + MO: Missouri + MT: Montana + NE: Nebraska + NV: Nevada + NH: 'New Hampshire' + NJ: 'New Jersey' + NM: 'New Mexico' + NY: 'New York' + NC: 'North Carolina' + ND: 'North Dakota' + MP: 'Northern Mariana Islands' + OH: Ohio + OK: Oklahoma + OR: Oregon + PW: Palau + PA: Pennsylvania + PR: 'Puerto Rico' + RI: 'Rhode Island' + SC: 'South Carolina' + SD: 'South Dakota' + TN: Tennessee + TX: Texas + UT: Utah + VT: Vermont + VI: 'Virgin Islands' + VA: Virginia + WA: Washington + WV: 'West Virginia' + WI: Wisconsin + WY: Wyoming diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.state_province_names.yml b/web/modules/contrib/webform/config/install/webform.webform_options.state_province_names.yml new file mode 100644 index 000000000..48f6608b3 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.state_province_names.yml @@ -0,0 +1,71 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: state_province_names +label: 'State/Province names' +options: | + Alabama: Alabama + Alaska: Alaska + 'American Samoa': 'American Samoa' + Arizona: Arizona + Arkansas: Arkansas + 'Armed Forces (Canada, Europe, Africa, or Middle East': 'Armed Forces (Canada, Europe, Africa, or Middle East' + 'Armed Forces Americas': 'Armed Forces Americas' + 'Armed Forces Pacific': 'Armed Forces Pacific' + California: California + Colorado: Colorado + Connecticut: Connecticut + Delaware: Delaware + 'District of Columbia': 'District of Columbia' + 'Federate States of Micronesia': 'Federate States of Micronesia' + Florida: Florida + Georgia: Georgia + Guam: Guam + Hawaii: Hawaii + Idaho: Idaho + Illinois: Illinois + Indiana: Indiana + Iowa: Iowa + Kansas: Kansas + Kentucky: Kentucky + Louisiana: Louisiana + Maine: Maine + 'Marshall Islands': 'Marshall Islands' + Maryland: Maryland + Massachusetts: Massachusetts + Michigan: Michigan + Minnesota: Minnesota + Mississippi: Mississippi + Missouri: Missouri + Montana: Montana + Nebraska: Nebraska + Nevada: Nevada + 'New Hampshire': 'New Hampshire' + 'New Jersey': 'New Jersey' + 'New Mexico': 'New Mexico' + 'New York': 'New York' + 'North Carolina': 'North Carolina' + 'North Dakota': 'North Dakota' + 'Northern Mariana Islands': 'Northern Mariana Islands' + Ohio: Ohio + Oklahoma: Oklahoma + Oregon: Oregon + Palau: Palau + Pennsylvania: Pennsylvania + 'Puerto Rico': 'Puerto Rico' + 'Rhode Island': 'Rhode Island' + 'South Carolina': 'South Carolina' + 'South Dakota': 'South Dakota' + Tennessee: Tennessee + Texas: Texas + Utah: Utah + Vermont: Vermont + 'Virgin Islands': 'Virgin Islands' + Virginia: Virginia + Washington: Washington + 'West Virginia': 'West Virginia' + Wisconsin: Wisconsin + Wyoming: Wyoming diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.time_zones.yml b/web/modules/contrib/webform/config/install/webform.webform_options.time_zones.yml new file mode 100644 index 000000000..dd7b9e5ce --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.time_zones.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: time_zones +label: 'Time zones' +options: '' diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.titles.yml b/web/modules/contrib/webform/config/install/webform.webform_options.titles.yml new file mode 100644 index 000000000..c6e9974c5 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.titles.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: titles +label: Titles +options: | + Miss: Miss + Ms: Ms + Mr: Mr + Mrs: Mrs + Dr: Dr diff --git a/web/modules/contrib/webform/config/install/webform.webform_options.yes_no.yml b/web/modules/contrib/webform/config/install/webform.webform_options.yes_no.yml new file mode 100644 index 000000000..245022966 --- /dev/null +++ b/web/modules/contrib/webform/config/install/webform.webform_options.yes_no.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +id: yes_no +label: Yes/No +options: | + Yes: Yes + No: No diff --git a/web/modules/contrib/webform/config/schema/webform.honeypot.schema.yml b/web/modules/contrib/webform/config/schema/webform.honeypot.schema.yml new file mode 100644 index 000000000..a85d6cdde --- /dev/null +++ b/web/modules/contrib/webform/config/schema/webform.honeypot.schema.yml @@ -0,0 +1,21 @@ +webform.admin_settings.third_party.honeypot: + type: mapping + label: 'Webform test third party settings' + mapping: + honeypot: + type: boolean + label: 'Protect all webforms with 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' + mapping: + honeypot: + type: boolean + label: 'Protect all webforms with Honeypot' + time_restriction: + type: boolean + label: 'Add time limit to all webforms' diff --git a/web/modules/contrib/webform/config/schema/webform.schema.yml b/web/modules/contrib/webform/config/schema/webform.schema.yml new file mode 100644 index 000000000..693fedb72 --- /dev/null +++ b/web/modules/contrib/webform/config/schema/webform.schema.yml @@ -0,0 +1,860 @@ +# Schema for the configuration files of the Webform module. + +webform.settings: + type: config_object + label: 'Webform settings' + mapping: + settings: + type: mapping + label: 'Webform default settings' + mapping: + default_page_base_path: + type: string + label: 'Default base path' + default_form_submit_label: + type: label + label: 'Default webform submit text' + default_form_submit_once: + type: boolean + label: 'Prevent duplicate submissions' + default_form_closed_message: + type: text + label: 'Default webform closed message' + default_form_exception_message: + type: text + label: 'Default webform exception message' + default_form_confidential_message: + type: text + label: 'Default webform confidential message' + default_form_novalidate: + type: boolean + label: 'Disable client-side validation' + default_form_unsaved: + type: boolean + label: 'Warn users about unsaved changes' + default_form_disable_back: + type: boolean + label: 'Disable back button' + default_form_details_toggle: + type: boolean + label: 'Display collapse/expand all details link' + default_wizard_prev_button_label: + type: label + label: 'Default wizard previous page button label' + default_wizard_next_button_label: + type: label + label: 'Default wizard next page button label' + default_wizard_start_label: + type: label + label: 'Default wizard start label' + default_wizard_complete_label: + type: label + label: 'Default wizard complete label' + default_preview_next_button_label: + type: label + label: 'Default preview button label' + default_preview_prev_button_label: + type: label + label: 'Default preview previous page button label' + default_preview_message: + type: text + label: 'Default preview message' + default_draft_button_label: + type: label + label: 'Default draft button label' + default_draft_saved_message: + type: text + label: 'Default draft save message' + default_draft_loaded_message: + type: text + label: 'Default draft load message' + default_confirmation_message: + type: text + label: 'Default confirmation message' + default_confirmation_back_label: + type: text + label: 'Default confirmation back label' + default_limit_total_message: + type: text + label: 'Default limit total message' + default_limit_user_message: + type: text + label: 'Default limit user message' + form_classes: + type: string + label: 'Webform CSS classes' + button_classes: + type: string + label: 'Button CSS classes' + confirmation_classes: + type: string + label: 'Confirmation CSS classes' + confirmation_back_classes: + type: string + label: 'Confirmation back link CSS classes' + assets: + type: mapping + label: 'Global Assets (CSS/JavaScript)' + mapping: + css: + type: string + label: 'CSS (Cascading Style Sheets)' + javascript: + type: string + label: 'JavaScript' + elements: + type: mapping + label: 'Elements default settings' + mapping: + allowed_tags: + type: string + label: 'Allowed tags' + wrapper_classes: + type: string + label: 'Wrapper CSS classes' + classes: + type: string + label: 'Element CSS classes' + default_description_display: + type: string + label: 'Default title display' + default_google_maps_api_key: + type: string + label: 'Default Google Maps API key' + excluded_types: + type: ignore + label: 'Excluded types' + file: + type: mapping + label: 'File upload default settings' + mapping: + file_public: + type: boolean + label: 'Allow files to be uploaded to public file system.' + default_max_filesize: + type: string + label: 'Default maximum upload size' + default_managed_file_extensions: + type: string + label: 'Default allowed managed file extensions' + default_image_file_extensions: + type: string + label: 'Default allowed image file extensions' + default_video_file_extensions: + type: string + label: 'Default allowed video file extensions' + default_audio_file_extensions: + type: string + label: 'Default allowed audio file extensions' + default_document_file_extensions: + type: string + label: 'Default allowed document file extensions' + format: + type: ignore + label: 'Format default settings' + mail: + type: mapping + label: 'Email default settings' + mapping: + default_from_mail: + type: email + label: 'Default email from address' + default_from_name: + type: label + label: 'Default email from name' + default_subject: + type: label + label: 'Default email subject' + default_body_text: + type: text + label: 'Default email body (Plain text)' + default_body_html: + type: text + label: 'Default email body (HTML)' + export: + type: mapping + label: 'Export default settings' + mapping: + exporter: + type: string + label: 'Results exporter' + delimiter: + type: string + label: 'Delimiter text format' + multiple_delimiter: + type: string + label: 'Element multiple values delimiter' + excel: + type: boolean + label: 'Open HTML table in Excel' + file_name: + type: string + label: 'File name' + header_format: + type: string + label: 'Column header format' + header_prefix: + type: boolean + label: 'Column header prefix' + header_prefix_key_delimiter: + type: string + label: 'Column header prefix key delimiter' + header_prefix_label_delimiter: + type: string + label: 'Column header prefix label delimiter' + entity_reference_format: + type: string + label: 'Options format' + options_format: + type: string + label: 'Options item format' + options_item_format: + type: string + label: 'Entity reference format' + likert_answers_format: + type: string + label: 'Likert answers format' + signature_format: + type: string + label: 'Signature format' + composite_element_item_format: + type: string + label: 'Composite element item format' + batch: + type: mapping + label: 'Batch settings' + mapping: + default_batch_export_size: + type: integer + label: 'Batch export size' + default_batch_update_size: + type: integer + label: 'Batch update size' + default_batch_delete_size: + type: integer + label: 'Batch delete size' + purge_settings: + type: mapping + label: 'Automated purging settings' + mapping: + cron_size: + type: integer + label: 'Amount of submissions to process' + test: + type: mapping + label: 'Test settings' + mapping: + types: + type: text + label: 'Test types' + names: + type: text + label: 'Test names' + ui: + type: mapping + label: 'User interface settings' + mapping: + video_display: + type: string + label: 'Video display' + dialog_disabled: + type: boolean + label: 'Disable dialogs' + offcanvas_disabled: + type: boolean + label: 'Disable system tray' + html_editor_disabled: + type: boolean + label: 'Disable HTML editor' + details_save: + type: boolean + label: 'Save details open/close state' + library: + type: mapping + label: 'Library settings' + mapping: + cdn: + type: boolean + label: 'Use CDN' + third_party_settings: + type: sequence + label: 'Third party settings' + sequence: + type: webform.admin_settings.third_party.[%key] + +webform.webform.*: + type: config_entity + label: 'Webforms' + mapping: + status: + type: string + label: 'Status' + open: + type: string + label: 'Open date/time' + close: + type: string + label: 'Close date/time' + uid: + type: integer + label: 'Author' + template: + type: boolean + label: 'Template' + id: + type: string + label: 'Machine name' + title: + type: label + label: 'Title' + description: + type: label + label: 'Administrative description' + elements: + type: text + label: 'Elements (YAML)' + css: + type: string + label: 'CSS (Cascading Style Sheets)' + javascript: + type: string + label: 'JavaScript' + settings: + type: mapping + label: 'Settings' + mapping: + page: + type: boolean + label: 'Enable page' + page_submit_path: + type: string + label: 'Page submit URL alias' + page_confirm_path: + type: string + label: 'Page confirm URL alias' + form_submit_label: + type: label + label: 'Webform submit text' + form_submit_once: + type: boolean + label: 'Prevent duplicate submissions' + form_submit_attributes: + type: ignore + label: 'Webform submit attributes' + form_closed_message: + type: text + label: 'Webform closed message' + form_exception_message: + type: text + label: 'Webform exception message' + form_previous_submissions: + type: boolean + label: 'Webform previous submissions notification' + form_confidential: + type: boolean + label: 'Webform confidential' + form_confidential_message: + type: text + label: 'Webform confidential message' + form_prepopulate: + type: boolean + label: 'Webform prepopulate elements' + form_prepopulate_source_entity: + type: boolean + label: 'Webform prepopulate source entity' + form_unsaved: + type: boolean + label: 'Warn users about unsaved changes' + form_disable_back: + type: boolean + label: 'Disable back button' + form_disable_autocomplete: + type: boolean + label: 'Disable autocompletion' + form_novalidate: + type: boolean + label: 'Disable client-side validation' + form_autofocus: + type: boolean + label: 'Autofocus' + form_details_toggle: + type: boolean + label: 'Display collapse/expand all details link' + wizard_progress_bar: + type: boolean + label: 'Show wizard progress bar' + wizard_progress_pages: + type: boolean + label: 'Show wizard progress pages' + wizard_progress_percentage: + type: boolean + label: 'Show wizard progress pages' + wizard_start_label: + type: label + label: 'Wizard start label' + wizard_start_attributes: + type: ignore + label: 'Wizard start attributes' + wizard_complete: + type: boolean + label: 'Include confirmation page in progress' + wizard_complete_label: + type: label + label: 'Wizard complete label' + wizard_prev_button_label: + type: label + label: 'Wizard previous page button label' + wizard_prev_button_attributes: + type: ignore + label: 'Wizard previous page button attributes' + wizard_next_button_label: + type: label + label: 'Wizard next page button label' + wizard_next_button_attributes: + type: ignore + label: 'Wizard next page button attributes' + preview: + type: integer + label: 'Enable preview page' + preview_next_button_label: + type: label + label: 'Preview button label' + preview_next_button_attributes: + type: ignore + label: 'Preview button attributes' + preview_prev_button_label: + type: label + label: 'Previous page button label' + preview_prev_button_attributes: + type: ignore + label: 'Previous page button attributes' + preview_message: + type: text + label: 'Preview message' + draft: + type: boolean + label: 'Allow your users to save and finish the webform later.' + draft_auto_save: + type: boolean + label: 'Automatically save as draft when previewing and when there are validation errors.' + draft_button_label: + type: label + label: 'Draft button label' + draft_button_attributes: + type: ignore + label: 'Draft button attributes' + draft_saved_message: + type: text + label: 'Draft saved message' + draft_loaded_message: + type: text + label: 'Draft loaded message' + confirmation_type: + type: string + label: 'Confirmation type' + confirmation_url: + type: string + label: 'Confirmation URL' + confirmation_title: + type: text + label: 'Confirmation title' + confirmation_message: + type: text + label: 'Confirmation message' + confirmation_attributes: + type: ignore + label: 'Confirmation attributes' + confirmation_back: + type: boolean + label: 'Display back to webform link.' + confirmation_back_label: + type: text + label: 'Confirmation back link label' + confirmation_back_attributes: + type: ignore + label: 'Confirmation back link attributes' + limit_total: + type: integer + label: 'Limit total submissions' + limit_total_message: + type: text + label: 'Limit total message' + limit_user: + type: integer + label: 'Limit user submissions' + limit_user_message: + type: text + label: 'Limit user message' + purge: + type: string + label: 'Default purging' + purge_days: + type: integer + label: 'Default days to retain submissions' + entity_limit_total: + type: integer + label: 'Entity limit total submissions' + entity_limit_user: + type: integer + label: 'Entity limit user submissions' + results_disabled: + type: boolean + label: 'Results disabled' + results_disabled_ignore: + type: boolean + label: 'Ignore disabled results warning' + token_update: + 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' + 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' + 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' + 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' + 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' + 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' + 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' + 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' + handlers: + type: sequence + label: 'Webform handlers' + sequence: + type: mapping + mapping: + id: + type: string + handler_id: + type: string + label: + type: label + status: + type: boolean + weight: + type: integer + settings: + type: webform.handler.[%parent.id] + third_party_settings: + type: sequence + label: 'Third party settings' + sequence: + type: webform.settings.third_party.[%key] + +webform.handler.*: + type: mapping + label: 'Handler settings' + +webform.handler.email: + type: mapping + label: 'Email' + mapping: + to_mail: + label: 'Email to address' + type: email + bcc_mail: + label: 'Email Bcc address' + type: email + cc_mail: + label: 'Email Cc address' + type: email + from_mail: + label: 'Email from address' + type: email + from_name: + label: 'Email from name' + type: label + subject: + label: 'Email subject' + type: label + body: + label: 'Email body' + type: text + excluded_elements: + type: sequence + label: 'Exclude elements' + sequence: + type: string + label: 'Element name' + html: + type: boolean + label: 'HTML' + attachments: + type: boolean + label: 'Attachments' + debug: + type: boolean + label: 'Enable debugging' + +webform.handler.remote_post: + type: mapping + label: 'Remote Post' + mapping: + type: + label: 'Type' + type: string + insert_url: + label: 'Insert URL' + type: uri + update_url: + label: 'Update URL' + type: uri + delete_url: + label: 'Delete URL' + type: uri + excluded_data: + type: sequence + label: 'Excluded data' + sequence: + type: string + label: 'Data name' + custom_data: + label: 'Custom data' + type: string + insert_custom_data: + label: 'Insert custom data' + type: string + update_custom_data: + label: 'Update custom data' + type: string + delete_custom_data: + label: 'Delete custom data' + type: string + debug: + type: boolean + label: 'Enable debugging' + +webform.exporter.*: + type: mapping + label: 'Exporter settings' + +webform.exporter.delimited_text: + type: mapping + label: 'Delimiter' + mapping: + delimiter: + type: string + label: 'Delimiter' + +webform.webform_options.*: + type: config_entity + label: 'Options' + mapping: + id: + type: string + label: 'Machine name' + label: + type: label + label: 'Label' + options: + type: text + label: 'Options (YAML)' + +block.settings.webform_block: + type: block_settings + label: 'Webforms block' + mapping: + webform_id: + type: text + label: 'Webforms' + default_data: + type: text + label: 'Default webform submission data' + +# Schema for the configuration of the webform field type. + +field.storage_settings.webform: + type: mapping + label: 'Webform field storage settings' + mapping: + target_type: + type: string + label: 'Type of item to reference' + +base_webform_field_field_settings: + type: mapping + mapping: + handler: + type: string + label: 'Reference method' + handler_settings: + type: entity_reference_selection.[%parent.handler] + label: 'Entity reference selection settings' + +field.field_settings.webform: + type: base_webform_field_field_settings + label: 'Webform settings' + mapping: + default_data: + type: string + label: 'Default webform submission data' + status: + type: boolean + label: 'Status of the webform' + +field.widget.settings.webform_entity_reference_autocomplete: + type: mapping + label: 'Webform autocomplete display format settings' + mapping: + match_operator: + type: string + label: 'Autocomplete matching' + size: + type: integer + label: 'Size of textfield' + placeholder: + type: label + label: 'Placeholder' + +field.formatter.settings.webform_entity_reference_entity_view: + type: mapping + label: 'Display the referenced webform with default submission data.' + +field.formatter.settings.webform_entity_reference_link: + type: mapping + label: 'Display the referenced webform as a link.' + mapping: + label: + type: label + label: 'Link label to the referenced webform' + +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' + +condition.plugin.webform: + type: condition.plugin + mapping: + webforms: + type: sequence + sequence: + type: string diff --git a/web/modules/contrib/webform/config/schema/webform.views.schema.yml b/web/modules/contrib/webform/config/schema/webform.views.schema.yml new file mode 100644 index 000000000..eb8a28294 --- /dev/null +++ b/web/modules/contrib/webform/config/schema/webform.views.schema.yml @@ -0,0 +1,3 @@ +views.field.webform_submission_bulk_form: + type: views_field_bulk_form + label: 'Webform submission bulk form' diff --git a/web/modules/contrib/webform/css/webform.admin.css b/web/modules/contrib/webform/css/webform.admin.css new file mode 100644 index 000000000..9e3d1df86 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.admin.css @@ -0,0 +1,173 @@ +/** + * @file + * Admin styles + */ + +/** + * Hide throbber which causes layout issue in dropbutton + */ +.dropbutton-multiple .ajax-progress-throbber { + display: none; +} + +/** + * Webform setting button displays a gear icon. + * @see /admin/structure/webform/manage/contact/results/table + */ +.button.button-webform-setting { + margin: 0 0 1em 0; +} + +.button.button-webform-setting:before { + content: '⚙'; + font-size: 1.2em; +} + +/** + * Add light gray background to Add-on details widget. + * @see /admin/structure/webform/addons + */ +.webform-addons details { + background-color: #f8f8f8; +} + +/** + * 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/results/manage + * @see /admin/structure/webform/manage/contact/results/submissions + * @see /admin/structure/webform/manage/contact/results/table + */ +.webform-results__table th, +.webform-results__table td { + max-width: 400px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* Make sure 'Operations' is never cut-off */ +.webform-results__table td:last-child { + overflow: visible; + max-width: inherit; + white-space: normal; +} + +th.webform-results__icon, +td.webform-results__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 { + display: none; +} + +/* Entire row is clickable. @see Drupal.behaviors.webformTableRowHref */ +.webform-results__table tbody tr { + cursor: pointer; + cursor: hand; +} + +/** + * 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 (sticky & notes) + */ +.webform-icon { + display: inline-block; + height: 16px; + width: 16px; + text-align: center; + background-color: transparent; + background-repeat: no-repeat; + background-position: center center; + background-size: 100% auto; + vertical-align: -2px; +} + +.webform-icon-notes--on { + background-image: url(../images/icons/notes-on.svg); +} + +.webform-icon-notes--off { + background-image: url(../images/icons/notes-off.svg); +} + +.webform-icon-notes--link { + background-image: url(../images/icons/notes-link.svg); +} + +a:hover .webform-icon-notes--on, +a:focus .webform-icon-notes--on, +a:hover .webform-icon-notes--off, +a:focus .webform-icon-notes--off { + background-image: url(../images/icons/notes-link.svg); +} + +.webform-icon-sticky { + background: transparent url(../images/icons/sticky.svg) no-repeat left top; + display: inline-block; + background-size: 64px 16px; +} + +.webform-icon-sticky--off:hover, +.webform-icon-sticky--off:focus { + background-position: -16px top; +} +.webform-icon-sticky--on { + background-position: -32px top; +} +.webform-icon-sticky--on:focus, +.webform-icon-sticky--on:hover { + background-position: -48px top; +} + +.webform-icon-sticky--link { + background: transparent url(../images/icons/sticky-link.svg) no-repeat left top; +} + +/** + * Submission view table. + * @see /admin/structure/webform/manage/{webform_id}/submission/{webform_submission_id}/table + */ +.webform-submission__table th { + width: 33%; + min-width: 100px; +} + +.webform-submission__table td { + width: 66%; +} + +.webform-horizontal-rule { + margin: .5em 0; +} diff --git a/web/modules/contrib/webform/css/webform.admin.translation.css b/web/modules/contrib/webform/css/webform.admin.translation.css new file mode 100644 index 000000000..78dfeece7 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.admin.translation.css @@ -0,0 +1,12 @@ +/** + * @file + * Translation styles. + */ + +/** + * Remove margin in left column in translate tab to ensure form items line up. + * @see /admin/structure/webform/manage/contact/translate/es/add + */ +.webform-translation-source .form-item { + margin: 0; +} diff --git a/web/modules/contrib/webform/css/webform.assets.css b/web/modules/contrib/webform/css/webform.assets.css new file mode 100644 index 000000000..908e641cb --- /dev/null +++ b/web/modules/contrib/webform/css/webform.assets.css @@ -0,0 +1,8 @@ +/** + * @file + * This an empty placeholder file that is replaced with custom CSS. + * + * @see webform_css_alter() + * @see _webform_asset_alter() + * @see \Drupal\webform\WebformSubmissionForm::form + */ diff --git a/web/modules/contrib/webform/css/webform.confirmation.css b/web/modules/contrib/webform/css/webform.confirmation.css new file mode 100644 index 000000000..4895f346e --- /dev/null +++ b/web/modules/contrib/webform/css/webform.confirmation.css @@ -0,0 +1,11 @@ +/** + * @file + * Confirmation styles. + * + * @see /webform/contact/confirmation + */ + +.webform-confirmation__message, +.webform-confirmation__back { + margin: 0 0 1em 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.codemirror.css b/web/modules/contrib/webform/css/webform.element.codemirror.css new file mode 100644 index 000000000..e75223c3e --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.codemirror.css @@ -0,0 +1,27 @@ +/** + * @file + * CodeMirror styles. + * + * @see /webform/test_element_codemirror/test + */ + +/* Clean up CodeMirror editor */ +form .CodeMirror { + border: 1px solid #ccc; + font-size: 13px; + height: auto; +} + +form .CodeMirror-scroll { + overflow-y: hidden; + overflow-x: auto; +} + +/* Clean up CodeMirror viewer */ +pre.webform-codemirror-runmode { + border: 1px solid #ccc; + font-size: 13px; + overflow: auto; + padding: 3px; + margin: 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.color.css b/web/modules/contrib/webform/css/webform.element.color.css new file mode 100644 index 000000000..3c6819bd0 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.color.css @@ -0,0 +1,44 @@ +/** + * @file + * Element color styles + * + * @see \Drupal\webform\Plugin\WebformElement\Color + * @see webform-element-color-value-swatch.html.twig + * @see http://stackoverflow.com/questions/11167281/webkit-css-to-control-the-box-around-the-color-in-an-elementtype-color + */ + +.webform-submission-form input.form-color { + margin: inherit; + padding: 0; + border: 1px solid #ccc; +} + +.webform-submission-form input.form-color::-webkit-color-swatch-wrapper { + padding: 0; +} + +.webform-submission-form input.form-color::-webkit-color-swatch { + border: none; +} + +input.form-color.form-color-small { + width: 16px; + height: 16px; + font-size: smaller; +} + +input.form-color.form-color-medium { + width: 24px; + height: 24px; +} + +input.form-color.form-color-large { + width: 36px; + height: 36px; +} + +input.form-color.form-color-output { + width: 6em; + padding: 0 .5em; + border-left: 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.composite.css b/web/modules/contrib/webform/css/webform.element.composite.css new file mode 100644 index 000000000..50083c57a --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.composite.css @@ -0,0 +1,11 @@ +/** + * @file + * Composite element styles + * + * @see /webform/example_elements_composite + */ + +/* Remove extra margin are composite element which already contain form elements with margins */ +fieldset.form-composite { + margin: 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.composite_telephone.css b/web/modules/contrib/webform/css/webform.element.composite_telephone.css new file mode 100644 index 000000000..4bda0d64d --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.composite_telephone.css @@ -0,0 +1,22 @@ +/** + * @file + * Composite telephone element styles. + */ + +.webform-telephone:after { + content: ""; + display: table; + clear: both; +} + +.webform-telephone .form-item, +.webform-telephone .form-item label { + float: left; + margin: 0 10px 0 0; + vertical-align: middle; +} + +.webform-telephone .form-item label { + line-height: 2em; + height: 2em; +} diff --git a/web/modules/contrib/webform/css/webform.element.details.toggle.css b/web/modules/contrib/webform/css/webform.element.details.toggle.css new file mode 100644 index 000000000..f980233eb --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.details.toggle.css @@ -0,0 +1,21 @@ +/** + * @file + * Element details toggle all styles. + * + * @see /webform/test_form_details_toggle + */ + +.webform-details-toggle-state-wrapper { + text-align: right; /* LTR */ +} +[dir="rtl"] .webform-details-toggle-state-wrapper { + text-align: left; +} + +.webform-details-toggle-state-wrapper { + margin-top: 1em; +} + +.webform-details-toggle-state-wrapper + details { + margin-top: 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.flexbox.css b/web/modules/contrib/webform/css/webform.element.flexbox.css new file mode 100644 index 000000000..d80266d36 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.flexbox.css @@ -0,0 +1,167 @@ +/** + * @file + * Flexbox styles. + * + * @see /webform/example_layout_flexbox + * @see /webform/test_element_flexbox_flex + */ + +@media (min-width: 768px) { + + .webform-flexbox { + display: -webkit-flex; + display: flex; + margin: 1em -.5em; + } + + .webform-flexbox .webform-flexbox { + margin: 1em 0; + } + + .webform-flexbox--flex-start { + -webkit-align-items: flex-start; + align-items: flex-start; + } + + .webform-flexbox--flex-end { + -webkit-align-items: flex-end; + align-items: flex-end; + } + + .webform-flexbox--center { + -webkit-align-items: center; + align-items: center; + } + + .webform-flex { + min-width: 0; + } + + .webform-flex--container { + margin: 0 .5em; + } + + .webform-flex--container > .form-item { + margin: 0; + } + + .webform-flex--container > .form-item > input, + .webform-flex--container > .form-item > select { + width: 100%; + } + + .container-inline .webform-flex--container > .form-item > input, + .container-inline .webform-flex--container > .form-item > select { + width: inherit; + } + + .webform-flex--container > .webform-has-field-prefix > input, + .webform-flex--container > .webform-has-field-suffix > input, + .webform-flex--container > .webform-has-field-prefix > select, + .webform-flex--container > .webform-has-field-suffix > select { + width: 85%; + } + + .webform-flex--container > .webform-has-field-prefix.webform-flex.webform-has-field-suffix > input, + .webform-flex--container > .webform-has-field-prefix.webform-flex.webform-has-field-suffix > select { + width: 70%; + } + + .webform-flex--1 { + -webkit-flex: 1; + flex: 1; + } + + .webform-flex--2 { + -webkit-flex: 2; + flex: 2; + } + + .webform-flex--3 { + -webkit-flex: 3; + flex: 3; + } + + .webform-flex--4 { + -webkit-flex: 4; + flex: 4; + } + + .webform-flex--5 { + -webkit-flex: 5; + flex: 5; + } + + .webform-flex--6 { + -webkit-flex: 6; + flex: 6; + } + + .webform-flex--7 { + -webkit-flex: 7; + flex: 7; + } + + .webform-flex--8 { + -webkit-flex: 8; + flex: 8; + } + + .webform-flex--9 { + -webkit-flex: 9; + flex: 9; + } + + .webform-flex--10 { + -webkit-flex: 10; + flex: 10; + } + + .webform-flex--11 { + -webkit-flex: 11; + flex: 11; + } + + .webform-flex--12 { + -webkit-flex: 12; + flex: 12; + } + + /** + * Select other. + */ + .webform-flex--container > .form-type-webform-select-other select { + width: 100%; + } + + .webform-flex--container > .form-type-webform-select-other .form-item { + margin: 0; + } + + .webform-flex--container > .form-type-webform-select-other .form-item + .form-item { + margin-top: .5em; + } + + /** + * Range. + */ + .webform-flex--container > .form-type-range-output > .form-range-output { + width: 66%; + } + + /** + * Composite. + */ + .form-composite > .webform-flexbox { + margin: 1em -.5em; + } + + /** + * Radios/checkboxes. + */ + .form-type-checkbox, + .form-type-radio { + white-space: nowrap; + } + +} diff --git a/web/modules/contrib/webform/css/webform.element.image_file.css b/web/modules/contrib/webform/css/webform.element.image_file.css new file mode 100644 index 000000000..99bd1abbc --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.image_file.css @@ -0,0 +1,11 @@ +/** + * @file + * Image file styles. + * + * @see /webform/test_element_media_file + */ + +img.webform-image-file { + max-width: 100%; + height: auto; +} diff --git a/web/modules/contrib/webform/css/webform.element.likert.css b/web/modules/contrib/webform/css/webform.element.likert.css new file mode 100644 index 000000000..edb941b13 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.likert.css @@ -0,0 +1,102 @@ +/** + * @file + * Likert element styles. + * + * @see /webform/test_element_likert + */ + +/** + * Answer column widths. + */ +[data-likert-answers-count="2"] th { + width: 30%; +} +[data-likert-answers-count="3"] th { + width: 20%; +} +[data-likert-answers-count="4"] th { + width: 15%; +} +[data-likert-answers-count="5"] th { + width: 12%; +} +[data-likert-answers-count="6"] th { + width: 10%; +} +[data-likert-answers-count="7"] th { + width: 8.57%; +} +[data-likert-answers-count="8"] th { + width: 7.5%; +} +[data-likert-answers-count="9"] th { + width: 6.66%; +} +[data-likert-answers-count="10"] th { + width: 6%; +} + +/** + * Basic table formatting. + */ +.webform-likert-table th, +.webform-likert-table td { + text-align: center; +} + +.form-type-webform-likert td label { + display: none; +} + +.webform-likert-table th:first-child, +.webform-likert-table td:first-child { + text-align: inherit; + width: 40%; +} + +.form-type-webform-likert td:first-child label { + display: block; +} + +/** + * Mobile support to likert webform element table. + */ +@media (max-width: 768px) { + .form-type-webform-likert table { + border-collapse: collapse; + font-size: inherit; + } + + .form-type-webform-likert thead { + display: none; + } + + .form-type-webform-likert tr, + .form-type-webform-likert tr.odd { + border: 0; + background: transparent; + } + + .form-type-webform-likert td { + display: block; + border: 0; + background: transparent; + padding: 0; + text-align: inherit; + } + + .webform-likert-table[data-likert-answers-count] th, + .webform-likert-table[data-likert-answers-count] th:first-child, + .webform-likert-table td, + .webform-likert-table td:first-child { + width: 100%; + } + + .form-type-webform-likert td label { + display: inline; + } + + .form-type-webform-likert td:last-child { + margin-bottom: 1em; + } +} diff --git a/web/modules/contrib/webform/css/webform.element.location.css b/web/modules/contrib/webform/css/webform.element.location.css new file mode 100644 index 000000000..a56011e2e --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.location.css @@ -0,0 +1,16 @@ +/** + * @file + * Location element styles. + * + * @see /webform/test_element_location + */ + +/** + * Remove power by Google. + * + * @see http://stackoverflow.com/questions/12327651/how-to-remove-the-powered-by-google-logo-in-the-google-map-autocomplete-dropdo + */ +.pac-container:after { + background-image: none; + height: 0; +} diff --git a/web/modules/contrib/webform/css/webform.element.message.css b/web/modules/contrib/webform/css/webform.element.message.css new file mode 100644 index 000000000..fc8c6873c --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.message.css @@ -0,0 +1,76 @@ +/** + * @file + * Messages element styles. + * + * @see /webform/test_element_message + */ + +/** + * Add styles to default (info) message, + */ +.messages { + box-shadow: -8px 0 0 #3b3b3b; /* LTR */ +} +[dir="rtl"] .messages { + box-shadow: 8px 0 0 #3b3b3b; + margin-left: 0; +} + +.messages.messages--info { + color: #31708f; + background-color: #d9edf7; + background-image: url(../images/icons/info.svg); + border-color: #0074bd #0074bd #0074bd transparent; /* LTR */ + box-shadow: -8px 0 0 #0074bd; /* LTR */ +} +[dir="rtl"] .messages.messages--info { + border-color: #0074bd transparent #0074bd #0074bd; + box-shadow: 8px 0 0 #0074bd; + margin-left: 0; +} + +/** + * Webform message close container. + */ +.webform-message--close .messages { + position: relative; +} + +/** + * Webform message close link. + */ +.webform-message--close .webform-message__link { + display: none; +} + +html.js .webform-message--close .webform-message__link { + display: block; + position: absolute; + top: 11px; + right: 10px; + line-height: 24px; + font-size: 24px; +} + +.webform-message__link { + color: inherit; + opacity: 0.33; +} + +.webform-message__link:link { + border-bottom: none; + text-decoration: none; +} + +.webform-message__link:hover, +.webform-message__link:focus, +.webform-message__link:active { + border-bottom: none; + text-decoration: none; + color: inherit; + opacity: 1; +} + +html.js .js-webform-message--close-storage { + display: none; +} diff --git a/web/modules/contrib/webform/css/webform.element.multiple.css b/web/modules/contrib/webform/css/webform.element.multiple.css new file mode 100644 index 000000000..a16b93072 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.multiple.css @@ -0,0 +1,103 @@ +/** + * @file + * Multiple element styles. + * + * @see /webform/test_element_multiple + */ + +.form-type-webform-multiple:after { + content: " "; + display: table; + clear: both; +} + +.webform-multiple-table table { + margin-bottom: 2px; +} + +.webform-multiple-table td { + white-space: nowrap; +} + +.webform-multiple-table td:first-child { + padding: 0 0 0 1em; + width: 26px; + vertical-align: middle; + text-align: center; +} + +.webform-multiple-table td:last-child { + padding: 0 2px; + vertical-align: middle; + text-align: center; + width: 60px; +} + + +.webform-multiple-table td:last-child input { + margin: 0 2px; +} + +.webform-multiple-table td:last-child input { + opacity: 0.4; +} + +.webform-multiple-table td:last-child input:hover, +.webform-multiple-table td:last-child input:focus, +.webform-multiple-table td:last-child input:active { + opacity: 1; + background: none; +} + +.webform-multiple-table td select, +.webform-multiple-table td .form-type-textfield input, +.webform-multiple-table td .form-type-email input, +.webform-multiple-table td .form-type-url input, +.webform-multiple-table td .form-type-tel input, +.webform-multiple-table td .form-type-entity-autocomplete input, +.webform-multiple-table td .form-type-webform-autocomplete input { + width: 100%; +} +.webform-multiple-table td .form-type-datelist select, +.form-type-webform-multiple.webform-has-field-prefix td select, +.form-type-webform-multiple.webform-has-field-suffix td select, +.form-type-webform-multiple.webform-has-field-prefix td input, +.form-type-webform-multiple.webform-has-field-suffix td input { + width: auto; +} + +.webform-multiple-table .webform-multiple-sort-weight { + width: 4em; +} + +.webform-multiple-table td input[type="image"] { + padding: 0; + width: 16px; + height: 16px; +} + +.webform-multiple-table .container-inline input[type="submit"] { + width: inherit; +} + +/** + * Hide AJAX throbber. + */ +.form-type-webform-multiple .ajax-progress-throbber { + display: none; +} + +/** + * Suppress table drag warnings. + */ +.webform-multiple-table .tabledrag-changed-warning, +.webform-multiple-table .tabledrag-changed { + display: none !important; /* Must use !important because .tabledrag-changed 'display' is set via JavaScript */ +} + +/** + * Tweak tabledrag toggle weight. + */ +.webform-multiple-tabledrag-toggle-weight { + float: right; +} diff --git a/web/modules/contrib/webform/css/webform.element.options.css b/web/modules/contrib/webform/css/webform.element.options.css new file mode 100644 index 000000000..3c6789820 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.options.css @@ -0,0 +1,38 @@ +/** + * @file + * Element options display styles. + * + * @see /webform/example_layout_basic + */ + +.webform-options-display-side-by-side > div { + display: inline-block; + margin-right: .5em; +} + +/** + * Display columns for devices that are more than 400px wide. + */ +@media (min-width: 400px) { + .webform-options-display-two-columns { + display: inline-block; + margin-top: .4em; + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; + } + + .webform-options-display-three-columns { + display: inline-block; + margin-top: .4em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + } + + .webform-options-display-two-columns div.form-item, + .webform-options-display-three-columns div.form-item { + margin-top: 0; + margin-bottom: 0; + } +} diff --git a/web/modules/contrib/webform/css/webform.element.other.css b/web/modules/contrib/webform/css/webform.element.other.css new file mode 100644 index 000000000..6ff186c67 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.other.css @@ -0,0 +1,13 @@ +/** + * @file + * Other element styles. + * + * @see /webform/test_element_other + */ + +html.js .webform-select-other .webform-select-other-input, +html.js .webform-checkboxes-other .webform-checkboxes-other-input, +html.js .webform-radios-other .webform-radios-other-input, +html.js .webform-buttons-other .webform-buttons-other-input { + display: none; +} diff --git a/web/modules/contrib/webform/css/webform.element.range.css b/web/modules/contrib/webform/css/webform.element.range.css new file mode 100644 index 000000000..611974409 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.range.css @@ -0,0 +1,21 @@ +/** + * @file + * Element range styles. + * + * @see /webform/example_elements#edit-range + * @see http://thenewcode.com/754/input-Antipode-The-HTML5-output-Element + */ + +.form-range.form-range-output { + margin-right: 8px; +} + +.form-range-output-container { + white-space: nowrap; +} + +.form-range-output-container input, +.form-range-output-container .field-prefix, +.form-range-output-container .field-suffix { + margin-left: 2px; +} diff --git a/web/modules/contrib/webform/css/webform.element.rating.css b/web/modules/contrib/webform/css/webform.element.rating.css new file mode 100644 index 000000000..38a4f72f7 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.rating.css @@ -0,0 +1,94 @@ +/** + * @file + * Rating element styles. + * + * @see /webform/example_elements + */ + +html.js .form-webform-rating { + display: none; +} + +div.svg div.rateit-range { + background: url(../images/rating/star-normal.svg); +} + +div.svg div.rateit-hover { + background: url(../images/rating/star-hover.svg); +} + +div.svg div.rateit-selected { + background: url(../images/rating/star-selected.svg); +} + +div.svg div.rateit-preset { + background: url(../images/rating/star-preset.svg); +} + +div.svg button.rateit-reset { + background: url(../images/rating/reset-normal.svg); +} + +div.svg button.rateit-reset:hover { + background: url(../images/rating/reset-hover.svg); +} + +/** + * Small + */ +div.svg.rateit-small { + height: 16px; +} + +div.svg.rateit-small div.rateit-range, +div.svg.rateit-small div.rateit-hover, +div.svg.rateit-small div.rateit-selected, +div.svg.rateit-small div.rateit-preset { + background-size: 16px 16px; +} + +div.svg.rateit-small button.rateit-reset { + height: 16px; + width: 16px; + background-size: 16px 16px; +} + +/** + * Medium + */ +div.svg.rateit-medium { + height: 24px; +} + +div.svg.rateit-medium div.rateit-range, +div.svg.rateit-medium div.rateit-hover, +div.svg.rateit-medium div.rateit-selected, +div.svg.rateit-medium div.rateit-preset { + background-size: 24px 24px; +} + +div.svg.rateit-medium button.rateit-reset { + height: 24px; + width: 24px; + background-size: 24px 24px; +} + +/** + * Large + */ +div.svg.rateit-large { + height: 36px; +} + +div.svg.rateit-large div.rateit-range, +div.svg.rateit-large div.rateit-hover, +div.svg.rateit-large div.rateit-selected, +div.svg.rateit-large div.rateit-preset { + background-size: 32px 32px; +} + +div.svg.rateit-large button.rateit-reset { + height: 32px; + width: 32px; + background-size: 32px 32px; +} diff --git a/web/modules/contrib/webform/css/webform.element.select2.css b/web/modules/contrib/webform/css/webform.element.select2.css new file mode 100644 index 000000000..67688f227 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.select2.css @@ -0,0 +1,14 @@ +/** + * @file + * Select2 styles. + */ + +/* + * Make sure container is visible. + * - Fixes Select2 not working with dialog or system tray. + * .ui-dialog z-index is 1260. @see core/themes/seven/css/components/dialog.css + */ +.select2-container--open { + z-index: 1261; +} + diff --git a/web/modules/contrib/webform/css/webform.element.signature.css b/web/modules/contrib/webform/css/webform.element.signature.css new file mode 100644 index 000000000..00ca88ae3 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.signature.css @@ -0,0 +1,45 @@ +/** + * @file + * Signature element styles. + * + * @see /webform/example_elements + * @see https://css-tricks.com/snippets/sass/maintain-aspect-ratio-mixin/ + */ + +.webform-signature-pad { + position: relative; + background-color: #f0f0f0; +} + +.webform-signature-pad:before { + display: block; + content: ""; + width: 100%; + padding-top: 33%; +} + +.webform-signature-pad canvas { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: block; + cursor: crosshair; +} + +/* Position 'Reset' button in right hand corner of the canvas */ +.webform-signature-pad .button { + z-index: 100; + position: absolute; + top: 2px; + right: 2px; +} + +/* Add border around signature image */ +.webform-signature-pad-image { + display: block; + width: 100%; + max-width: 600px; /* Max width is used to prevent signature pad from getting too wide and tall */ + border: 1px solid #ccc; +} diff --git a/web/modules/contrib/webform/css/webform.element.states.css b/web/modules/contrib/webform/css/webform.element.states.css new file mode 100644 index 000000000..405dbda4f --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.states.css @@ -0,0 +1,71 @@ +/** + * @file + * States element styles. + * + * @see /webform/test_element_states + */ + +.webform-states-table td { + white-space: nowrap; +} + +.webform-states-table--state { + border-top: 2px solid #a6a6a6; +} + +.webform-states-table--state:first-child { + border-top: inherit; +} + +.webform-states-table--state td select { + width: inherit; + max-width: inherit; +} + +.webform-states-table--state td:first-child select, +.webform-states-table--condition select, +.webform-states-table--condition input[type="text"] { + width: 100%; + max-width: 100%; +} + +.webform-states-table .image-button { + margin: 0; +} + +.webform-states-table th:last-child, +.webform-states-table td:last-child { + padding: 0 2px; + vertical-align: middle; + text-align: center; +} + +.webform-states-table td:last-child input { + margin: 0 2px; +} + +.webform-states-table td:last-child input { + opacity: 0.4; +} + +.webform-states-table td:last-child input:hover, +.webform-states-table td:last-child input:focus, +.webform-states-table td:last-child input:active { + opacity: 1; + background: none; +} + +/** + * Hide AJAX throbber. + */ +.form-type-webform-element-states .ajax-progress-throbber { + display: none; +} + +/** + * Suppress table drag warnings. + */ +.webform-states-table .tabledrag-changed-warning, +.webform-states-table .tabledrag-changed { + display: none !important; /* Must use !important because .tabledrag-changed 'display' is set via JavaScript */ +} diff --git a/web/modules/contrib/webform/css/webform.element.tableselect.css b/web/modules/contrib/webform/css/webform.element.tableselect.css new file mode 100644 index 000000000..aeda6cc65 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.tableselect.css @@ -0,0 +1,12 @@ +/** + * @file + * Table select element styles. + * + * @see /webform/test_element_table + */ + +/* Make the first column containing a checkbox/radio as small as possible. */ +.tableselect th:first-child, +.tableselect td:first-child { + width: 1px; +} diff --git a/web/modules/contrib/webform/css/webform.element.tableselect_sort.css b/web/modules/contrib/webform/css/webform.element.tableselect_sort.css new file mode 100644 index 000000000..7f803f5a1 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.tableselect_sort.css @@ -0,0 +1,12 @@ +/** + * @file + * Table select sort element styles. + * + * @see /webform/test_element_table + */ + +.tableselect-sort th:first-child, +.tableselect-sort td:first-child { + white-space: nowrap; + width: 40px; +} diff --git a/web/modules/contrib/webform/css/webform.element.toggle.css b/web/modules/contrib/webform/css/webform.element.toggle.css new file mode 100644 index 000000000..a29a768c0 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.toggle.css @@ -0,0 +1,44 @@ +/** + * @file + * Toggle element styles. + * + * @see /webform/example_elements + */ + +html.js .form-type-webform-toggle .form-checkbox { + display: none; +} + +html.js .form-type-webform-toggle label.option { + display: block; + float: left; +} + +html.js .form-type-webform-toggle label + input + .toggle { + margin: 2px 0; +} + +html.js .toggle { + display: block; + float: left; + margin: 2px .5em 2px 0; + text-transform: uppercase; +} + +html.js .toggle.toggle-small, +html.js .toggle.toggle-small + label { + height: 16px; + line-height: 16px; +} + +html.js .toggle.toggle-medium, +html.js .toggle.toggle-medium+ label { + height: 24px; + line-height: 24px; +} + +html.js .toggle.toggle-large, +html.js .toggle.toggle-large + label { + height: 36px; + line-height: 36px; +} diff --git a/web/modules/contrib/webform/css/webform.element.video_file.css b/web/modules/contrib/webform/css/webform.element.video_file.css new file mode 100644 index 000000000..3c59d6b55 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.element.video_file.css @@ -0,0 +1,21 @@ +/** + * @file + * Video file styles. + * + * @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%; +} diff --git a/web/modules/contrib/webform/css/webform.form.css b/web/modules/contrib/webform/css/webform.form.css new file mode 100644 index 000000000..f2600a45a --- /dev/null +++ b/web/modules/contrib/webform/css/webform.form.css @@ -0,0 +1,41 @@ +/** + * @file + * Webform styles. + */ + +/** + * Container inline + */ +.form-item .container-inline { + margin: 2px 0; +} + +/** + * Issue #2731991: Setting required on radios marks all options required. + */ +.form-checkboxes .form-required:after, +.form-radios .form-required:after { + display: none; +} + +/** + * Element inline + */ +.webform-element--title-inline label { + display: inline; +} + +.webform-element--title-inline label { + padding-right: 0.5em; +} + +.webform-element--title-inline label::after { + content: ':'; +} + +/** + * Readonly inputs. + */ +.webform-readonly { + background-color: #f0f0f0; +} diff --git a/web/modules/contrib/webform/css/webform.help.css b/web/modules/contrib/webform/css/webform.help.css new file mode 100644 index 000000000..5c9e9533e --- /dev/null +++ b/web/modules/contrib/webform/css/webform.help.css @@ -0,0 +1,54 @@ +/** + * @file + * Help styles. + * + * @see /admin/help/webform + * @see /admin/help/webform/introduction + */ + +/** + * Help block. + */ +.block-help { + margin: 1em 0; +} + +/** + * Help play button. + */ +.button.button-webform-play:before { + content: '► '; + font-size: .8em; +} + +/** + * Help accordion. + */ +.webform-help-accordion dt { + font-weight: bold; +} + +/** + * Help video. + */ +.webform-help-video-youtube { + margin: 1.5em 0; + max-width: 1024px; + border: 1px solid #000; +} + +.webform-help-video-youtube--container { + position: relative; + padding-bottom: 56.25%; + padding-top: 30px; + height: 0; + overflow: hidden; +} + +.webform-help-video-youtube iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} diff --git a/web/modules/contrib/webform/css/webform.navigation.css b/web/modules/contrib/webform/css/webform.navigation.css new file mode 100644 index 000000000..3fba8a6b8 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.navigation.css @@ -0,0 +1,51 @@ +/** + * @file + * Navigation styles. + * + * @see /admin/structure/webform/manage/{webform_id}/submission/{webform_submission_id} + */ + +/** + * Navigation. + */ +.webform-submission-navigation { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + margin: 0.5em 0; +} + +/** + * Pager. + */ +.webform-submission-pager { + margin: 0; + overflow: auto; + padding: 0.5em 0; +} + +.webform-submission-pager__item { + display: inline-block; + list-style-type: none; + vertical-align: top; +} + +.webform-submission-pager__item--previous { + text-align: left; /* LTR */ + width: 45%; +} + +[dir="rtl"] .webform-submission-pager__item--previous { + float: right; + text-align: right; +} + +.webform-submission-pager__item--next { + float: right; /* LTR */ + text-align: right; /* LTR */ + width: 45%; +} + +[dir="rtl"] .webform-submission-pager__item--next { + float: left; + text-align: left; +} diff --git a/web/modules/contrib/webform/css/webform.progress.bar.css b/web/modules/contrib/webform/css/webform.progress.bar.css new file mode 100644 index 000000000..524013bbb --- /dev/null +++ b/web/modules/contrib/webform/css/webform.progress.bar.css @@ -0,0 +1,130 @@ +/** + * @file + * Wizard progress bar styles. + * + * @see /webform/example_wizard + * @see /webform/test_form_wizard_advanced + */ + +/** + * Progress bar. + * + * @see https://coderwall.com/p/-7trcg/simple-css-only-wizard-progress-tracker + */ +.webform-progress-bar { + margin: 0; + padding: 0; + overflow: hidden; + font-size: smaller; +} + +li.webform-progress-bar__page { + list-style-type: none; + display: inline-block; + position: relative; + margin: 0; + padding: 0; + line-height: 30px; + height: 30px; + background-color: #f0f0f0; +} + +.webform-progress-bar[data-steps="2"] li { + width: 50%; +} +.webform-progress-bar[data-steps="3"] li { + width: 33%; +} +.webform-progress-bar[data-steps="4"] li { + width: 25%; +} +.webform-progress-bar[data-steps="5"] li { + width: 20%; +} +.webform-progress-bar[data-steps="6"] li { + width: 16.6%; +} +.webform-progress-bar[data-steps="7"] li { + width: 14.28%; +} +.webform-progress-bar[data-steps="8"] li { + width: 12.5%; +} +.webform-progress-bar[data-steps="9"] li { + width: 11.11%; +} +.webform-progress-bar[data-steps="10"] li { + width: 10%; +} + +li.webform-progress-bar__page > b { + display: block; + padding: 0 0 0 30px; + font-weight: normal; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +li.webform-progress-bar__page--done > b { + background-color: #dbdbdb; +} + +li.webform-progress-bar__page--current > b { + font-weight: bold; +} + +li.webform-progress-bar__page > b:after, +li.webform-progress-bar__page > b:before { + content: ""; + display: block; + width: 0; + height: 0; + position: absolute; + top: 0; + left: 0; + border: solid transparent; + border-left-color: #ededed; + border-width: 15px; +} + +li.webform-progress-bar__page > b:after { + top: -5px; + z-index: 1; + border-left-color: white; + border-width: 20px; +} + +li.webform-progress-bar__page > b:before { + z-index: 2; +} + +li.webform-progress-bar__page--done + li > b:before { + border-left-color: #dbdbdb; +} + +li.webform-progress-bar__page:first-child > b:after, +li:first-child > b:before { + display: none; +} + +li.webform-progress-bar__page:first-child span, +li.webform-progress-bar__page:last-child span { + display: block; + height: 0; + width: 0; + position: absolute; + top: 0; + left: 0; + border: solid transparent; + border-left-color: white; + border-width: 15px; +} + +li.webform-progress-bar__page:last-child span { + left: auto; + right: -15px; + border-left-color: transparent; + border-top-color: white; + border-bottom-color: white; +} diff --git a/web/modules/contrib/webform/css/webform.progress.css b/web/modules/contrib/webform/css/webform.progress.css new file mode 100644 index 000000000..66798b641 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.progress.css @@ -0,0 +1,10 @@ +/** + * @file + * Wizard progress (numbering) styles. + * + * @see /webform/test_form_wizard_advanced + */ + +.webform-progress__status { + text-align: center; +} diff --git a/web/modules/contrib/webform/css/webform.theme.bartik.css b/web/modules/contrib/webform/css/webform.theme.bartik.css new file mode 100644 index 000000000..e7efbd482 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.theme.bartik.css @@ -0,0 +1,9 @@ +/** + * @file + * Bartik theme styles. + */ + +/* Add background color to table cells so that very wide off-screen tables look okay. */ +table { + background-color: #fff; +} diff --git a/web/modules/contrib/webform/css/webform.theme.seven.css b/web/modules/contrib/webform/css/webform.theme.seven.css new file mode 100644 index 000000000..1ee49f415 --- /dev/null +++ b/web/modules/contrib/webform/css/webform.theme.seven.css @@ -0,0 +1,54 @@ +/** + * @file + * Seven theme styles. + */ + +/* Align all tables cells top */ +table td { + vertical-align: top; +} + +/* Add background to nested details */ +details details { + background-color: #f8f8f8; +} + +details details details { + background-color: #fff; +} + +/* Add white background to tooltips */ +.ui-tooltip.ui-widget { + background: #fff; +} + +/* Hide tabs and elements from printing. */ +@media print { + .shortcut-action, + .dropbutton-wrapper, + .block-system-breadcrumb-block, + .block-local-tasks-block, + .pager, + .button-action, + .webform-submission-navigation, + .webform-filter-form { + display: none !important; /* Using !important to ensure with these elements are hidden with getting into a specificity war */ + } +} + +/* System tray divider */ +.ui-resizable-w { + background-color: #bfbfba; + border: 1px solid #6b6b6b; + border-width: 1px 2px; +} + +/* System tray title bar */ +.ui-dialog.ui-dialog-offcanvas .ui-dialog-titlebar { + border-radius: 0; +} + +/* System tray actions */ +.ui-dialog.ui-dialog-offcanvas .ui-dialog-content .form-actions { + margin: 1em 0; +} diff --git a/web/modules/contrib/webform/docs/DEVELOPMENT-CHEATSHEET.md b/web/modules/contrib/webform/docs/DEVELOPMENT-CHEATSHEET.md new file mode 100644 index 000000000..1562cfc94 --- /dev/null +++ b/web/modules/contrib/webform/docs/DEVELOPMENT-CHEATSHEET.md @@ -0,0 +1,105 @@ +Development Cheatsheet +---------------------- + +### GitFlow + +```bash +# Create branch +git checkout 8.x-5.x +git checkout -b [issue-number]-[issue-description] +git push -u origin [issue-number]-[issue-description] + +# Create patch +git diff 8.x-5.x > [project_name]-[issue-description]-[issue-number]-00.patch + +# 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 + +# Merge branch with all commits +git checkout 8.x-5.x +git merge [issue-number]-[issue-description] +git push + +# Merge branch as a single new commit +git checkout 8.x-5.x +git merge --squash [issue-number]-[issue-description] +git commit -m 'Issue #[issue-number]: [issue-description]' +git push + +# Delete branch +git branch -D [issue-number]-[issue-description] +git push origin :[issue-number]-[issue-description] +``` + +**Import and Export Configuration** + +```bash +# Generate *.features.yml for the webform.module and sub-modules. +# These files will be ignored. @see .gitignore. +echo 'true' > webform.features.yml +echo 'true' > modules/webform_examples/webform_examples.features.yml +echo 'true' > modules/webform_templates/webform_templates.features.yml +echo 'true' > modules/webform_node/webform_node.features.yml + +# Make sure all modules that are going to be exported are enabled +drush en -y webform\ + webform_demo_application_evaluation\ + webform_examples\ + webform_templates\ + webform_test\ + webform_test_element\ + webform_test_handler\ + webform_test_options\ + webform_test_views\ + webform_test_translation\ + webform_node; + +# 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. +drush features-export -y webform +drush features-export -y webform_demo_application_evaluation +drush features-export -y webform_examples +drush features-export -y webform_templates +drush features-export -y webform_test +drush features-export -y webform_test_element +drush features-export -y webform_test_handler +drush features-export -y webform_test_options +drush features-export -y webform_test_views +drush features-export -y webform_test_translation +drush features-export -y webform_node + +# Revert all feature update to *.info.yml files. +git checkout -- *.info.yml + +# Tidy webform configuration from your site. +drush webform-tidy -y --dependencies webform +drush webform-tidy -y --dependencies webform_demo_application_evaluation +drush webform-tidy -y --dependencies webform_examples +drush webform-tidy -y --dependencies webform_templates +drush webform-tidy -y --dependencies webform_test +drush webform-tidy -y --dependencies webform_test_element +drush webform-tidy -y --dependencies webform_test_handler +drush webform-tidy -y --dependencies webform_test_options +drush webform-tidy -y --dependencies webform_test_views +drush webform-tidy -y --dependencies webform_test_translation +drush webform-tidy -y --dependencies webform_node + +# Re-import all webform configuration into your site. +drush features-import -y webform +drush features-import -y webform_demo_application_evaluation +drush features-import -y webform_examples +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_handler +drush features-import -y webform_test_options +drush features-import -y webform_test_views +drush features-import -y webform_test_translation +drush features-import -y webform_node +``` diff --git a/web/modules/contrib/webform/docs/DEVELOPMENT-NOTES.md b/web/modules/contrib/webform/docs/DEVELOPMENT-NOTES.md new file mode 100644 index 000000000..97df26818 --- /dev/null +++ b/web/modules/contrib/webform/docs/DEVELOPMENT-NOTES.md @@ -0,0 +1,246 @@ +Development Notes +----------------- + +Below are useful commands that make it a little easier for +me to maintain the Webform module. + +### Patching + +**[Create and manage patches](https://www.drupal.org/node/707484)** + +```bash +# Create and checkout issue branch +git checkout -b [issue-number]-[issue-description] + +# Push issue branch to D.O. (optional) +git push -u origin [issue-number]-[issue-description] + +# Create patch by comparing (current) issue branch with 8.x-5.x branch +git diff 8.x-5.x > [project_name]-[issue-description]-[issue-number]-[comment-number]-[drupal-version].patch +``` + +**Ignoring *.patch, *.diff, and .gitignore files** + +```bash +cat >> .gitignore <<'EOF' +.gitignore +*.patch +*.diff +EOF +``` +**[Apply patch](https://www.drupal.org/node/1399218)** + +```bash +curl https://www.drupal.org/files/[patch-name].patch | git apply - +``` + +**[Revert patch](https://www.drupal.org/patch/reverse)** + +```bash +curl https://www.drupal.org/files/[patch-name].patch | git apply -R - +``` + +### Branching + +**Merge branch** + +```bash +# Merge branch with all commits +git checkout 8.x-5.x +git merge [issue-number]-[issue-description] +git push + +# Merge branch as a single new commit +git checkout 8.x-5.x +git merge --squash [issue-number]-[issue-description] +git commit -m 'Issue #[issue-number]: [issue-description]' +git push +``` +**Exporting a branch** + +```bash +# Create a zip archive for a branch +git archive --format zip --output webform-[issue-number]-[issue-description].zip [issue-number]-[issue-description] +``` + +**Delete issue branch** + +```bash +# Delete local issue branch. +git branch -d [issue-number]-[issue-description] + +# Delete remote issue branch. +git push origin :[issue-number]-[issue-description] +``` + +### [Interdiff](https://www.drupal.org/documentation/git/interdiff) + +```bash +interdiff \ + [issue-number]-[old-comment-number].patch \ + [issue-number]-[new-comment-number].patch \ + > interdiff-[issue-number]-[old-comment-number]-[new-comment-number].txt +``` + +### Drush + +**Reinstall Webform module.** + +```bash +drush php-eval 'module_load_include('install', 'webform'); webform_uninstall();'; drush cron; +drush php-eval 'module_load_include('install', 'webform_node'); webform_node_uninstall();'; drush cron; +drush webform-purge --all -y; drush pmu -y webform_test; drush pmu -y webform_devel; drush pmu -y webform_examples; drush pmu -y webform_templates; drush pmu -y webform_ui; drush pmu -y webform_node; drush pmu -y webform; +drush en -y webform webform_ui webform_devel webform_examples webform_templates webform_node; + +# Optional. +drush en -y webform_test; +drush en -y webform_test_third_party_settings; +drush en -y webform_test_translation; +drush pmu -y webform_test_third_party_settings webform_test_translation; +``` + +**Reinstall Webform Test module.** + +```bash +drush webform-purge --all -y; drush pmu -y webform_test; drush en -y webform_test; +``` + +**Install extra modules.** + +```bash +drush en -y webform captcha image_captcha honeypot validators; +``` + +**Create test roles and users.** + +```bash +drush role-create developer +drush role-add-perm developer 'view the administration theme,access toolbar,access administration pages,access content overview,access webform overview,administer webform,edit webform assets,administer blocks,administer nodes' +drush user-create developer --password="developer" +drush user-add-role developer developer + +drush role-create admin +drush role-add-perm admin 'view the administration theme,access toolbar,access administration pages,access content overview,access webform overview,administer webform submission' +drush user-create admin --password="admin" +drush user-add-role admin admin + +drush role-create manager +drush role-add-perm manager 'view the administration theme,access toolbar,access administration pages,access content overview,access webform overview' +drush user-create manager --password="manager" +drush user-add-role manager manager + +drush role-create viewer +drush role-add-perm viewer 'view the administration theme,access toolbar,access administration pages,access content overview,access webform overview,view any webform submission' +drush user-create viewer --password="viewer" +drush user-add-role viewer viewer + +drush role-create user +drush user-create user --password="user" +drush user-add-role user user + +drush role-create any +drush user-create any --password="any" +drush role-add-perm any 'view the administration theme,access administration pages,access toolbar,access webform overview,edit webform assets,create webform,edit any webform,delete any webform,view webform submissions any node,edit webform submissions any node,delete webform submissions any node' +drush user-add-role any any + +drush role-create own +drush user-create own --password="own" +drush role-add-perm own 'view the administration theme,access administration pages,access toolbar,access webform overview,edit webform assets,create webform,edit own webform,delete own webform,view webform submissions own node,edit webform submissions own node,delete webform submissions own node' +drush user-add-role own own +``` + +**Create test submissions for 'Contact' and 'Example: Elements' webform.** + +```bash +drush webform-generate contact +drush webform-generate example_elements +``` + +**Test update hooks** + +```bash +drush php-eval 'module_load_include('install', 'webform'); ($message = webform_update_8001()) ? drupal_set_message($message) : NULL;' +``` + +**Access developer information** + +```bash +drush role-add-perm anonymous 'access devel information' +drush role-add-perm authenticated 'access devel information' +``` + +**Reinstall** + +```bash +drush -y site-install\ + --account-mail="example@example.com"\ + --account-name="webmaster"\ + --account-pass="drupal.admin"\ + --site-mail="example@example.com"\ + --site-name="Drupal 8 (Webform)"; + +# Enable core modules +drush -y pm-enable\ + book\ + simpletest\ + telephone\ + language\ + locale\ + content_translation\ + config_translation; + +# Disable core modules +drush -y pm-uninstall\ + update; + +# Enable contrib modules +drush -y pm-enable\ + devel\ + devel_generate\ + kint\ + webprofiler\ + webform\ + webform_devel\ + webform_examples\ + webform_node\ + webform_templates\ + webform_test\ + webform_test_translation; +``` + +### How to take a screencast + +**Setup** + +- Drupal + - Install Drupal locally. + - Remove all blocks in first sidebar. + http://localhost/d8_dev/admin/structure/block +- Desktop + - Switch to laptop. + - Turn 'Hiding on' in the Dock System Preferences. + - Set screen display to 'Large Text' +- Chrome + - Hide Bookmarks. + - Hide Extra Icons. + - Always Show Toolbar in Full Screen. + - Delete all webform.* keys from local storage. + +**Generate list of screencasts** + +```php +$help = _webform_help(); +print '
          ';
          +foreach ($help as $name => $info) {
          +  print "webform-" . $name . PHP_EOL;
          +  print 'Webform Help: ' . $info['title'] . PHP_EOL;
          +  print PHP_EOL;
          +}
          +print '
          '; exit; +``` + +**Uploading** + +- Title : Webform Help: {title} [v01] +- Tags: Drupal 8,Webform,Form Builder +- Privacy: Unlisted diff --git a/web/modules/contrib/webform/docs/FEATURES.md b/web/modules/contrib/webform/docs/FEATURES.md new file mode 100644 index 000000000..6acd0c627 --- /dev/null +++ b/web/modules/contrib/webform/docs/FEATURES.md @@ -0,0 +1,392 @@ +Features +-------- + +## Form Builder + +
          + +Form Builder + +
          + +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. + +Form builder features include: + +- Drag-n-drop webform element management +- Generation of test submissions +- Duplication of existing webforms, templates, and elements + + +## Form Settings + +
          + +Form Settings + +
          + +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 + + +## Elements + +
          + +Elements + +
          + +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. + +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, Credit card number, Geolocation, + Select/Checkboxes/Radios with other, and more... +- **Markups** Inline dismissable messages, HTML Markup, Details, and Fieldsets. +- **Composite elements:** Name, Address, Contact, and Credit Card + + +## Element Settings + +
          + +Element Settings + +
          + +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 + + +## Viewing Source + +
          + +Viewing Source + +
          + +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. + + +## States/Conditional Logic + +
          + +States/Conditional Logic + +
          + +Drupal's State API can be used by developers to provide conditional logic to +hide and show webform elements. + +Drupal's State API supports: + +- Show/Hide +- Open/Close +- Enable/Disable + + +## Multistep Forms + +
          + +Multistep Forms + +
          + +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: + +- Customizable progress bar +- Customizable previous and next button labels and styles +- Saving drafts between steps + + +## Email & Remote Post Handlers + +
          + +Email/Handlers + +
          + +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 + +
          + +Results Management + +
          + +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 +- 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 + +
          + +Access Controls + +
          + +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. + +Access controls allow users to: + +- Create new forms +- Update forms +- Delete forms +- View submissions +- Update submissions +- Delete submissions +- View selected elements +- Update selected elements + + +## Reusable Templates + +
          + +Reusable Templates + +
          + +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 +- Donation +- Employee Evaluation +- Issue +- 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 + +
          + +Reusable Options + +
          + +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 include: + +- Country codes & names +- Credit card codes +- 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 + + +## Internationalization + +
          + +Internationalization + +
          + +Forms and configuration can be translated into multiple languages using Drupal's +configuration translation system. + + +## Drupal Integration + +
          + +Drupal Integration + +
          + +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 + +
          + +Add-ons & Third Party Settings + +
          + +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 + +
          + +Extendable Plugins + +
          + +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 + +
          + +Help & Video Tutorials + +
          + +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 + +
          + +Drush Integration + +
          + +Drush commands are provided to: + +- Generate multiple webform submissions +- Export webform submissions +- Purge webform submissions +- Download and manage third party libraries +- Tidy YAML configuration files + +-------------------------------------------------------------------------------- + +_This file was generated from [FEATURES.md](http://cgit.drupalcode.org/webform/tree/FEATURES.md?h=8.x-5.x)._ diff --git a/web/modules/contrib/webform/docs/ISSUES.md b/web/modules/contrib/webform/docs/ISSUES.md new file mode 100644 index 000000000..1639066d3 --- /dev/null +++ b/web/modules/contrib/webform/docs/ISSUES.md @@ -0,0 +1,125 @@ +Known Issues +------------ + +Below are known Drupal 8 core issues that are affecting the Webform module. + +### Configuration Management + +**[Issue #2808287: Importing Webform config file via the UI is throwing serialization error](https://www.drupal.org/node/2808287)** + +> Importing configuration files using Drush is working fine. + +**[Issue #1920902: Unable to tidy the bulk export of Webform and WebformOptions config files +because Drupal's YAML utility is not a service.](https://www.drupal.org/node/1920902)** + +> The Webform module provides drush commands to 'tidy' exported YAML and +> configuration files that so they are easier to read and edit. + +### Form Elements + +**[Drupal core webforms system issues](https://www.drupal.org/project/issues/drupal?status=Open&version=8.x&component=forms+system)** + +> Any changes, improvements, and bug fixes for Drupal's Form API may directly +> impact the Webform module. + +- [Issue #1593964: Allow FAPI usage of the datalist element](https://www.drupal.org/node/1593964) + +**[Issue #2502195: Regression: Webform throws LogicException when trying to render a webform with object as an element's default value.](https://www.drupal.org/node/2502195)** + +> Impacts previewing entity autocomplete elements. + +**[Issue #2207383: Create a tooltip component](https://www.drupal.org/node/2207383)** + +> Impacts displaying element description in a tooltip. jQuery UI's tooltip's UX +> is not great. + +**[Issue #2741877: Nested modals don't work: when using CKEditor in a modal, then clicking the image button opens another modal, which closes the original modal](https://www.drupal.org/node/2741877)** + +> Makes it impossible to display the CKEditor in a dialog. +> Workaround: Use custom download of CKEditor which include a CKEditor specific +> link dialog. + +### \#states API (Conditionals) + +#### Button (button & submit) + +**[Issue #1671190 by Lucasljj, idebr, Cameron Tod: Use ') + .attr('title', Drupal.t('Toggle details widget state.')) + .on('click', function (e) { + var open; + if (isFormDetailsOpen($form)) { + $form.find('details').removeAttr('open'); + open = 0; + } + else { + $form.find('details').attr('open', 'open'); + open = 1; + } + setDetailsToggleLabel($form); + + // Set the saved states for all the details elements. + // @see webform.element.details.save.js + if (Drupal.webformDetailsSaveGetName) { + $form.find('details').each(function () { + var name = Drupal.webformDetailsSaveGetName($(this)); + if (name) { + localStorage.setItem(name, open); + } + }); + } + }) + .wrap('
          ') + .parent() + ); + + setDetailsToggleLabel($form); + }); + } + }; + + /** + * Determine if a webform's details are all opened. + * + * @param $form + * A webform. + * + * @returns {boolean} + * TRUE if a webform's details are all opened. + */ + function isFormDetailsOpen($form) { + return ($form.find('details[open]').length == $form.find('details').length) + } + + /** + * Set a webform's details toggle state widget label. + * + * @param $form + * A webform. + */ + function setDetailsToggleLabel($form) { + var label = (isFormDetailsOpen($form)) ? Drupal.t('Collapse all') : Drupal.t('Expand all'); + $form.find('.webform-details-toggle-state').html(label); + } + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.html_editor.js b/web/modules/contrib/webform/js/webform.element.html_editor.js new file mode 100644 index 000000000..c8e5ff201 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.html_editor.js @@ -0,0 +1,52 @@ +/** + * @file + * Javascript behaviors for HTML editor integration. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Initialize HTML Editor. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformHtmlEditor = { + attach: function (context) { + $(context).find('.js-form-type-webform-html-editor textarea').once('webform-html-editor').each(function () { + var allowedContent = drupalSettings['webform']['html_editor']['allowedContent']; + var $textarea = $(this); + CKEDITOR.replace(this.id, { + // Turn off external config and styles. + customConfig: '', + stylesSet: false, + contentsCss: [], + allowedContent: allowedContent, + // Use
          tags instead of

          tags. + enterMode: CKEDITOR.ENTER_BR, + shiftEnterMode: CKEDITOR.ENTER_BR, + // Set height, hide the status bar, and remove plugins. + height: '100px', + resize_enabled: false, + removePlugins: 'elementspath,magicline', + // Toolbar settings. + format_tags: 'p;h2;h3;h4;h5;h6', + toolbar: [ + { name: 'styles', items: ['Format', 'Font', 'FontSize' ] }, + { name: 'basicstyles', items: [ 'Bold', 'Italic', 'Subscript', 'Superscript' ] }, + { name: 'insert', items: [ 'SpecialChar' ] }, + { name: 'colors', items: [ 'TextColor', 'BGColor' ] }, + { name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote'] }, + { name: 'links', items: [ 'Link', 'Unlink'] }, + { name: 'tools', items: [ 'Source', '-', 'Maximize' ] } + ] + }).on('change', function (evt) { + // Save data onchange since AJAX dialogs don't execute webform.onsubmit. + $textarea.val(evt.editor.getData().trim()); + }); + }) + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/webform/js/webform.element.inputmask.js b/web/modules/contrib/webform/js/webform.element.inputmask.js new file mode 100644 index 000000000..9730155ed --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.inputmask.js @@ -0,0 +1,21 @@ +/** + * @file + * Javascript behaviors for jquery.inputmask integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize input masks. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformElementMask = { + attach: function (context) { + $(context).find('input.js-webform-element-mask').once('webform-element-mask').inputmask(); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.location.js b/web/modules/contrib/webform/js/webform.element.location.js new file mode 100644 index 000000000..8c1423fce --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.location.js @@ -0,0 +1,53 @@ +/** + * @file + * Javascript behaviors for Geocomplete location integration. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Initialize location Geocompletion. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformLocation = { + attach: function (context) { + $(context).find('div.js-webform-location').once('webform-location').each(function () { + var $element = $(this); + var $geocomplete = $element.find('.webform-location-geocomplete').geocomplete({ + details: $element, + detailsAttribute: 'data-webform-location-attribute', + types: ['geocode'] + }); + + $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')) { + navigator.geolocation.getCurrentPosition(function (position) { + $geocomplete.geocomplete('find', position.coords.latitude + ', ' + position.coords.longitude); + }); + } + }) + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/webform/js/webform.element.message.js b/web/modules/contrib/webform/js/webform.element.message.js new file mode 100644 index 000000000..3b144e946 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.message.js @@ -0,0 +1,92 @@ +/** + * @file + * Javascript behaviors for message element integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Behavior for handler message close. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformMessageClose = { + attach: function (context) { + $(context).find('.js-webform-message--close').once('webform-message--close').each(function () { + var $element = $(this); + + var id = $element.attr('data-message-id'); + var storage = $element.attr('data-message-storage'); + var effect = $element.attr('data-message-close-effect') || 'hide'; + switch (effect) { + case 'slide': effect = 'slideUp'; break; + + case 'fade': effect = 'fadeOut'; break; + } + + // Check storage status. + if (isClosed($element, storage, id)) { + return; + } + + $element.show().find('.js-webform-message__link').on('click', function (event) { + $element[effect](); + setClosed($element, storage, id); + $element.trigger('close'); + event.preventDefault(); + }); + }) + } + }; + + function isClosed($element, storage, id) { + if (!id || !storage) { + return false; + } + + switch (storage) { + case 'local': + if (window.localStorage) { + return localStorage.getItem('Drupal.webform.message.' + id) || false; + } + return false; + + case 'session': + if (window.sessionStorage) { + return sessionStorage.getItem('Drupal.webform.message.' + id) || false; + } + return false; + + default: + return false; + } + } + + function setClosed($element, storage, id) { + if (!id || !storage) { + return; + } + + switch (storage) { + case 'local': + if (window.localStorage) { + localStorage.setItem('Drupal.webform.message.' + id, true); + } + break; + + case 'session': + if (window.sessionStorage) { + sessionStorage.setItem('Drupal.webform.message.' + id, true); + } + break; + + case 'user': + case 'state': + $.get($element.find('.js-webform-message__link').attr('href')); + return true; + } + } + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.multiple.js b/web/modules/contrib/webform/js/webform.element.multiple.js new file mode 100644 index 000000000..43e794062 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.multiple.js @@ -0,0 +1,26 @@ +/** + * @file + * Javascript behaviors for message element integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Move show weight to after the table. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformMultiple = { + attach: function (context, settings) { + for (var base in settings.tableDrag) { + var $tableDrag = $(context).find('#' + base); + var $toggleWeight = $tableDrag.parent().find('.tabledrag-toggle-weight'); + $toggleWeight.addClass('webform-multiple-tabledrag-toggle-weight'); + $tableDrag.after($toggleWeight); + } + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.other.js b/web/modules/contrib/webform/js/webform.element.other.js new file mode 100644 index 000000000..d866cad71 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.other.js @@ -0,0 +1,157 @@ +/** + * @file + * Javascript behaviors for other elements. + */ + +(function ($, Drupal) { + + "use strict"; + + /** + * Toggle other input (text) field. + * + * @param {boolean} show + * TRUE will display the text field. FALSE with hide and clear the text field. + * @param {object} $element + * The input (text) field to be toggled. + */ + function toggleOther(show, $element) { + var $input = $element.find('input'); + if (show) { + // Limit the other inputs width to the parent's container. + $element.width($element.parent().width()); + // Display the element. + $element.slideDown(); + // Focus and require the input. + $input.focus().prop('required', true); + // Restore the input's value. + var value = $input.data('webform-value'); + if (value !== undefined) { + $input.val(value); + $input.get(0).setSelectionRange(0, 0); + } + // Refresh CodeMirror used as other element. + $element.parent().find('.CodeMirror').each(function (index, $element) { + $element.CodeMirror.refresh(); + }); + } + else { + $element.slideUp(); + // Save the input's value. + $input.data('webform-value', $input.val()); + // Empty and un-required the input. + $input.val('').prop('required', false); + } + } + + /** + * Attach handlers to select other elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformSelectOther = { + attach: function (context) { + $(context).find('.js-webform-select-other').once('webform-select-other').each(function () { + var $element = $(this); + + var $select = $element.find('select'); + var $otherOption = $element.find('option[value="_other_"]'); + var $input = $element.find('.js-webform-select-other-input'); + + if ($otherOption.is(':selected')) { + $input.show().find('input').prop('required', true); + } + + $select.on('change', function () { + toggleOther($otherOption.is(':selected'), $input); + }); + }); + } + }; + + /** + * Attach handlers to checkboxes other elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformCheckboxesOther = { + attach: function (context) { + $(context).find('.js-webform-checkboxes-other').once('webform-checkboxes-other').each(function () { + var $element = $(this); + var $checkbox = $element.find('input[value="_other_"]'); + var $input = $element.find('.js-webform-checkboxes-other-input'); + + if ($checkbox.is(':checked')) { + $input.show().find('input').prop('required', true); + } + + $checkbox.on('change', function () { + toggleOther(this.checked, $input); + }); + }); + } + }; + + /** + * Attach handlers to radios other elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformRadiosOther = { + attach: function (context) { + $(context).find('.js-webform-radios-other').once('webform-radios-other').each(function () { + var $element = $(this); + + var $radios = $element.find('input[type="radio"]'); + var $input = $element.find('.js-webform-radios-other-input'); + + if ($radios.filter(':checked').val() === '_other_') { + $input.show().find('input').prop('required', true); + } + + $radios.on('change', function () { + toggleOther(($radios.filter(':checked').val() === '_other_'), $input); + }); + }); + } + }; + + /** + * Attach handlers to buttons other elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformButtonsOther = { + attach: function (context) { + $(context).find('.js-webform-buttons-other').once('webform-buttons-other').each(function () { + var $element = $(this); + + var $buttons = $element.find('input[type="radio"]'); + var $input = $element.find('.js-webform-buttons-other-input'); + + if ($buttons.filter(':checked').val() === '_other_') { + $input.show().find('input').prop('required', true); + } + + // Note: Initializing buttonset here so that we can set the onchange + // event handler. + // @see Drupal.behaviors.webformButtons + var $container = $(this).find('.form-radios'); + // Remove all div and classes around radios and labels. + $container.html($container.find('input[type="radio"], label').removeClass()); + // Create buttonset and set onchange handler. + $container.buttonset().change(function () { + toggleOther(($(this).find(':radio:checked').val() === '_other_'), $input); + }); + // Disable buttonset. + $container.buttonset('option', 'disabled', $container.find('input[type="radio"]:disabled').length); + // Turn buttonset off/on when the input is disabled/enabled. + // @see webform.states.js + $container.on('webform:disabled', function () { + $container.buttonset('option', 'disabled', $container.find('input[type="radio"]:disabled').length); + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.radios.js b/web/modules/contrib/webform/js/webform.element.radios.js new file mode 100644 index 000000000..0c6967546 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.radios.js @@ -0,0 +1,58 @@ +/** + * @file + * Javascript behaviors for radio buttons. + * + * Fix #states and #required for radios buttons. + * + * @see Issue #2856795: If radio buttons are required but not filled form is nevertheless submitted. + * @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. + * @see Issue #2731991: Setting required on radios marks all options required. + * @see css/webform.form.css + * @see /core/misc/states.js + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Attach handler to add .js-webform-radios-fieldset to radios wrapper fieldset. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformRadios = { + attach: function (context) { + $('.js-webform-radios', context).closest('fieldset.form-composite').addClass('js-webform-radios-fieldset'); + } + }; + + // Make absolutely sure the below event handlers are triggered after + // the /core/misc/states.js event handlers by attaching them after DOM load. + $(function () { + Drupal.behaviors.webformRadios.attach($(document)); + + function setRequired($target, required) { + if (!$target.hasClass('js-webform-radios-fieldset')) { + return; + } + + if (required) { + $target.find('input[type="radio"]').attr({'required': 'required', 'aria-required': 'aria-required'}) + $target.find('legend span').addClass('js-form-required form-required'); + } + else { + $target.find('input[type="radio"]').removeAttr('required aria-required'); + $target.find('legend span').removeClass('js-form-required form-required'); + } + } + + setRequired($('.form-composite[required="required"]'), true); + + $(document).on('state:required', function (e) { + if (e.trigger) { + setRequired($(e.target), e.value); + } + }); + }); + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.range.js b/web/modules/contrib/webform/js/webform.element.range.js new file mode 100644 index 000000000..1bd4bb4f1 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.range.js @@ -0,0 +1,62 @@ +/** + * @file + * Javascript behaviors for range element integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Enhance HTML5 range element. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformRange = { + attach: function (context) { + $(context).find('.form-range[data-range-output]').once('webform-range').each(function () { + var $element = $(this); + // Handle browser that don't support the HTML5 range input. + if (Modernizr.inputtypes.range === false) { + return; + } + + var prefix = $element.attr('data-range-output-prefix'); + var suffix = $element.attr('data-range-output-suffix'); + + // Display range input's output to the end user. + var html = ''; + html += '

          '; + html += (prefix ? '' + prefix + '' : ''); + html += ''; + html += (suffix ? '' + suffix + '' : ''); + html += '
          '; + + var height = parseInt($element.outerHeight()) || 24; + var $outputContainer = $(html); + + // Set the container element's line height which will vertically + // align the range widget and the output. + $outputContainer.find('input, span').css({ + height: height + 'px', + lineHeight: height + 'px' + }); + + var $output = $outputContainer.find('input'); + $output[0].value = $element[0].value; + $element + .after($outputContainer) + .css({float: 'left'}); + + // Sync $element and $output. + $element.on('input', function () { + $output[0].value = $element[0].value; + }); + $output.on('input', function () { + $element[0].value = $output[0].value; + }); + }) + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.rating.js b/web/modules/contrib/webform/js/webform.element.rating.js new file mode 100644 index 000000000..a70fce41e --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.rating.js @@ -0,0 +1,39 @@ +/** + * @file + * Javascript behaviors for RateIt integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize rating element using RateIt. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformRating = { + attach: function (context) { + $(context) + .find('[data-rateit-backingfld]') + .once('webform-rating') + .each(function () { + var $rateit = $(this); + var $input = $($rateit.attr('data-rateit-backingfld')); + + // Update the RateIt widget when the input's value has changed. + // @see webform.states.js + $input.on('change', function () { + $rateit.rateit('value', $input.val()); + }); + + // Set RateIt widget to be readonly when the input is disabled. + // @see webform.states.js + $input.on('webform:disabled', function () { + $rateit.rateit('readonly', $input.is(':disabled')); + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.roles.js b/web/modules/contrib/webform/js/webform.element.roles.js new file mode 100644 index 000000000..1946b6a22 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.roles.js @@ -0,0 +1,39 @@ +/** + * @file + * Javascript behaviors for roles element integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Enhance roles element. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformRoles = { + attach: function (context) { + $(context).find('.js-webform-roles-role[value="authenticated"]').once('webform-roles').each(function () { + var $authenticated = $(this); + var $checkboxes = $authenticated.parents('.form-checkboxes').find('.js-webform-roles-role').filter(function () { + return ($(this).val() != 'anonymous' && $(this).val() != 'authenticated'); + }); + + $authenticated.on('click', function () { + if ($authenticated.is(':checked')) { + $checkboxes.prop('checked', true).attr('disabled', true); + } + else { + $checkboxes.prop('checked', false).removeAttr('disabled'); + } + }); + + if ($authenticated.is(':checked')) { + $checkboxes.prop('checked', true).attr('disabled', true); + } + }) + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.select2.js b/web/modules/contrib/webform/js/webform.element.select2.js new file mode 100644 index 000000000..a4479c511 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.select2.js @@ -0,0 +1,26 @@ +/** + * @file + * Javascript behaviors for Select2 integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize Select2 support. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformSelect2 = { + attach: function (context) { + $(context) + .find('select.js-webform-select2, .js-webform-select2 select') + .once('webform-select2') + // http://stackoverflow.com/questions/14313001/select2-not-calculating-resolved-width-correctly-if-select-is-hidden + .css('width', '100%') + .select2(); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.signature.js b/web/modules/contrib/webform/js/webform.element.signature.js new file mode 100644 index 000000000..0be2e5f56 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.signature.js @@ -0,0 +1,83 @@ +/** + * @file + * Javascript behaviors for signature pad integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize signature element. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformSignature = { + attach: function (context) { + $(context).find('input.js-webform-signature').once('webform-signature').each(function () { + var $input = $(this); + var value = $input.val(); + var $wrapper = $input.parent(); + var $canvas = $wrapper.find('canvas'); + var $button = $wrapper.find('input[type="submit"]'); + var canvas = $canvas[0]; + + // 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); + + // Resizing clears the canvas so we need to reset the signature pad. + signaturePad.clear(); + var value = $input.val(); + if (value) { + signaturePad.fromDataURL(value); + } + }); + + // Initialize signature canvas. + var signaturePad = new SignaturePad(canvas, { + 'onEnd': function () { + $input.val(signaturePad.toDataURL()); + } + }); + + // Set value. + if (value) { + signaturePad.fromDataURL(value); + } + + // Set reset handler. + $button.on('click', function () { + signaturePad.clear(); + $input.val(); + this.blur(); + return false; + }); + + // Input onchange clears signature pad if value is empty. + // @see webform.states.js + $input.on('change', function () { + if (!$input.val()) { + signaturePad.clear(); + } + }); + + // Turn signature pad off/on when the input is disabled/enabled. + // @see webform.states.js + $input.on('webform:disabled', function () { + if ($input.is(':disabled')) { + signaturePad.off(); + } + else { + signaturePad.on(); + } + }); + + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.telephone.js b/web/modules/contrib/webform/js/webform.element.telephone.js new file mode 100644 index 000000000..c26804a01 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.telephone.js @@ -0,0 +1,50 @@ +/** + * @file + * Javascript behaviors for Telephone element. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Initialize Telephone international element. + * @see http://intl-tel-input.com/node_modules/intl-tel-input/examples/gen/is-valid-number.html + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformTelephoneInternational = { + attach: function (context) { + $(context).find('input.js-webform-telephone-international').once('webform-telephone-international').each(function () { + var $telephone = $(this); + + // Add error message container. + var $error = $('
          ' + Drupal.t('Invalid phone number') + '
          ').hide(); + $telephone.closest('.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 + $telephone.intlTelInput({ + 'nationalMode': false + }); + + var reset = function() { + $telephone.removeClass('error'); + $error.hide(); + }; + + $telephone.blur(function() { + reset(); + if ($.trim($telephone.val())) { + if (!$telephone.intlTelInput('isValidNumber')) { + $telephone.addClass('error'); + $error.show(); + } + } + }); + + $telephone.on('keyup change', reset); + }) + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/webform/js/webform.element.text_format.js b/web/modules/contrib/webform/js/webform.element.text_format.js new file mode 100644 index 000000000..4c85a81ab --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.text_format.js @@ -0,0 +1,45 @@ +/** + * @file + * Javascript behaviors for Text format integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Enhance text format element. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformTextFormat = { + attach: function (context) { + $(context).find('.js-text-format-wrapper textarea').once('webform-text-format').each(function () { + var $textarea = $(this); + if (!window.CKEDITOR) { + return; + } + + // Update the CKEDITOR when the textarea's value has changed. + // @see webform.states.js + $textarea.on('change', function () { + if (CKEDITOR.instances[$textarea.attr('id')]) { + var editor = CKEDITOR.instances[$textarea.attr('id')]; + editor.setData($textarea.val()); + } + }); + + // Set CKEDITOR to be readonly when the textarea is disabled. + // @see webform.states.js + $textarea.on('webform:disabled', function () { + if (CKEDITOR.instances[$textarea.attr('id')]) { + var editor = CKEDITOR.instances[$textarea.attr('id')]; + editor.setReadOnly($textarea.is(':disabled')); + } + }); + + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.time.js b/web/modules/contrib/webform/js/webform.element.time.js new file mode 100644 index 000000000..db8793444 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.time.js @@ -0,0 +1,57 @@ +/** + * @file + * Javascript behaviors for time integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Attach timepicker fallback on time elements. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior to time elements. + */ + Drupal.behaviors.webformTime = { + attach: function (context, settings) { + var $context = $(context); + // Skip if time inputs are supported by the browser. + if (Modernizr.inputtypes.time === true) { + return; + } + $context.find('input[type="time"]').once('timePicker').each(function () { + var $input = $(this); + + var options = {}; + if ($input.data('webformTimeFormat')) { + options.timeFormat = $input.data('webformTimeFormat'); + } + if ($input.attr('min')) { + options.minTime = $input.attr('min'); + } + if ($input.attr('max')) { + options.maxTime = $input.attr('max'); + } + + // HTML5 time element steps is in seconds but for the timepicker + // fallback it needs to be in minutes. + // Note: The 'datetime' element uses the #date_increment which defaults + // to 1 (second). + // @see \Drupal\Core\Datetime\Element\Datetime::processDatetime + // Only use step if it is greater than 60 seconds. + if ($input.attr('step') && ($input.attr('step') > 60)) { + options.step = Math.round($input.attr('step') / 60); + } + else { + options.step = 1; + } + + $input.timepicker(options); + }); + } + } + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.element.toggle.js b/web/modules/contrib/webform/js/webform.element.toggle.js new file mode 100644 index 000000000..0f746e082 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.element.toggle.js @@ -0,0 +1,56 @@ +/** + * @file + * Javascript behaviors for toggle integration. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize toggle element using Toggles. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformToggle = { + attach: function (context) { + $(context).find('.js-webform-toggle').once('webform-toggle').each(function () { + var $toggle = $(this); + var $wrapper = $toggle.parent(); + var $checkbox = $wrapper.find('input[type="checkbox"]'); + var $label = $wrapper.find('label'); + + $toggle.toggles({ + checkbox: $checkbox, + on: $checkbox.is(':checked'), + clicker: $label, + text: { + on: $toggle.attr('data-toggle-text-on') || '', + off: $toggle.attr('data-toggle-text-off') || '' + } + }); + + // If checkbox is disabled then add the .disabled class to the toggle. + if ($checkbox.attr('disabled') || $checkbox.attr('readonly')) { + $toggle.addClass('disabled'); + } + + // Add .clearfix to the wrapper. + $wrapper.addClass('clearfix') + + }); + } + }; + + // Track the disabling of a toggle's checkbox using states. + $(document).on('state:disabled', function (event) { + $('.js-webform-toggle').each(function () { + var $toggle = $(this); + 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'); + }); + }); + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.form.disable_back.js b/web/modules/contrib/webform/js/webform.form.disable_back.js new file mode 100644 index 000000000..952fecbc5 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.form.disable_back.js @@ -0,0 +1,10 @@ +/** + * @file + * Javascript to disable back button. + */ + +// From: http://stackoverflow.com/questions/17962130/restrict-user-to-refresh-and-back-forward-in-any-browser +history.pushState({ page: 1 }, "Title 1", "#no-back"); +window.onhashchange = function (event) { + window.location.hash = "no-back"; +}; diff --git a/web/modules/contrib/webform/js/webform.form.js b/web/modules/contrib/webform/js/webform.form.js new file mode 100644 index 000000000..514252713 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.form.js @@ -0,0 +1,141 @@ +/** + * @file + * Javascript behaviors for webforms. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Autofocus first input. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the webform autofocusing. + */ + Drupal.behaviors.webformAutofocus = { + attach: function (context) { + $(context).find('.webform-submission-form.js-webform-autofocus :input:visible:enabled:first').focus(); + } + }; + + /** + * Prevent webform autosubmit on wizard pages. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for disabling webform autosubmit. + */ + Drupal.behaviors.webformDisableAutoSubmit = { + attach: function (context) { + // @see http://stackoverflow.com/questions/11235622/jquery-disable-form-submit-on-enter + $(context).find('.webform-submission-form.js-webform-disable-autosubmit input').once('webform-disable-autosubmit').on('keyup keypress', function (e) { + var keyCode = e.keyCode || e.which; + if (keyCode === 13) { + e.preventDefault(); + return false; + } + }); + } + }; + + /** + * Skip client-side validation when submit button is pressed. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the skipping client-side validation. + */ + Drupal.behaviors.webformSubmitNoValidate = { + attach: function (context) { + $(context).find('input:submit.js-webform-novalidate').once('webform-novalidate').on('click', function () { + $(this.form).attr('novalidate', 'novalidate'); + }); + } + }; + + /** + * Disable validate when save draft submit button is clicked. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the webform draft submit button. + */ + Drupal.behaviors.webformDraft = { + attach: function (context) { + $(context).find('#edit-draft').once('webform-draft').on('click', function () { + $(this.form).attr('novalidate', 'novalidate'); + }); + } + }; + + /** + * 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 $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.parent().parent(); + var textMatch = $label.text().toLowerCase().indexOf(query) !== -1; + $row.toggle(textMatch); + } + + // Filter if the length of the query is at least 2 characters. + if (query.length >= 2) { + $filter_rows.each(toggleEntry); + } + else { + $filter_rows.each(function (index) { + $(this).parent().parent().show(); + }); + } + } + + if ($table.length) { + $filter_rows = $table.find('div.webform-form-filter-text-source'); + $input.on('keyup', filterElementList); + } + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.form.submit_once.js b/web/modules/contrib/webform/js/webform.form.submit_once.js new file mode 100644 index 000000000..10a969f88 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.form.submit_once.js @@ -0,0 +1,53 @@ +/** + * @file + * Javascript behaviors for preventing duplicate webform submissions. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Submit once. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for preventing duplicate webform submissions. + */ + Drupal.behaviors.webformSubmitOnce = { + attach: function (context) { + $('.js-webform-submit-once', context).each(function () { + var $form = $(this); + $form.removeAttr('webform-submitted'); + $form.find('#edit-actions input[type="submit"]').removeAttr('webform-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('#edit-actions input[type="submit"]').click(function () { + $form.find('#edit-actions input[type="submit"]').removeAttr('webform-clicked'); + $(this).attr('webform-clicked', 'true'); + }); + + $(this).submit(function () { + // Track webform submitted. + if ($form.attr('webform-submitted')) { + return false; + } + $form.attr('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. + $form.find('#edit-actions input[type="submit"]').addClass('is-disabled'); + + // Set the throbber progress indicator. + // @see Drupal.Ajax.prototype.setProgressIndicatorThrobber + var $clickedButton = $form.find('#edit-actions input[type=submit][webform-clicked=true]'); + var $progress = $('
           
          '); + $clickedButton.after($progress); + }); + }) + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.form.unsaved.js b/web/modules/contrib/webform/js/webform.form.unsaved.js new file mode 100644 index 000000000..06572a08c --- /dev/null +++ b/web/modules/contrib/webform/js/webform.form.unsaved.js @@ -0,0 +1,75 @@ +/** + * @file + * Javascript behaviors for unsaved webforms. + */ + +(function ($, Drupal) { + + 'use strict'; + + var unsaved = false; + + /** + * Unsaved changes. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for unsaved changes. + */ + Drupal.behaviors.webformUnsaved = { + attach: function (context) { + // Look for the 'data-webform-unsaved' attribute which indicates that the + // multi-step webform has unsaved data. + // @see \Drupal\webform\WebformSubmissionForm::buildForm + if ($('.js-webform-unsaved[data-webform-unsaved]').length) { + unsaved = true; + } + else { + $('.js-webform-unsaved :input:not(input[type=\'submit\'])', context).once('webform-unsaved').on('change keypress', function () { + unsaved = true; + }); + } + + $('.js-webform-unsaved button, .js-webform-unsaved input[type=\'submit\']', context).once('webform-unsaved').on('click', function () { + unsaved = false; + }); + } + }; + + $(window).on('beforeunload', function () { + if (unsaved) { + return true; + } + }); + + /*! + * An experimental shim to partially emulate onBeforeUnload on iOS. + * Part of https://github.com/codedance/jquery.AreYouSure/ + * + * Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/ + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Author: chris.dance@papercut.com + * Date: 19th May 2014 + */ + $(function () { + if (!navigator.userAgent.toLowerCase().match(/iphone|ipad|ipod|opera/)) { + return; + } + $('a').bind('click', function (evt) { + var href = $(evt.target).closest('a').attr('href'); + if (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.'))) { + return false; + } + } + window.location.href = href; + return false; + } + }); + }); + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.help.js b/web/modules/contrib/webform/js/webform.help.js new file mode 100644 index 000000000..d833d4d4a --- /dev/null +++ b/web/modules/contrib/webform/js/webform.help.js @@ -0,0 +1,60 @@ +/** + * @file + * Javascript behaviors for help. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Handles help accordion. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for help accordion. + */ + Drupal.behaviors.webformHelpAccordion = { + attach: function (context) { + var $widget = $(context).find('.webform-help-accordion'); + $widget.once('webform-help-accordion').accordion({ + collapsible: true, + heightStyle: "content" + }); + + var $container = $('h3' + location.hash, $widget); + if ($container.length) { + var active = $widget.find($widget.accordion('option', 'header')).index($container); + $widget.accordion('option', 'active', active); + } + } + }; + + /** + * Handles disabling help dialog for mobile devices. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for disabling help dialog for mobile devices. + */ + Drupal.behaviors.webformHelpDialog = { + attach: function (context) { + $(context).find('.button-webform-play').once('webform-help-dialog').on('click', function (event) { + if ($(window).width() < 768) { + event.stopImmediatePropagation(); + } + }).each(function () { + // Must make sure that this click event handler is execute first and + // before the AJAX dialog handler. + // @see http://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t + var handlers = $._data(this, 'events')['click']; + var handler = handlers.pop(); + // Move it at the beginning. + handlers.splice(0, 0, handler); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.states.js b/web/modules/contrib/webform/js/webform.states.js new file mode 100644 index 000000000..3fd4c12d4 --- /dev/null +++ b/web/modules/contrib/webform/js/webform.states.js @@ -0,0 +1,172 @@ +/** + * @file + * Javascript behaviors for custom webform #states. + */ + +(function ($, Drupal) { + + 'use strict'; + + // Make absolutely sure the below event handlers are triggered after + // the /core/misc/states.js event handlers by attaching them after DOM load. + $(function () { + var $document = $(document); + $document.on('state:visible', function (e) { + if (!e.trigger) { + return TRUE; + } + + if (!e.value) { + // @see https://www.sitepoint.com/jquery-function-clear-form-data/ + $(':input', e.target).andSelf().each(function () { + var $input = $(this); + backupValueAndRequired(this); + clearValueAndRequired(this); + triggerEventHandlers(this); + }); + } + else { + $(':input', e.target).andSelf().each(function () { + restoreValueAndRequired(this); + triggerEventHandlers(this); + }); + } + }); + + $document.on('state:disabled', function (e) { + if (e.trigger) { + $(e.target).trigger('webform:disabled') + .find('select, input, textarea').trigger('webform:disabled'); + } + }); + }); + + /** + * Trigger an input's event handlers. + * + * @param input + * An input. + */ + function triggerEventHandlers(input) { + var $input = $(input); + var type = input.type; + var tag = input.tagName.toLowerCase(); // Normalize case. + if (type == 'checkbox' || type == 'radio') { + $input + .trigger('change') + .trigger('blur'); + } + else if (tag == 'select') { + $input + .trigger('change') + .trigger('blur'); + } + else if (type != 'submit' && type != 'button') { + $input + .trigger('input') + .trigger('change') + .trigger('keydown') + .trigger('keyup') + .trigger('blur'); + } + } + + /** + * Backup an input's current value and required attribute + * + * @param input + * An input. + */ + function backupValueAndRequired(input) { + var $input = $(input); + var type = input.type; + var tag = input.tagName.toLowerCase(); // Normalize case. + + // Backup required. + if ($input.prop('required')) { + $input.data('webform-require', true); + } + + // Backup value. + if (type == 'checkbox' || type == 'radio') { + $input.data('webform-value', $input.prop('checked')); + } + else if (tag == 'select') { + var values = []; + $input.find('option:selected').each(function (i, option) { + values[i] = option.value; + }); + $input.data('webform-value', values); + } + else if (type != 'submit' && type != 'button') { + $input.data('webform-value', input.value); + } + + } + + /** + * Restore an input's value and required attribute. + * + * @param input + * An input. + */ + function restoreValueAndRequired(input) { + var $input = $(input); + + // Restore value. + var value = $input.data('webform-value'); + if (typeof value !== 'undefined') { + var type = input.type; + var tag = input.tagName.toLowerCase(); // Normalize case. + + if (type == 'checkbox' || type == 'radio') { + $input.prop('checked', value) + } + else if (tag == 'select') { + $.each(value, function (i, option_value) { + $input.find("option[value='" + option_value + "']").prop("selected", true); + }); + } + else if (type != 'submit' && type != 'button') { + input.value = value; + } + } + + // Restore required. + if ($input.data('webform-required')) { + $input.prop('required', TRUE); + } + } + + /** + * Clear an input's value and required attributes. + * + * @param input + * An input. + */ + function clearValueAndRequired(input) { + var $input = $(input); + + // Clear value. + var type = input.type; + var tag = input.tagName.toLowerCase(); // Normalize case. + if (type == 'checkbox' || type == 'radio') { + $input.prop('checked', false); + } + else if (tag == 'select') { + if ($input.find('option[value=""]').length) { + $input.val(''); + } + else { + input.selectedIndex = -1; + } + } + else if (type != 'submit' && type != 'button') { + input.value = (type == 'color') ? '#000000' : ''; + } + + // Clear required. + $input.prop('required', false); + } + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/js/webform.tooltip.js b/web/modules/contrib/webform/js/webform.tooltip.js new file mode 100644 index 000000000..608a95c7e --- /dev/null +++ b/web/modules/contrib/webform/js/webform.tooltip.js @@ -0,0 +1,46 @@ +/** + * @file + * Javascript behaviors for jQuery UI tooltip integration. + * + * Please Note: + * jQuery UI's tooltip implementation is not very responsive or adaptive. + * + * @see https://www.drupal.org/node/2207383 + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize jQuery UI tooltip element support. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformTooltipElement = { + attach: function (context) { + $(context).find('.js-webform-tooltip-element').once('webform-tooltip-element').each(function () { + var $element = $(this); + var $description = $element.children('.description.visually-hidden'); + $element.tooltip({ + items: ':input', + content: $description.html() + }); + }); + } + }; + + /** + * Initialize jQuery UI tooltip link support. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformTooltipLink = { + attach: function (context) { + $(context).find('a.js-webform-tooltip-link').once('webform-tooltip-link').each(function () { + $(this).tooltip(); + }); + } + }; + +})(jQuery, Drupal); diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml new file mode 100644 index 000000000..ef1e8ed9d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application.yml @@ -0,0 +1,119 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_demo_application_evaluation +open: null +close: null +uid: null +template: false +id: demo_application +title: 'Demo: Application' +description: 'A demonstration of a very basic application form.' +elements: | + contact: + '#type': webform_contact + '#title': Contact + '#name__required': true + '#company__required': true + '#email__required': true + '#phone__required': true + '#address__required': true + '#address_2__required': true + '#city__required': true + '#state_province__required': true + '#postal_code__required': true + '#country__required': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: + - authenticated + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml new file mode 100644 index 000000000..2fe16cf7d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/install/webform.webform.demo_application_evaluation.yml @@ -0,0 +1,117 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_demo_application_evaluation +open: null +close: null +uid: null +template: 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.' +elements: | + application_evaluation: + '#type': fieldset + '#title': 'Application evaluation' + '#attributes': + style: 'background-color: #f5f5f2' + rating: + '#type': webform_rating + '#title': Rating + '#required': true + comments: + '#type': textarea + '#title': Comments +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: true + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: 1 + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml new file mode 100644 index 000000000..6b90d85aa --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.bartik_webform_demo_application_evaluation.yml @@ -0,0 +1,31 @@ +langcode: en +status: true +dependencies: + module: + - webform + theme: + - bartik + enforced: + module: + - webform_demo_application_evaluation +id: bartik_webform_demo_application_evaluation +theme: bartik +region: content +weight: 5 +provider: null +plugin: webform_block +settings: + id: webform_block + label: 'Demo: Application Evaluation' + provider: webform + label_display: '0' + webform_id: demo_application_evaluation + default_data: '' +visibility: + webform: + id: webform + webforms: + demo_application: demo_application + negate: false + context_mapping: + webform_submission: '@webform.webform_submission_route_context:webform_submission' diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml new file mode 100644 index 000000000..241f53b66 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/config/optional/block.block.seven_webform_demo_application_evaluation.yml @@ -0,0 +1,31 @@ +langcode: en +status: true +dependencies: + module: + - webform + theme: + - seven + enforced: + module: + - webform_demo_application_evaluation +id: seven_webform_demo_application_evaluation +theme: seven +region: content +weight: 5 +provider: null +plugin: webform_block +settings: + id: webform_block + label: 'Demo: Application Evaluation' + provider: webform + label_display: '0' + webform_id: demo_application_evaluation + default_data: '' +visibility: + webform: + id: webform + webforms: + demo_application: demo_application + negate: false + context_mapping: + webform_submission: '@webform.webform_submission_route_context:webform_submission' diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.features.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.features.yml new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.features.yml @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml new file mode 100644 index 000000000..6199a3487 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.info.yml @@ -0,0 +1,14 @@ +name: 'Webform Demo: Application/Evaluation System' +type: module +description: 'Demonstrate how to use the Webform module to build an application/evaluation system.' +package: 'Webform Demo' +# core: 8.x +dependencies: + - 'drupal:block' + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.install b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.install new file mode 100644 index 000000000..2bcc34299 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_demo/webform_demo_application_evaluation/webform_demo_application_evaluation.install @@ -0,0 +1,75 @@ +moduleExists('webform_node')) { + // Create a webfrom node. + $webform_node = Node::create([ + 'type' => 'webform', + 'title' => 'Demo: Application', + 'status' => 1, + ]); + $webform_node->webform->target_id = 'demo_application'; + $webform_node->webform->status = 1; + $webform_node->save(); + + // Generate some initial webform node applications. + if (\Drupal::moduleHandler()->moduleExists('devel_generate')) { + /** @var \Drupal\devel_generate\DevelGeneratePluginManager $devel_generate_manager */ + $devel_generate_manager = \Drupal::service('plugin.manager.develgenerate'); + + /** @var \Drupal\webform\Plugin\DevelGenerate\WebformSubmissionDevelGenerate $webform_submission_devel_generate */ + $webform_submission_devel_generate = $devel_generate_manager->createInstance('webform_submission', []); + $webform_submission_devel_generate->generateElements([ + 'webform_ids' => [ + 'demo_application' => 'demo_application' + ], + 'entity-type' => 'node', + 'entity-id' => $webform_node->id(), + 'num' => 50, + ]); + } + } + + // Add submissions to demo_application webform. + if (\Drupal::moduleHandler()->moduleExists('devel_generate')) { + /** @var \Drupal\devel_generate\DevelGeneratePluginManager $devel_generate_manager */ + $devel_generate_manager = \Drupal::service('plugin.manager.develgenerate'); + + /** @var \Drupal\webform\Plugin\DevelGenerate\WebformSubmissionDevelGenerate $webform_submission_devel_generate */ + $webform_submission_devel_generate = $devel_generate_manager->createInstance('webform_submission', []); + $webform_submission_devel_generate->generateElements([ + 'webform_ids' => [ + 'demo_application' => 'demo_application' + ], + 'num' => 50, + ]); + } +} + +/** + * Implements hook_install(). + */ +function webform_demo_application_evaluation_uninstall() { + // Delete all webform:demo_application nodes. + $entity_ids = \Drupal::entityQuery('node') + ->condition('webform.target_id', 'demo_application') + ->execute(); + if ($entity_ids) { + /** @var $nodes \Drupal\node\Entity\Node[] */ + $nodes = Node::loadMultiple($entity_ids); + foreach ($nodes as $node) { + $node->delete(); + } + } +} diff --git a/web/modules/contrib/webform/modules/webform_devel/src/Form/WebformEntityExportForm.php b/web/modules/contrib/webform/modules/webform_devel/src/Form/WebformEntityExportForm.php new file mode 100644 index 000000000..5a5e9fe31 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/src/Form/WebformEntityExportForm.php @@ -0,0 +1,82 @@ + 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t("Here is your webform's configuration:"), + '#description' => $this->t('Filename: %file', ['%file' => $this->getConfigName() . '.yml']), + '#default_value' => $this->getYaml(), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function actionsElement(array $form, FormStateInterface $form_state) { + $element['download'] = [ + '#type' => 'submit', + '#value' => $this->t('Download'), + '#button_type' => 'primary', + ]; + return $element; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $content = $this->getYaml(); + $filename = $this->getConfigName() . '.yml'; + $headers = [ + 'Content-Type' => 'text/yaml', + 'Content-Disposition' => sprintf('attachment; filename="%s"', $filename), + ]; + $response = new Response($content, 200, $headers); + $form_state->setResponse($response); + } + + /** + * Get the webform's raw data. + * + * @return string + * The webform's raw data. + */ + protected function getYaml() { + $config_name = $this->getConfigName(); + $data = $this->config($config_name)->getRawData(); + $yaml = Yaml::encode($data); + return WebformYaml::tidy($yaml); + } + + /** + * Get the webform's config file name (without *.yml). + * + * @return string + * The webform's config file name (without *.yml). + */ + protected function getConfigName() { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $this->entity; + $definition = $this->entityTypeManager->getDefinition('webform'); + return $definition->getConfigPrefix() . '.' . $webform->getConfigTarget(); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_devel/webform_devel.drush.inc b/web/modules/contrib/webform/modules/webform_devel/webform_devel.drush.inc new file mode 100644 index 000000000..a4fdcbe5c --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/webform_devel.drush.inc @@ -0,0 +1,60 @@ + 'Resets Webform user data and saved state for messages', + 'core' => ['8+'], + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, + 'examples' => [ + 'webform-repair' => 'Resets Webform user data and saved state for messages', + ], + 'aliases' => ['webform-reset'], + ]; + + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function webform_devel_drush_help($section) { + switch ($section) { + case 'drush:webform-reset': + return dt('Resets Webform user data and saved state for messages'); + + case 'meta:webform:title': + return dt('Webform development commands'); + + case 'meta:webform:summary': + return dt('Developer specific commands for the Webform module.'); + } +} + +/******************************************************************************/ +// Repair. +/******************************************************************************/ + +/** + * Implements drush_hook_COMMAND(). + */ +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('Resetting message closed via State API...'); + \Drupal::state()->delete('webform.element.message'); + + drush_print('Resetting message closed via User Data...'); + \Drupal::service('user.data')->delete('webform', NULL, 'webform.element.message'); +} diff --git a/web/modules/contrib/webform/modules/webform_devel/webform_devel.info.yml b/web/modules/contrib/webform/modules/webform_devel/webform_devel.info.yml new file mode 100644 index 000000000..d7c8319cf --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/webform_devel.info.yml @@ -0,0 +1,14 @@ +name: 'Webform Devel' +type: module +description: 'Provides development tools for the Webform module.' +package: Webform +# core: 8.x +dependencies: + - 'drupal:devel' + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_devel/webform_devel.links.task.yml b/web/modules/contrib/webform/modules/webform_devel/webform_devel.links.task.yml new file mode 100644 index 000000000..0bc70deb5 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/webform_devel.links.task.yml @@ -0,0 +1,5 @@ +entity.webform.export_form: + title: 'Export' + route_name: entity.webform.export_form + parent_id: devel.entities:webform.devel_tab + weight: 100 diff --git a/web/modules/contrib/webform/modules/webform_devel/webform_devel.module b/web/modules/contrib/webform/modules/webform_devel/webform_devel.module new file mode 100644 index 000000000..21ee51005 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/webform_devel.module @@ -0,0 +1,65 @@ +getHandlerClasses(); + $handlers['form']['export'] = 'Drupal\webform_devel\Form\WebformEntityExportForm'; + $entity_type->setHandlerClass('form', $handlers['form']); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function webform_form_config_single_export_form_alter(&$form, FormStateInterface $form_state) { + $form['export']['#type'] = 'webform_codemirror'; + $form['export']['#mode'] = 'yaml'; + + $form['config_name']['#ajax']['callback'] = '_webform_form_config_single_export_form_update_export'; +} + +/** + * Handles switching the export textarea and tidies exported MSK configuration. + * + * Copied from: \Drupal\config\Form\ConfigSingleExportForm::updateExport. + */ +function _webform_form_config_single_export_form_update_export($form, FormStateInterface $form_state) { + // Determine the full config name for the selected config entity. + if ($form_state->getValue('config_type') !== 'system.simple') { + $definition = \Drupal::entityManager()->getDefinition($form_state->getValue('config_type')); + $name = $definition->getConfigPrefix() . '.' . $form_state->getValue('config_name'); + } + // The config name is used directly for simple configuration. + else { + $name = $form_state->getValue('config_name'); + } + + // 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. + if (strpos($name, 'webform') === 0) { + $value = WebformYaml::tidy($value); + } + + $form['export']['#type'] = 'webform_codemirror'; + $form['export']['#mode'] = 'yaml'; + $form['export']['#description'] = t('Filename: %name', ['%name' => $name . '.yml']); + $form['export']['#value'] = $value; + + return $form['export']; +} diff --git a/web/modules/contrib/webform/modules/webform_devel/webform_devel.routing.yml b/web/modules/contrib/webform/modules/webform_devel/webform_devel.routing.yml new file mode 100644 index 000000000..e975a196f --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_devel/webform_devel.routing.yml @@ -0,0 +1,7 @@ +entity.webform.export_form: + path: '/admin/structure/webform/manage/{webform}/export' + defaults: + _entity_form: 'webform.export' + _title_callback: '\Drupal\webform\Controller\WebformController::title' + requirements: + _permission: 'access devel information' diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements.yml new file mode 100644 index 000000000..7b7baf4d6 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements.yml @@ -0,0 +1,595 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_elements +title: 'Example: Elements' +description: 'Examples of every supported webform element.' +elements: | + basic_elements: + '#type': details + '#title': 'Basic elements' + '#open': true + checkbox: + '#type': checkbox + '#title': Checkbox + hidden: + '#type': hidden + '#title': Hidden + password: + '#type': password + '#title': Password + textarea: + '#type': textarea + '#title': Textarea + textfield: + '#type': textfield + '#title': 'Text field' + textfield_multiple: + '#type': textfield + '#title': 'Text field multiple' + '#multiple': true + advanced_elements: + '#type': details + '#title': 'Advanced elements' + '#open': true + webform_autocomplete: + '#type': webform_autocomplete + '#title': Autocomplete + '#autocomplete_items': country_names + webform_autocomplete_multiple: + '#type': webform_autocomplete + '#title': 'Autocomplete multiple' + '#autocomplete_items': country_names + '#multiple': true + captcha: + '#type': captcha + '#title': CAPTCHA + webform_codemirror: + '#type': webform_codemirror + '#title': CodeMirror + '#mode': yaml + color: + '#type': color + '#title': Color + webform_creditcard_number: + '#type': webform_creditcard_number + '#title': 'Credit card number' + email: + '#type': email + '#title': Email + email_multiple: + '#type': email + '#title': 'Email multiple' + '#multiple': true + webform_email_confirm: + '#type': webform_email_confirm + '#title': 'Email confirm' + webform_email_multiple: + '#type': webform_email_multiple + '#title': 'Email multiple' + number: + '#type': number + '#title': Number + '#min': 0 + '#max': 10 + '#step': 1 + number_multiple: + '#type': number + '#title': 'Number multiple' + '#min': 0 + '#max': 10 + '#step': 1 + '#multiple': true + password_confirm: + '#type': password_confirm + '#title': 'Password confirm' + '#description': 'Known Issues:
          Issue #1427838: password and password_confirm children do not pick up #states or #attributes' + range: + '#type': range + '#title': Range + '#min': 0 + '#max': 100 + '#step': 1 + '#range__output': true + '#range__output_prefix': $ + '#range__output_suffix': '.00' + webform_rating: + '#type': webform_rating + '#title': Rating + search: + '#type': search + '#title': Search + webform_signature: + '#type': webform_signature + '#title': Signature + tel: + '#type': tel + '#title': Telephone + '#international': true + tel_multiple: + '#type': tel + '#title': 'Telephone multiple' + '#international': true + '#multiple': true + text_format: + '#type': text_format + '#title': 'Text format' + '#description': 'Known Issues:
          Issue #997826: #states doesn''t work correctly with type text_format
          Issue #2625128: Text format selection stays visible when using editor and a hidden webform state
          Issue #1954968: Required CKEditor fields always fail HTML5 validation' + webform_toggle: + '#type': webform_toggle + '#title': Toggle + url: + '#type': url + '#title': URL + url_multiple: + '#type': url + '#title': 'URL multiple' + '#multiple': true + value: + '#type': value + '#title': Value + markup_elements: + '#type': details + '#title': 'Markup elements' + '#open': true + processed_text: + '#type': processed_text + '#title': 'Advanced HTML/Text' + processed_text_content: + '#markup': 'This is a Advanced HTML/Text container.' + webform_markup: + '#type': webform_markup + '#title': 'Basic HTML' + '#description': 'Known Issues:
          Issue #2700667: Notice: Undefined index: #type in drupal_process_states()' + webform_markup_content: + '#markup': 'This is a Basic HTML container.' + webform_message: + '#type': webform_message + '#title': Message + '#description': 'Known Issues:
          Issue #77245: A place for JavaScript status messages' + '#message_type': warning + '#message_message': 'This is a warning message.' + webform_message_content: + '#markup': 'This is a Message container.' + file_upload_elements: + '#type': details + '#title': 'File upload elements' + '#open': true + webform_audio_file: + '#type': webform_audio_file + '#title': 'Audio file' + webform_audio_file_multiple: + '#type': webform_audio_file + '#title': 'Audio file multiple' + '#multiple': true + webform_document_file: + '#type': webform_document_file + '#title': 'Document file' + webform_document_file_multiple: + '#type': webform_document_file + '#title': 'Document file multiple' + '#multiple': true + managed_file: + '#type': managed_file + '#title': File + '#description': 'Known Issues:
          Issue #2705471: Webform states managed file fields
          Issue #2113931: File Field design update' + managed_file_multiple: + '#type': managed_file + '#title': 'File multiple' + '#multiple': true + webform_image_file: + '#type': webform_image_file + '#title': 'Image file' + webform_image_file_multiple: + '#type': webform_image_file + '#title': 'Image file multiple' + '#multiple': true + webform_video_file: + '#type': webform_video_file + '#title': 'Video file' + webform_video_file_multiple: + '#type': webform_video_file + '#title': 'Video file multiple' + '#multiple': true + options_elements: + '#type': details + '#title': 'Options elements' + '#open': true + webform_buttons: + '#type': webform_buttons + '#title': Buttons + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_buttons_other: + '#type': webform_buttons_other + '#title': 'Buttons other' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + checkboxes: + '#type': checkboxes + '#title': Checkboxes + '#description': 'Known Issues:
          Issue #994360: #states cannot disable/enable radios and checkboxes
          Issue #2836364: Wrapper attributes are not supported by composite elements, this includes radios, checkboxes, and buttons.' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_checkboxes_other: + '#type': webform_checkboxes_other + '#title': 'Checkboxes other' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + 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 + radios: + '#type': radios + '#title': Radios + '#description': 'Known Issues:
          Issue #2731991: Setting required on radios marks all options required
          Issue #994360: #states cannot disable/enable radios and checkboxes
          Issue #2836364: Wrapper attributes are not supported by composite elements, this includes radios, checkboxes, and buttons.' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_radios_other: + '#type': webform_radios_other + '#title': 'Radios other' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select: + '#type': select + '#title': Select + '#description': 'Known Issues:
          Issue #1426646: "-Select-" option is lost when webform elements uses ''#states''
          Issue #1149078: States API doesn''t work with multiple select fields
          Issue #2791741: FAPI states: fields aren''t hidden initially when depending on multi-value selection' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + select_multiple: + '#type': select + '#title': 'Select multiple' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + '#multiple': true + '#select2': true + webform_select_other: + '#type': webform_select_other + '#title': 'Select other' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_select_other_multiple: + '#type': webform_select_other + '#title': 'Select other multiple' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + '#multiple': true + '#select2': true + tableselect: + '#type': tableselect + '#title': 'Table select' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_tableselect_sort: + '#type': webform_tableselect_sort + '#title': 'Tableselect sort' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_table_sort: + '#type': webform_table_sort + '#title': 'Table sort' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + webform_toggles: + '#type': webform_toggles + '#title': Toggles + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + containers: + '#type': details + '#title': Containers + '#open': true + container: + '#type': container + '#title': Container + container_content: + '#markup': 'This is a Container container.' + details: + '#type': details + '#title': Details + '#description': 'Known Issues:
          Issue #2348851: Regression: Allow HTML tags inside detail summary' + details_content: + '#markup': 'This is a Details container.' + fieldset: + '#type': fieldset + '#title': Fieldset + fieldset_content: + '#markup': 'This is a Fieldset container.' + item: + '#type': item + '#title': Item + '#description': 'Known Issues:
          Issue #783438: #states doesn''t work for #type item' + '#markup': '{markup}' + '#field_prefix': '{field_prefix}' + '#field_suffix': '{field_suffix}' + item_content: + '#markup': 'This is a Item container.' + label: + '#type': label + '#title': Label + label_content: + '#markup': 'This is a Label container.' + date_time_elements: + '#type': details + '#title': 'Date/time elements' + '#open': true + date: + '#type': date + '#title': Date + date_multiple: + '#type': date + '#title': 'Date multiple' + '#multiple': true + datetime: + '#type': datetime + '#title': Date/time + '#description': 'Known Issues:
          Issue #2419131: #states attribute does not work on #type datetime' + datetime_multiple: + '#type': datetime + '#title': 'Date/time multiple' + '#multiple': true + datelist: + '#type': datelist + '#title': 'Date list' + datelist_multiple: + '#type': datelist + '#title': 'Date list multiple' + '#multiple': true + webform_time: + '#type': webform_time + '#title': Time + '#description': 'Known Issues:
          Issue #1838234: Add jQuery Timepicker for the Time element of the datetime field' + webform_time_multiple: + '#type': webform_time + '#title': 'Time multiple' + '#multiple': true + entity_reference_elements: + '#type': details + '#title': 'Entity reference elements' + '#open': true + entity_autocomplete: + '#type': entity_autocomplete + '#title': 'Entity autocomplete' + '#description': 'Known Issues:
          Issue #2826451: TermSelection returning HTML characters in select list' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + entity_autocomplete_tags: + '#type': entity_autocomplete + '#title': 'Entity autocomplete tags' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + '#tags': true + entity_autocomplete_multiple: + '#type': entity_autocomplete + '#title': 'Entity autocomplete multiple' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + '#multiple': true + webform_entity_checkboxes: + '#type': webform_entity_checkboxes + '#title': 'Entity checkboxes' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + '#options': + 1: Administrator + 0: Anonymous + webform_entity_radios: + '#type': webform_entity_radios + '#title': 'Entity radios' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + '#options': null + webform_entity_select: + '#type': webform_entity_select + '#title': 'Entity select' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + webform_entity_select_multiple: + '#type': webform_entity_select + '#title': 'Entity select multiple' + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: true + '#multiple': true + '#select2': true + webform_term_select: + '#type': webform_term_select + '#title': 'Term select' + '#vocabulary': tags + webform_term_select_multiple: + '#type': webform_term_select + '#title': 'Term select multiple' + '#vocabulary': tags + '#multiple': true + '#select2': true + other_elements: + '#type': details + '#title': 'Other elements' + '#open': true + webform_element: + '#type': webform_element + '#title': 'Generic element' + language_select: + '#type': language_select + '#title': 'Language select' + '#languages': 3 + machine_name: + '#type': machine_name + '#title': 'Machine name' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_composite.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_composite.yml new file mode 100644 index 000000000..325453a13 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_composite.yml @@ -0,0 +1,182 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_elements_composite +title: 'Example: Elements: Composite' +description: 'Examples of composite elements, includes address, contact, and credit card.' +elements: | + webform_address_example: + '#type': details + '#title': Address + '#open': true + webform_address: + '#type': webform_address + '#title': Address + webform_address_multiple: + '#type': webform_address + '#title': '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_creditcard_example: + '#type': details + '#title': 'Credit card' + '#open': true + webform_creditcard: + '#type': webform_creditcard + '#title': 'Credit card' + webform_link_example: + '#type': details + '#title': Link + '#open': true + webform_link: + '#type': webform_link + '#title': Link + webform_link_multiple: + '#type': webform_link + '#title': 'Link multiple' + '#multiple': true + '#multiple__header': true + webform_location_example: + '#type': details + '#title': Location + '#open': true + webform_location: + '#type': webform_location + '#title': Location + webform_location_multiple: + '#type': webform_location + '#title': 'Location multiple' + '#multiple': true + webform_name_example: + '#type': details + '#title': Name + '#open': true + webform_name: + '#type': webform_name + '#title': Name + webform_name_multiple: + '#type': webform_name + '#title': 'Name multiple' + '#multiple': true + '#multiple__header': true + webform_telephone_example: + '#type': details + '#title': 'Telephone advanced' + '#open': true + webform_telephone: + '#type': webform_telephone + '#title': 'Telephone advanced' + webform_telephone_multiple: + '#type': webform_telephone + '#title': 'Telephone advanced multiple' + '#multiple': true + '#multiple__header': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_masks.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_masks.yml new file mode 100644 index 000000000..b911449f2 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_masks.yml @@ -0,0 +1,167 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_elements_masks +title: 'Example: Elements: Input Masks' +description: 'Examples of elements with input masks.' +elements: | + masks: + '#type': details + '#title': 'Simple masks' + '#open': true + phone: + '#type': textfield + '#title': Phone + '#input_mask': '(999) 999-9999' + zip: + '#type': textfield + '#title': 'Zip code' + '#input_mask': '99999[-9999]' + ssn: + '#type': textfield + '#title': SSN + '#input_mask': 999-99-9999 + license_plate: + '#type': textfield + '#title': 'License plate' + '#input_mask': '[9-]AAA-999' + aliases: + '#type': details + '#title': Aliases + '#open': true + email: + '#type': textfield + '#title': Email + '#input_mask': '''alias'': ''email''' + date: + '#type': textfield + '#title': 'Date (mm/dd/yyyy)' + '#input_mask': '''alias'': ''mm/dd/yyyy''' + currency: + '#type': textfield + '#title': Currency + '#input_mask': '''alias'': ''currency''' + '#attributes': + style: 'text-align: right' + numeric: + '#type': textfield + '#title': Numeric + '#input_mask': '''alias'': ''numeric'', ''groupSeparator'': '','', ''autoGroup'': true, ''digits'': 2, ''digitsOptional'': false, ''prefix'': ''$ '', ''placeholder'': ''0''' + '#attributes': + style: 'text-align: right' + percentage: + '#type': textfield + '#title': 'Percentage (default)' + '#input_mask': '''alias'': ''percentage''' + '#attributes': + style: 'text-align: right' + ip: + '#type': textfield + '#title': 'IP address' + '#input_mask': '''alias'': ''ip''' + mac: + '#type': textfield + '#title': 'MAC addresses' + '#input_mask': '''alias'': ''mac''' + vin: + '#type': textfield + '#title': 'VIN (Vehicle identification number)' + '#input_mask': '''alias'': ''vin''' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_states.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_states.yml new file mode 100644 index 000000000..509ed1fb2 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_elements_states.yml @@ -0,0 +1,267 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_elements_states +title: 'Example: Elements: Condition Logic' +description: 'Examples of elements using conditional logic.' +elements: | + checkbox_example: + '#type': details + '#title': 'Checkbox example' + '#open': true + checkbox: + '#type': checkbox + '#title': 'Please check this box' + checkbox_explain: + '#type': textarea + '#title': 'Please explain why you checked the above box' + '#states': + visible: + ':input[name="checkbox"]': + checked: true + required: + ':input[name="checkbox"]': + checked: true + checkboxes_example: + '#type': details + '#title': 'Checkboxes example' + '#open': true + checkboxes: + '#type': checkboxes + '#title': 'Please check all' + '#options': + one: One + two: Two + checkboxes_explain: + '#type': textarea + '#title': 'Please explain why you checked all the above box' + '#states': + visible: + ':input[name="checkboxes[one]"]': + checked: true + ':input[name="checkboxes[two]"]': + checked: true + required: + ':input[name="checkboxes[one]"]': + checked: true + ':input[name="checkboxes[two]"]': + checked: true + select_example: + '#type': details + '#title': 'Select with other example' + '#open': true + select: + '#type': select + '#title': 'Please select ''other'' in the below select menu.' + '#options': + 1: One + 2: Two + 3: Three + other: Other... + select_other: + '#type': textfield + '#attributes': + placeholder: 'Enter other...' + '#states': + visible: + ':input[name="select"]': + value: other + required: + ':input[name="select"]': + value: other + select_multiple_example: + '#type': details + '#title': 'Select multiple example' + '#open': true + select_multiple: + '#type': select + '#title': 'Please select ''One'' or ''Two'' in the below select menu.' + '#options': + 1: One + 2: Two + 3: Three + 4: Four + select_multiple_other: + '#type': textfield + '#attributes': + placeholder: 'Enter other...' + '#states': + visible: + - ':input[name="select_multiple"]': + - value: 1 + - or + - ':input[name="select_multiple"]': + - value: 2 + required: + - ':input[name="select_multiple"]': + - value: 1 + - or + - ':input[name="select_multiple"]': + - value: 2 + radios_example: + '#type': details + '#title': 'Radio buttons with other example' + '#open': true + radios: + '#type': radios + '#title': 'Please select ''other'' from the below radio buttons.' + '#options': + 1: One + 2: Two + 3: Three + other: Other... + radios_other: + '#type': textfield + '#attributes': + placeholder: 'Enter other...' + '#states': + visible: + ':input[name="radios"]': + value: other + required: + ':input[name="radios"]': + value: other + checkbox_more_example: + '#type': details + '#title': 'Checkbox with more information example' + '#open': true + checkbox_more: + '#type': checkbox + '#title': 'Please check this box to enter more information' + checkbox_more_details: + '#type': details + '#title': 'More information' + '#open': true + '#states': + expanded: + ':input[name="checkbox_more"]': + checked: true + checkbox_more_first_name: + '#type': textfield + '#title': 'First name' + '#states': + required: + ':input[name="checkbox_more"]': + checked: true + checkbox_more_last_name: + '#type': textfield + '#title': 'Last name' + '#states': + required: + ':input[name="checkbox_more"]': + checked: true + filled_example: + '#type': details + '#title': 'Text field filled example' + '#open': true + filled: + '#type': textfield + '#title': 'Fill in the below text field to enable and require the next text field.' + filled_next: + '#type': textfield + '#title': 'The next text field' + '#states': + enabled: + ':input[name="filled"]': + filled: true + required: + ':input[name="filled"]': + filled: true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_basic.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_basic.yml new file mode 100644 index 000000000..f4405d9b2 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_basic.yml @@ -0,0 +1,303 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_layout_basic +title: 'Example: Layout: Basic' +description: 'Examples of basic element layout and enhancements using messages, CSS classes, custom attributes, and tables.' +elements: | + message_example: + '#type': details + '#title': Messages + '#open': true + message_info: + '#type': webform_message + '#message_type': info + '#message_message': 'This is an info message.' + '#message_close': true + message_status: + '#type': webform_message + '#message_message': 'This is a status message.' + '#message_close': true + message_warning: + '#type': webform_message + '#message_type': warning + '#message_message': 'This is a warning message.' + '#message_close': true + message_error: + '#type': webform_message + '#message_type': error + '#message_message': 'This is a error message.' + '#message_close': true + form_inline_example: + '#type': details + '#title': 'Webform inline with warning (.form--inline.messages.messages--warning)' + '#open': true + form_inline_container: + '#type': container + '#attributes': + class: + - 'form--inline clearfix' + - 'messages messages--warning' + style: 'padding-top: 0; padding-bottom: 0;' + form_inline_first_name: + '#title': 'First Name' + '#type': textfield + '#size': 20 + form_inline_last_name: + '#title': 'Last Name' + '#type': textfield + '#size': 20 + container_inline_example: + '#type': details + '#title': 'Container inline (.container-inline)' + '#open': true + container_inline_first_name: + '#title': 'First Name' + '#type': textfield + '#size': 20 + '#wrapper_attributes': + class: + - 'container-inline clearfix' + container_inline_last_name: + '#title': 'Last Name' + '#type': textfield + '#size': 20 + '#wrapper_attributes': + class: + - 'container-inline clearfix' + options_example: + '#type': details + '#title': 'Options (#options_display)' + '#open': true + checkboxes_side_by_side: + '#type': checkboxes + '#title': 'Checkboxes side by side' + '#options_display': side_by_side + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + six: Six + radios_two_columns: + '#type': radios + '#title': 'Radios two columns' + '#options_display': two_columns + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + six: Six + checkboxes_two_columns: + '#type': radios + '#title': 'Checkboxes three columns' + '#options': + one: One + two: Two + three: Three + four: Four + five: Five + six: Six + '#options_display': three_columns + title_example: + '#type': details + '#title': 'Title display' + '#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_example: + '#type': details + '#title': 'Description display' + '#open': true + description_display_before: + '#type': textfield + '#title': 'Description displayed before' + '#description': 'This description is displayed before the input.' + '#description_display': before + description_display_tooltip: + '#type': textfield + '#title': 'Description displayed in tooltip' + '#description': 'This description is displayed in a tooltip. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante.' + '#description_display': tooltip + table_example: + '#type': details + '#title': Table + '#open': true + table: + '#type': table + '#header': + - 'First Name' + - 'Last Name' + - Gender + - 'US Citizen' + table__1: + table__1__first_name: + '#title': '1 - First name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__1__last_name: + '#title': '1 - Last name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__1__gender: + '#title': '1 - Gender' + '#title_display': invisible + '#type': select + '#options': gender + table__1__us: + '#title': '1 - US Citizen' + '#title_display': invisible + '#type': checkbox + table__2: + table__2__first_name: + '#title': '2 - First name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__2__last_name: + '#title': '2 - Last name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__2__gender: + '#title': '2 - Gender' + '#title_display': invisible + '#type': select + '#options': gender + table__2__us: + '#title': '2 - US Citizen' + '#title_display': invisible + '#type': checkbox + table__3: + table__3__first_name: + '#title': '3 - First name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__3__last_name: + '#title': '3 - Last name' + '#title_display': invisible + '#type': textfield + '#size': 30 + table__3__gender: + '#title': '3 - Gender' + '#title_display': invisible + '#type': select + '#options': gender + table__3__us: + '#title': '3 - US Citizen' + '#title_display': invisible + '#type': checkbox +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_flexbox.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_flexbox.yml new file mode 100644 index 000000000..893f979c9 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_layout_flexbox.yml @@ -0,0 +1,207 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_layout_flexbox +title: 'Example: Layout: Flexbox' +description: 'Examples of multiple column webform layout using Flexbox.' +elements: | + name: + '#type': webform_flexbox + title: + '#type': textfield + '#title': Title + first_name: + '#type': textfield + '#title': 'First name' + '#flex': 3 + middle_name: + '#type': textfield + '#title': 'Middle name' + '#flex': 3 + last_name: + '#type': textfield + '#title': 'Last name' + '#flex': 5 + suffix: + '#type': textfield + '#title': Suffix + personal_information_1: + '#type': webform_flexbox + birth_date: + '#type': datelist + '#title': 'Birth date' + '#date_part_order': + - month + - day + - year + gender: + '#type': radios + '#title': Gender + '#options': gender + '#options_display': two_columns + personal_information_2: + '#type': webform_flexbox + ethnicity: + '#type': webform_checkboxes_other + '#title': Ethnicity + '#options': ethnicity + '#options_display': three_columns + marital_status: + '#type': radios + '#title': 'Marital status' + '#options': marital_status + '#options_display': two_columns + work: + '#type': webform_flexbox + employment_status: + '#type': webform_select_other + '#title': 'Employment status' + '#options': employment_status + industry: + '#type': webform_select_other + '#title': 'If employed, industry of employer' + '#options': industry + address_line_1: + '#type': webform_flexbox + address: + '#type': textfield + '#title': 'Street address (Line 1)' + address_line_2: + '#type': webform_flexbox + address_1: + '#type': textfield + '#title': 'Street address (Line 2)' + address_line_3: + '#type': webform_flexbox + city: + '#type': textfield + '#title': City + state_province: + '#type': webform_select_other + '#title': State/Province + '#options': state_province_names + zip: + '#type': textfield + '#title': Zip + address_line_4: + country: + '#type': select + '#title': Country + '#options': country_names + phone_number: + '#type': webform_flexbox + mobile_phone: + '#type': tel + '#title': 'Mobile phone' + home_phone: + '#type': tel + '#title': 'Home phone' + work_phone: + '#type': tel + '#title': 'Home phone' + web: + '#type': webform_flexbox + email: + '#type': email + '#title': 'E-mail address' + url: + '#type': url + '#title': 'Home page (URL)' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml new file mode 100644 index 000000000..da42c05ea --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/config/install/webform.webform.example_wizard.yml @@ -0,0 +1,141 @@ +langcode: en +status: open +dependencies: + enforced: + module: + - webform_examples +open: null +close: null +uid: null +template: false +id: example_wizard +title: 'Example: Wizard' +description: 'Example of a multiple step ''wizard'' webform.' +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': radios + '#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 +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Apply + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: true + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 2 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: true + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_examples/webform_examples.info.yml b/web/modules/contrib/webform/modules/webform_examples/webform_examples.info.yml new file mode 100644 index 000000000..04985722b --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_examples/webform_examples.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Examples' +type: module +description: 'Provides examples of all webform elements and functionality which can used for demonstrating and testing advanced functionality or used as cut-n-paste code snippets for creating new webforms.' +package: Webform +# core: 8.x +dependencies: + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/core.base_field_override.node.webform.promote.yml b/web/modules/contrib/webform/modules/webform_node/config/install/core.base_field_override.node.webform.promote.yml new file mode 100644 index 000000000..9625bf5df --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/core.base_field_override.node.webform.promote.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + config: + - node.type.webform +id: node.webform.promote +field_name: promote +entity_type: node +bundle: webform +label: 'Promoted to front page' +description: '' +required: false +translatable: true +default_value: + - value: 0 +default_value_callback: '' +settings: + on_label: 'On' + off_label: 'Off' +field_type: boolean diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_form_display.node.webform.default.yml b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_form_display.node.webform.default.yml new file mode 100644 index 000000000..a24125c87 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_form_display.node.webform.default.yml @@ -0,0 +1,67 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.webform.body + - field.field.node.webform.webform + - node.type.webform + module: + - path + - text + - webform +id: node.webform.default +targetEntityType: node +bundle: webform +mode: default +content: + body: + type: text_textarea_with_summary + weight: 0 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 15 + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 16 + third_party_settings: { } + title: + type: string_textfield + weight: -5 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } + webform: + weight: 0 + settings: { } + third_party_settings: { } + type: webform_entity_reference_select +hidden: { } diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.default.yml b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.default.yml new file mode 100644 index 000000000..5ba69cfae --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.default.yml @@ -0,0 +1,33 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.webform.body + - field.field.node.webform.webform + - node.type.webform + module: + - text + - user + - webform +id: node.webform.default +targetEntityType: node +bundle: webform +mode: default +content: + body: + label: hidden + type: text_default + weight: 101 + settings: { } + third_party_settings: { } + links: + weight: 100 + settings: { } + third_party_settings: { } + webform: + weight: 102 + label: hidden + settings: { } + third_party_settings: { } + type: webform_entity_reference_entity_view +hidden: { } diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.teaser.yml b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.teaser.yml new file mode 100644 index 000000000..969e0acba --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/core.entity_view_display.node.webform.teaser.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.webform.body + - field.field.node.webform.webform + - node.type.webform + module: + - text + - user +id: node.webform.teaser +targetEntityType: node +bundle: webform +mode: teaser +content: + body: + label: hidden + type: text_summary_or_trimmed + weight: 101 + settings: + trim_length: 600 + third_party_settings: { } + links: + weight: 100 + settings: { } + third_party_settings: { } +hidden: + webform: true diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/field.field.node.webform.body.yml b/web/modules/contrib/webform/modules/webform_node/config/install/field.field.node.webform.body.yml new file mode 100644 index 000000000..fd9818435 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/field.field.node.webform.body.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.webform + module: + - text +id: node.webform.body +field_name: body +entity_type: node +bundle: webform +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/contrib/webform/modules/webform_node/config/install/field.field.node.webform.webform.yml b/web/modules/contrib/webform/modules/webform_node/config/install/field.field.node.webform.webform.yml new file mode 100644 index 000000000..18d13227b --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/field.field.node.webform.webform.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.webform + - node.type.webform + module: + - webform +id: node.webform.webform +field_name: webform +entity_type: node +bundle: webform +label: Form +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + default_data: '' + status: true + handler: 'default:webform' + handler_settings: { } +field_type: webform diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/field.storage.node.webform.yml b/web/modules/contrib/webform/modules/webform_node/config/install/field.storage.node.webform.yml new file mode 100644 index 000000000..ee9415de3 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/field.storage.node.webform.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node + - webform +id: node.webform +field_name: webform +entity_type: node +type: webform +settings: + target_type: webform +module: webform +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/web/modules/contrib/webform/modules/webform_node/config/install/node.type.webform.yml b/web/modules/contrib/webform/modules/webform_node/config/install/node.type.webform.yml new file mode 100644 index 000000000..7a2f1bf06 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/config/install/node.type.webform.yml @@ -0,0 +1,13 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - webform +name: Webform +type: webform +description: 'A basic page with a webform attached.' +help: '' +new_revision: false +preview_mode: 1 +display_submitted: true diff --git a/web/modules/contrib/webform/modules/webform_node/src/Access/WebformNodeAccess.php b/web/modules/contrib/webform/modules/webform_node/src/Access/WebformNodeAccess.php new file mode 100644 index 000000000..c51d1633a --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/src/Access/WebformNodeAccess.php @@ -0,0 +1,151 @@ +isAllowed()) { + $webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($node); + return WebformAccess::checkResultsAccess($node->$webform_field_name->entity, $node); + } + else { + return $access_result; + } + } + + /** + * Check whether the user can access a node's webform. + * + * @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 checkWebformAccess($operation, $entity_access, NodeInterface $node, AccountInterface $account) { + return self::checkAccess($operation, $entity_access, $node, NULL, $account); + } + + /** + * Check whether the user can access a node's webform submission. + * + * @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\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * @param bool $disable_pages + * Flag to disable pages for the current route. + * @param bool $resend + * Flag to check resend email access. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkWebformSubmissionAccess($operation, $entity_access, NodeInterface $node, WebformSubmissionInterface $webform_submission, AccountInterface $account, $disable_pages = FALSE, $resend = FALSE) { + $access_result = self::checkAccess($operation, $entity_access, $node, $webform_submission, $account); + if ($access_result->isForbidden()) { + return $access_result; + } + + if ($disable_pages) { + return WebformAccess::checkWebformWizardPagesAccess($webform_submission->getWebform()); + } + + if ($resend) { + return WebformAccess::checkEmailAccess($webform_submission, $account); + } + + return $access_result; + } + + /** + * Check whether the user can access a node's webform and/or submission. + * + * @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\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + protected static function checkAccess($operation, $entity_access, NodeInterface $node, WebformSubmissionInterface $webform_submission = NULL, AccountInterface $account = NULL) { + $webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($node); + // Check that the node has a valid webform reference. + if (!$webform_field_name || !$node->$webform_field_name->entity) { + return AccessResult::forbidden(); + } + + // Check that the webform submission was created via the webform node. + if ($webform_submission && $webform_submission->getSourceEntity() != $node) { + return AccessResult::forbidden(); + } + + // Check the node operation. + if ($operation && $node->access($operation, $account)) { + return AccessResult::allowed(); + } + + // Check entity access. + if ($entity_access) { + // Check entity access for the webform. + if (strpos($entity_access, 'webform.') === 0 + && $node->$webform_field_name->entity->access(str_replace('webform.', '', $entity_access), $account)) { + return AccessResult::allowed(); + } + // Check entity access for the webform submission. + if (strpos($entity_access, 'webform_submission.') === 0 + && $webform_submission->access(str_replace('webform_submission.', '', $entity_access), $account)) { + return AccessResult::allowed(); + } + } + + return AccessResult::forbidden(); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php new file mode 100644 index 000000000..b6c43d3f0 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeAccessTest.php @@ -0,0 +1,131 @@ +createUsers(); + } + + /** + * Tests webform node access rules. + * + * @see \Drupal\webform\Tests\WebformAccessTest::testAccessRules + */ + public function testAccessRules() { + // Create webform node that references the contact webform. + $webform = Webform::load('contact'); + $node = $this->drupalCreateNode(['type' => 'webform']); + $node->webform->target_id = 'contact'; + $node->webform->status = 1; + $node->save(); + $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', + ]; + $this->drupalPostForm('node/' . $node->id(), $edit, t('Send message')); + $sid = $this->getLastSubmissionId($webform); + + // 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/table' => '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/contrib/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php new file mode 100644 index 000000000..8360b8ee8 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeResultsTest.php @@ -0,0 +1,196 @@ +placeBlocks(); + } + + /** + * Tests webform node results. + */ + public function testResults() { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + + $this->createUsers(); + + $webform = Webform::load('contact'); + + // Create node. + $node = $this->drupalCreateNode(['type' => 'webform']); + + /* Webform entity reference */ + + // Check access denied to webform results. + $this->drupalLogin($this->adminSubmissionUser); + $this->drupalGet('node/' . $node->id() . '/webform/results/submissions'); + $this->assertResponse(403); + + // Set Node webform to the contact webform. + $node->webform->target_id = 'contact'; + $node->webform->status = 1; + $node->save(); + + /* Submission management */ + + // Generate 3 node submissions and 3 webform submissions. + $this->drupalLogin($this->normalUser); + $node_sids = []; + $webform_sids = []; + for ($i = 1; $i <= 3; $i++) { + $edit = [ + 'name' => "node$i", + 'email' => "node$i@example.com", + 'subject' => "Node $i subject", + 'message' => "Node $i message", + ]; + $this->drupalPostForm('node/' . $node->id(), $edit, t('Send message')); + $node_sids[$i] = $this->getLastSubmissionId($webform); + $edit = [ + 'name' => "webform$i", + 'email' => "webform$i@example.com", + 'subject' => "Webform $i subject", + 'message' => "Webform $i message", + ]; + $this->drupalPostForm('webform/contact', $edit, t('Send message')); + $webform_sids[$i] = $this->getLastSubmissionId($webform); + } + + // Check that 6 submission were created. + $this->assertEqual($submission_storage->getTotal($webform, $node), 3); + $this->assertEqual($submission_storage->getTotal($webform), 6); + + // Check webform node results. + $this->drupalLogin($this->adminSubmissionUser); + $node_route_parameters = ['node' => $node->id(), 'webform_submission' => $node_sids[1]]; + $node_submission_url = Url::fromRoute('entity.node.webform_submission.canonical', $node_route_parameters); + $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->assertResponse(200); + $this->assertRaw('

          ' . $node->label() . '

          '); + $this->assertNoRaw('

          ' . $webform->label() . '

          '); + $this->assertRaw(('' . $node_sids[1] . '')); + $this->assertNoRaw(('' . $webform_sids[1] . '')); + + // Check webform node title. + $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->assertRaw($node->label() . ': Submission #' . $node_sids[2]); + + // Check webform node navigation. + $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(''); + + // Check webform node saved draft. + $webform->setSetting('draft', TRUE); + $webform->save(); + + // Check webform saved draft. + $this->drupalLogin($this->normalUser); + $edit = [ + 'name' => "nodeDraft", + 'email' => "nodeDraft@example.com", + 'subject' => "Node draft subject", + 'message' => "Node draft message", + ]; + $this->drupalPostForm('node/' . $node->id(), $edit, t('Save Draft')); + $this->drupalGet('node/' . $node->id()); + $this->assertRaw('A partially-completed form was found. Please complete the remaining portions.'); + $this->drupalGet('webform/contact'); + $this->assertNoRaw('A partially-completed form was found. Please complete the remaining portions.'); + + /* Table customization */ + $this->drupalLogin($this->adminSubmissionUser); + + // Check default node results table. + $this->drupalGet('node/' . $node->id() . '/webform/results/table'); + $this->assertRaw(''); + $this->assertRaw('sort by Created'); + $this->assertNoRaw('sort by Changed'); + + // Customize to main webform's results table. + $edit = [ + 'columns[created][checkbox]' => FALSE, + 'columns[changed][checkbox]' => TRUE, + 'direction' => 'asc', + 'limit' => 20, + 'default' => TRUE, + ]; + $this->drupalPostForm('admin/structure/webform/manage/' . $webform->id() . '/results/table/custom', $edit, t('Save')); + $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/table'); + $this->assertRaw(''); + $this->assertNoRaw('sort by Created'); + $this->assertRaw('sort by Changed'); + + $this->drupalLogout(); + + /* Access control */ + + // Create any and own user accounts. + $any_user = $this->drupalCreateUser([ + 'access content', + 'view webform submissions any node', + 'edit webform submissions any node', + 'delete webform submissions any node', + ]); + $own_user = $this->drupalCreateUser([ + 'access content', + 'view webform submissions own node', + 'edit webform submissions own node', + 'delete webform submissions own node', + ]); + + // Check accessing results posted to any webform node. + $this->drupalLogin($any_user); + $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->assertResponse(403); + + $node->setOwnerId($own_user->id())->save(); + $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->assertEqual($submission_storage->getTotal($webform, $node), 0); + $this->assertEqual($submission_storage->getTotal($webform), 3); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeTest.php b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeTest.php new file mode 100644 index 000000000..945cbc677 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/src/Tests/WebformNodeTest.php @@ -0,0 +1,174 @@ +createUsers(); + } + + /** + * Tests webform node. + */ + public function testNode() { + // Create node. + $node = $this->drupalCreateNode(['type' => 'webform']); + + // Check contact webform. + $node->webform->target_id = 'contact'; + $node->webform->status = 1; + $node->save(); + $this->drupalGet('node/' . $node->id()); + $this->assertRaw('webform-submission-contact-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->assertFieldByName('name', 'John Smith'); + + /* Webform closed */ + + // Check contact webform closed. + $node->webform->status = 0; + $node->save(); + $this->drupalGet('node/' . $node->id()); + $this->assertNoFieldByName('name', 'John Smith'); + $this->assertRaw('Sorry...This form is closed to new submissions.'); + + /* Confirmation inline (test_confirmation_inline) */ + + // Check confirmation inline webform. + $node->webform->target_id = 'test_confirmation_inline'; + $node->webform->default_data = ''; + $node->webform->status = 1; + $node->save(); + $this->drupalPostForm('node/' . $node->id(), [], t('Submit')); + $this->assertRaw('This is a custom inline confirmation message.'); + + /* Submission limit (test_form_limit) */ + + // Set per entity total and user limit. + // @see \Drupal\webform\Tests\WebformSubmissionFormSettingsTest::testSettings + $node->webform->target_id = 'test_form_limit'; + $node->webform->default_data = ''; + $node->save(); + + $limit_form = Webform::load('test_form_limit'); + $limit_form->setSettings([ + 'limit_total' => NULL, + 'limit_user' => NULL, + 'entity_limit_total' => 3, + 'entity_limit_user' => 1, + 'limit_total_message' => 'Only 3 submissions are allowed.', + 'limit_user_message' => 'You are only allowed to have 1 submission for this webform.', + ]); + $limit_form->save(); + + // Check per entity user limit. + $this->drupalLogin($this->normalUser); + $this->drupalPostForm('node/' . $node->id(), [], t('Submit')); + $this->drupalGet('node/' . $node->id()); + $this->assertNoFieldByName('op', 'Submit'); + $this->assertRaw('You are only allowed to have 1 submission for this webform.'); + $this->drupalLogout(); + + // Check per entity total limit. + $this->drupalPostForm('node/' . $node->id(), [], t('Submit')); + $this->drupalPostForm('node/' . $node->id(), [], t('Submit')); + $this->drupalGet('node/' . $node->id()); + $this->assertNoFieldByName('op', 'Submit'); + $this->assertRaw('Only 3 submissions are allowed.'); + $this->assertNoRaw('You are only allowed to have 1 submission for this webform.'); + + /* Prepopulate source entity */ + + $webform_contact = Webform::load('contact'); + + $node->webform->target_id = 'contact'; + $node->webform->status = 1; + $node->webform->default_data = "name: '{name}'"; + $node->save(); + + $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->assertFieldByName('name', '{name}'); + + // Check prepopulating source entity using query string. + $edit = [ + 'name' => 'name', + 'email' => 'example@example.com', + 'subject' => 'subject', + 'message' => 'message', + ]; + $this->drupalPostForm('webform/contact', $edit, t('Send message'), $source_entity_options); + $sid = $this->getLastSubmissionId($webform_contact); + $submission = WebformSubmission::load($sid); + $this->assertNotNull($submission->getSourceEntity()); + if ($submission->getSourceEntity()) { + $this->assertEqual($submission->getSourceEntity() + ->getEntityTypeId(), 'node'); + $this->assertEqual($submission->getSourceEntity()->id(), $node->id()); + } + + /* Check displaying link to webform */ + + // Set webform reference to be displayed as a link. + $display_options = [ + 'type' => 'webform_entity_reference_link', + 'settings' => [ + 'label' => 'Register', + ], + ]; + $view_display = EntityViewDisplay::load('node.webform.default'); + $view_display->setComponent('webform', $display_options)->save(); + + // Set default data. + $node->webform->target_id = 'contact'; + $node->webform->status = 1; + $node->webform->default_data = "name: '{name}'"; + $node->save(); + + // Check 'Register' link. + $this->drupalGet('node/' . $node->id()); + $this->assertLink('Register'); + + // Check that link include source_entity_type and source_entity_id. + $this->assertLinkByHref($webform_contact->toUrl('canonical', $source_entity_options)->toString()); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.info.yml b/web/modules/contrib/webform/modules/webform_node/webform_node.info.yml new file mode 100644 index 000000000..ce8d12dd9 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.info.yml @@ -0,0 +1,18 @@ +name: 'Webform Node' +type: module +description: 'Provides a Webform content type which allows webforms to be integrated into a website as nodes.' +package: Webform +# core: 8.x +dependencies: + - 'drupal:field' + - 'drupal:node' + - 'drupal:path' + - 'drupal:text' + - 'drupal:user' + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.install b/web/modules/contrib/webform/modules/webform_node/webform_node.install new file mode 100644 index 000000000..d3a060139 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.install @@ -0,0 +1,45 @@ + t('Webform Node'), + 'description' => t('%title content type already exists, please delete the %title content type before installing the Webform node module.', ['%title' => $node_type->label()]), + 'severity' => REQUIREMENT_ERROR, + ]; + } + return $requirements; +} + +/** + * Implements hook_uninstall(). + */ +function webform_node_uninstall() { + // Delete the webform node type. + if ($node_type = NodeType::load('webform')) { + $node_type->delete(); + } + + // Delete field storage if it is no longer being used by any bundles. + if ($field_storage = \Drupal::entityTypeManager()->getStorage('field_storage_config')->load('node.webform')) { + if (!$field_storage->getBundles()) { + $field_storage->delete(); + } + } +} + diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.links.task.yml b/web/modules/contrib/webform/modules/webform_node/webform_node.links.task.yml new file mode 100644 index 000000000..67c7be3e1 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.links.task.yml @@ -0,0 +1,99 @@ + +entity.node.webform.test: + title: 'Test' + route_name: entity.node.webform.test + base_route: entity.node.canonical + +entity.node.webform.results: + title: 'Results' + route_name: entity.node.webform.results_submissions + base_route: entity.node.canonical + +entity.node.webform.results_submissions: + title: 'Submissions' + route_name: entity.node.webform.results_submissions + parent_id: entity.node.webform.results + +entity.node.webform.results_table: + title: 'Table' + route_name: entity.node.webform.results_table + parent_id: entity.node.webform.results + +entity.node.webform.results_export: + title: 'Download' + route_name: entity.node.webform.results_export + parent_id: entity.node.webform.results + +entity.node.webform.results_clear: + title: 'Clear' + route_name: entity.node.webform.results_clear + parent_id: entity.node.webform.results + +# Submission + +entity.node.webform_submission.canonical: + title: 'View' + route_name: entity.node.webform_submission.canonical + base_route: entity.node.webform_submission.canonical + +entity.node.webform_submission.html: + title: 'HTML' + route_name: entity.node.webform_submission.canonical + parent_id: entity.node.webform_submission.canonical + +entity.node.webform_submission.table: + title: 'Table' + route_name: entity.node.webform_submission.table + parent_id: entity.node.webform_submission.canonical + +entity.node.webform_submission.text: + title: 'Plain text' + route_name: entity.node.webform_submission.text + parent_id: entity.node.webform_submission.canonical + +entity.node.webform_submission.yaml: + title: 'Data (YAML)' + route_name: entity.node.webform_submission.yaml + parent_id: entity.node.webform_submission.canonical + +entity.node.webform_submission.edit_form: + title: 'Edit' + route_name: entity.node.webform_submission.edit_form + base_route: entity.node.webform_submission.canonical + +entity.node.webform_submission.edit_form.page: + title: 'Pages' + route_name: entity.node.webform_submission.edit_form + parent_id: entity.node.webform_submission.edit_form + +entity.node.webform_submission.edit_form.all: + title: 'All' + route_name: entity.node.webform_submission.edit_form.all + parent_id: entity.node.webform_submission.edit_form + +entity.node.webform_submission.notes_form: + title: 'Notes' + route_name: entity.node.webform_submission.notes_form + base_route: entity.node.webform_submission.canonical + +entity.node.webform_submission.resend_form: + title: 'Resend' + route_name: entity.node.webform_submission.resend_form + base_route: entity.node.webform_submission.canonical + +entity.node.webform_submission.delete_form: + title: 'Delete' + route_name: entity.node.webform_submission.delete_form + base_route: entity.node.webform_submission.canonical + +# User Submission + +entity.node.webform.user.submission: + title: 'View' + route_name: entity.node.webform.user.submission + base_route: entity.node.webform.user.submission + +entity.node.webform.user.submission.edit: + title: 'Edit' + route_name: entity.node.webform.user.submission.edit + base_route: entity.node.webform.user.submission diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.module b/web/modules/contrib/webform/modules/webform_node/webform_node.module new file mode 100644 index 000000000..26f2966bc --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.module @@ -0,0 +1,47 @@ +$webform_field_name->entity) { + return AccessResult::forbidden(); + } + + // Check administer webform submissions. + if ($account->hasPermission('administer webform submission')) { + return AccessResult::allowed(); + } + + // Change access to ANY submission. + $operation = str_replace('webform_submission_', '', $operation); + $any_permission = "$operation webform submissions any node"; + if ($account->hasPermission($any_permission)) { + return AccessResult::allowed(); + } + + // Change access to submission associated with the node's webform. + $own_permission = "$operation webform submissions own node"; + if ($account->hasPermission($own_permission) && $node->getOwnerId() === $account->id()) { + return AccessResult::allowed(); + } + + return AccessResult::forbidden(); + } +} diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.permissions.yml b/web/modules/contrib/webform/modules/webform_node/webform_node.permissions.yml new file mode 100644 index 000000000..c14ed7a69 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.permissions.yml @@ -0,0 +1,12 @@ +'view webform submissions any node': + title: 'View webform submissions for any node' +'view webform submissions own node': + title: 'View webform submissions for own node' +'edit webform submissions any node': + title: 'Edit webform submissions for any node' +'edit webform submissions own node': + title: 'Edit webform submissions for own node' +'delete webform submissions any node': + title: 'Delete webform submissions for any node' +'delete webform submissions own node': + title: 'Delete webform submissions for own node' diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.routing.yml b/web/modules/contrib/webform/modules/webform_node/webform_node.routing.yml new file mode 100644 index 000000000..afb4aaee1 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.routing.yml @@ -0,0 +1,313 @@ +entity.node.webform.confirmation: + path: '/node/{node}/webform/confirmation' + defaults: + _controller: '\Drupal\webform\Controller\WebformController::confirmation' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + operation: view + entity_access: 'webform.submission_create' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' + +entity.node.webform.user.submissions: + path: '/node/{node}/webform/submissions' + defaults: + _entity_list: 'webform_submission' + _title: 'Submissions' + operation: '' + entity_access: 'webform.submission_view_own' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' + _user_is_logged_in: 'TRUE' + +entity.node.webform.user.submission: + path: '/node/{node}/webform/submissions/{webform_submission}' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::index' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + type: 'default' + operation: webform_submission_view + entity_access: 'webform_submission.view' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' + _user_is_logged_in: 'TRUE' + +entity.node.webform.user.submission.edit: + path: '/node/{node}/webform/submissions/{webform_submission}/edit' + defaults: + _entity_form: 'webform_submission.default' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_edit + entity_access: 'webform_submission.update' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform.test: + path: '/node/{node}/webform/test' + defaults: + _controller: '\Drupal\webform\Controller\WebformTestController::testForm' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + operation: webform_submission_view + entity_access: 'webform.update' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformAccess' + +entity.node.webform.results_submissions: + path: '/node/{node}/webform/results/submissions' + defaults: + _entity_list: 'webform_submission' + _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.results_table: + path: '/node/{node}/webform/results/table' + defaults: + _entity_list: 'webform_submission' + _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.results_table.custom: + path: '/node/{node}/webform/results/table/custom' + defaults: + _form: 'Drupal\webform\Form\WebformResultsCustomForm' + _title: 'Customize table' + 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.results_export: + path: '/node/{node}/webform/results/download' + defaults: + _controller: '\Drupal\webform\Controller\WebformResultsExportController::index' + _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.results_export_file: + path: '/node/{node}/webform/results/download/file/{filename}' + defaults: + _controller: '\Drupal\webform\Controller\WebformResultsExportController::file' + _title_callback: 'Drupal\webform\Controller\WebformController::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.results_clear: + path: '/node/{node}/webform/results/clear' + defaults: + _form: 'Drupal\webform\Form\WebformResultsClearForm' + _title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' + operation: webform_submission_delete + entity_access: 'webform.submission_purge_any' + options: + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformResultsAccess' + +entity.node.webform_submission.canonical: + path: '/node/{node}/webform/submission/{webform_submission}' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::index' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + type: 'default' + operation: webform_submission_view + entity_access: 'webform_submission.view' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.table: + path: '/node/{node}/webform/submission/{webform_submission}/table' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::index' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + type: 'table' + operation: webform_submission_view + entity_access: 'webform_submission.view_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.text: + path: '/node/{node}/webform/submission/{webform_submission}/text' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::index' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + type: 'text' + operation: webform_submission_view + entity_access: 'webform_submission.view_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.yaml: + path: '/node/{node}/webform/submission/{webform_submission}/yaml' + defaults: + _controller: '\Drupal\webform\Controller\WebformSubmissionController::index' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + type: 'yaml' + operation: webform_submission_view + entity_access: 'webform_submission.view_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.edit_form: + path: '/node/{node}/webform/submission/{webform_submission}/edit' + defaults: + _entity_form: 'webform_submission.default' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_edit + entity_access: 'webform_submission.update' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.edit_form.all: + path: '/node/{node}/webform/submission/{webform_submission}/edit/all' + defaults: + _entity_form: 'webform_submission.default' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_edit + entity_access: 'webform_submission.update' + disable_pages: true + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.notes_form: + path: '/node/{node}/webform/submission/{webform_submission}/notes' + defaults: + _entity_form: 'webform_submission.notes' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_notes + entity_access: 'webform_submission.update_any' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.resend_form: + path: '/node/{node}/webform/submission/{webform_submission}/resend' + defaults: + _form: 'Drupal\webform\Form\WebformSubmissionResendForm' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_resend + entity_access: 'webform_submission.update_any' + resend: true + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.duplicate_form: + path: '/node/{node}/webform/submission/{webform_submission}/duplicate' + defaults: + _entity_form: 'webform_submission.duplicate' + _title_callback: 'Drupal\webform\Controller\WebformSubmissionController::title' + operation: webform_submission_duplicate + entity_access: 'webform_submission.update_any' + duplicate: TRUE + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' + +entity.node.webform_submission.delete_form: + path: '/node/{node}/webform/submission/{webform_submission}/delete' + defaults: + _entity_form: 'webform_submission.delete' + _title: 'Delete webform submission' + operation: webform_submission_delete + entity_access: 'webform_submission.delete' + options: + _admin_route: TRUE + parameters: + node: + type: 'entity:node' + requirements: + _custom_access: '\Drupal\webform_node\Access\WebformNodeAccess::checkWebformSubmissionAccess' diff --git a/web/modules/contrib/webform/modules/webform_node/webform_node.tokens.inc b/web/modules/contrib/webform/modules/webform_node/webform_node.tokens.inc new file mode 100644 index 000000000..4896ee843 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_node/webform_node.tokens.inc @@ -0,0 +1,64 @@ + t('Webform submissions'), + 'description' => t('Tokens related to webform submission.'), + 'needs-data' => 'webform_submission', + ]; + $webform_submission['node'] = [ + 'name' => t('Node'), + 'description' => t("The node that the webform was submitted from."), + 'type' => 'node', + ]; + + return [ + 'types' => $types, + 'tokens' => [ + 'webform_submission' => $webform_submission, + ], + ]; +} + +/** + * Implements hook_tokens(). + */ +function webform_node_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $token_service = \Drupal::token(); + + $replacements = []; + + if ($type == 'webform_submission' && !empty($data['webform_submission'])) { + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $data['webform_submission']; + $source_entity = $webform_submission->getSourceEntity(); + if (!$source_entity || (!$source_entity instanceof NodeInterface)) { + return $replacements; + } + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'node': + $replacements[$original] = $source_entity->label(); + break; + } + } + + if ($entity_tokens = $token_service->findWithPrefix($tokens, 'node')) { + $replacements += $token_service->generate('node', $entity_tokens, ['node' => $source_entity], $options, $bubbleable_metadata); + } + } + + return $replacements; +} diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml new file mode 100644 index 000000000..b7e01a9d8 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_contact.yml @@ -0,0 +1,157 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_contact +title: 'Contact Us' +description: 'A basic contact webform template.' +elements: | + name: + '#title': 'Your Name' + '#type': textfield + '#required': true + email: + '#title': 'Your Email' + '#type': email + '#required': true + subject: + '#title': Subject + '#type': textfield + '#required': true + message: + '#title': Message + '#type': textarea + '#required': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: 'Send message' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: url_message + confirmation_title: '' + confirmation_message: 'Your message has been sent.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_confirmation: + id: email + label: 'Email confirmation' + handler_id: email_confirmation + status: true + weight: 1 + settings: + to_mail: '[webform_submission:values:email:raw]' + cc_mail: '' + bcc_mail: '' + from_mail: default + from_name: default + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false + email_notification: + id: email + label: 'Email notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:name:value]' + subject: '[webform_submission:values:subject:value]' + body: '[webform_submission:values:message:value]' + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_donation.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_donation.yml new file mode 100644 index 000000000..9b89c9c23 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_donation.yml @@ -0,0 +1,192 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_donation +title: Donation +description: 'A donation webform template.' +elements: | + donation: + '#title': 'Donation Information' + '#type': fieldset + amount: + '#type': webform_buttons_other + '#title': Amount + '#title_display': invisible + '#required': true + '#options': + 25: $25 + 50: $50 + 100: $100 + 250: $250 + 500: $500 + '#other__option_label': Other + '#other__title': 'Please Enter Your Donation' + '#other__type': number + '#other__min': 1 + '#other__placeholder': '' + '#other__field_prefix': $ + '#other__field_suffix': '.00' + billing: + '#title': 'Billing Information' + '#type': fieldset + name: + '#type': webform_name + '#title': Name + '#title_display': invisible + '#required': true + '#first__required': true + '#last__required': true + '#suffix__access': false + '#degree__access': false + '#flexbox': '1' + address: + '#type': webform_address + '#title': Address + '#title_display': invisible + '#required': true + '#address__required': true + '#city__required': true + '#state_province__required': true + '#postal_code__required': true + '#country__required': true + '#flexbox': '1' + email: + '#type': pmail + '#title': Email + '#required': true + phone: + '#type': phone + '#title': Phone + payment: + '#title': 'Payment Information' + '#type': fieldset + credit_card: + '#type': webform_creditcard + '#title': 'Credit Card' + '#title_display': invisible + '#required': true + '#name__required': true + '#type__required': true + '#number__required': true + '#civ__required': true + '#expiration_month__required': true + '#expiration_year__required': true + '#flexbox': '1' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Donate + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: | +

          Thank you!!!

          +

          Your donation was processed successfully!

          + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml new file mode 100644 index 000000000..7dca201f9 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_employee_evaluation.yml @@ -0,0 +1,168 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_employee_evaluation +title: 'Employee Evaluation' +description: 'An employee evaluation webform template.' +elements: | + evaluator_information: + '#title': 'Your Information' + '#type': fieldset + evaluator_first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + evaluator_last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + evaluator_job_title: + '#type': textfield + '#title': 'Job Title' + '#required': true + evaluator_relationship: + '#type': webform_select_other + '#title': 'Relationship with employee' + '#options': + Coworker: Coworker + Supervisor: Supervisor + '#required': true + employee_information: + '#title': 'Employee Information' + '#type': fieldset + employee_first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + employee_last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + employee_job_title: + '#type': textfield + '#title': 'Job Title' + '#required': true + employee_ratings: + '#type': webform_likert + '#title': 'How would you rate the employee''s...' + '#questions': + attendance: Attendance + attire: Attire + professionalism: Professionalism + work_area: 'Work area' + ability: 'Ability to do the job' + work_with_others: 'Ability to work with others' + receive_feedback: 'Ability to receive feedback/criticism' + adaptabily: 'Ability to adapt' + learning: 'Willingness to learn' + participation: 'Willingness to participate' + work_ethic: 'Work ethic' + quality: 'Quality of work' + '#answers': likert_quality + '#required': true + employee_qualities: + '#type': textarea + '#title': 'What are the employee''s top qualities?' + '#required': true + employee_improve: + '#type': textarea + '#title': 'In what ways could the employee improve?' + '#required': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Apply + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for applying.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml new file mode 100644 index 000000000..b6ee1186b --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_issue.yml @@ -0,0 +1,213 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_issue +title: Issue +description: 'An issue webform template.' +elements: | + meta_data_details: + '#title': 'Issue Meta Data' + '#type': details + '#open': true + meta_data_01: + '#type': webform_flexbox + title: + '#type': textfield + '#title': Title + '#required': true + project: + '#type': select + '#title': Project + '#required': true + '#options': + 'Project I': 'Project I' + 'Project II': 'Project II' + 'Project III': 'Project III' + meta_data_02: + '#type': webform_flexbox + category: + '#type': select + '#title': Category + '#required': true + '#options': + 'Bug report': 'Bug report' + Task: Task + 'Feature request': 'Feature request' + 'Support request': 'Support request' + Plan: Plan + priority: + '#type': select + '#title': Priority + '#required': true + '#options': + 4: Critical + 3: Major + 2: Normal + 1: Minor + status: + '#type': webform_select_other + '#title': Status + '#required': true + '#options': + Active: Active + 'Needs work': 'Needs work' + 'Needs review': 'Needs review' + 'Reviewed & tested by the community': 'Reviewed & tested by the community' + 'Patch (to be ported)': 'Patch (to be ported)' + Fixed: Fixed + Postponed: Postponed + 'Postponed (maintainer needs more info)': 'Postponed (maintainer needs more info)' + 'Closed (duplicate)': 'Closed (duplicate)' + 'Closed (won''t fix)': 'Closed (won''t fix)' + 'Closed (works as designed)': 'Closed (works as designed)' + 'Closed (cannot reproduce)': 'Closed (cannot reproduce)' + 'Closed (outdated)': 'Closed (outdated)' + version: + '#type': webform_autocomplete + '#title': Version + '#required': true + '#autocomplete_existing': true + component: + '#type': webform_select_other + '#title': Component + '#required': true + '#options': + Code: Code + Documentation: Documentation + Miscellaneous: Miscellaneous + 'User interface': 'User interface' + assigned: + '#type': entity_autocomplete + '#title': Assigned + '#target_type': user + '#selection_handler': 'default:user' + '#selection_settings': + include_anonymous: false + meta_data_03: + '#type': webform_flexbox + tags: + '#type': entity_autocomplete + '#title': 'Issue Tags' + '#description': 'Do NOT use tags for adding random keywords or duplicating any other fields. Separate terms with a comma, not a space.' + '#tags': true + '#target_type': taxonomy_term + '#selection_handler': 'default:taxonomy_term' + '#selection_settings': + target_bundles: + tags: tags + auto_create: 1 + auto_create_bundle: tags + summary_and_relationshsips_details: + '#title': 'Issue Summary' + '#type': details + '#open': true + summary: + '#type': text_format + '#title': Summary + '#description': 'An issue summary is a concise overview of a full issue report. Issue summaries need to be written if the issue has more than a few comments and/or an average developer cannot understand the subject matter after a few minutes of study. These summaries are key sources of information for core developers, patch reviewers and users who need to skim large amounts of issues and information quickly.' + files_details: + '#title': Files + '#type': details + '#open': true + files: + '#type': managed_file + '#title': Files + '#multiple': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: false + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 1 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: { } diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml new file mode 100644 index 000000000..5d2953943 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_application.yml @@ -0,0 +1,187 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_job_application +title: 'Job Application' +description: 'A job application webform template.' +elements: | + information: + '#title': 'Your Information' + '#type': fieldset + first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + gender: + '#type': radios + '#title': Gender + '#options': gender + '#required': true + contact_information: + '#title': 'Contact Information' + '#type': fieldset + contact: + '#type': webform_contact + '#title': Contact + '#title_display': invisible + '#name__access': false + '#company__access': false + resume: + '#title': 'Your Resume' + '#type': fieldset + resume_method: + '#type': radios + '#options': + attach: 'Attach resume file' + paste: 'Paste your resume' + '#prefix': '
          ' + '#suffix': '
          ' + '#default_value': attach + resume_file: + '#type': managed_file + '#title': Resume + '#title_display': invisible + '#states': + visible: + ':input[name="resume_method"]': + value: attach + required: + ':input[name="resume_method"]': + value: attach + enabled: + ':input[name="resume_method"]': + value: attach + resume_text: + '#type': textarea + '#title': Resume + '#title_display': invisible + '#states': + visible: + ':input[name="resume_method"]': + value: paste + required: + ':input[name="resume_method"]': + value: paste + enabled: + ':input[name="resume_method"]': + value: paste +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Apply + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for applying.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml new file mode 100644 index 000000000..9fd3a5582 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_job_seeker_profile.yml @@ -0,0 +1,190 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_job_seeker_profile +title: 'Job Seeker Profile' +description: 'A job seeker profile webform template.' +elements: | + introduction: + '#markup': | +

          Creating a job seeker profile will help companies find you:

          +
            +
          • Upload a resume
          • +
          • Providing more information gives a better picture to employers
          • +
          • Salary requirements, location preferences and skill level are all important factors in the hiring decision
          • +
          + information: + '#title': 'Job Seeker Information' + '#type': fieldset + first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + address: + '#type': webform_address + '#required': true + current_title: + '#type': textfield + '#title': 'Current Job Title' + current_organization: + '#type': textfield + '#title': 'Current Place of Occupation' + positions: + '#type': textfield + '#title': 'Position you are looking for' + relocation: + '#type': checkbox + '#title': 'Willing to relocate' + salary: + '#title': 'Salary Minimum' + '#type': textfield + '#description': 'Add currency sign/currency code if desired. e.g. 500,000 USD, 500,000 CAD, ÂĽ 100,000' + skill: + '#title': 'Skill Level' + '#type': radios + '#options_display': two_columns + '#options': + Novice/Beginner: Novice/Beginner + Intermediate/Proficient: Intermediate/Proficient + Expert/Advanced: Expert/Advanced + categories: + '#title': 'Job Categories' + '#type': checkboxes + '#options_display': two_columns + '#options': + 'Account Manager': 'Account Manager' + Back-End: Back-End + Designer: Designer + DevOps: DevOps + Front-End: Front-End + 'Project Manager': 'Project Manager' + Sales: Sales + 'Site Builder': 'Site Builder' + Themer: Themer + resume: + '#type': managed_file + '#title': Resume + url: + '#type': url + '#title': Website + '#description': 'Enter your existing profile/resume/portfolio link.' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for applying.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml new file mode 100644 index 000000000..01cdf93de --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_registration.yml @@ -0,0 +1,156 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_registration +title: Registration +description: 'A registration webform template.' +elements: | + personal_information: + '#title': 'Your Personal Information' + '#type': fieldset + first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + contact_information: + '#title': 'Your Contact Information' + '#type': fieldset + contact: + '#type': webform_contact + '#title': Contact + '#title_display': invisible + '#name__access': false + '#company__access': false + mailinglist: + '#title': 'Mailing List' + '#type': fieldset + subscribe: + '#title': 'Please subscribe me to your mailing list.' + '#type': checkbox + additional_information: + '#title': 'Additional Information' + '#type': fieldset + '#open': true + notes: + '#title': Comments + '#type': textarea +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Register + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for registering.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml new file mode 100644 index 000000000..716551b87 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_session_evaluation.yml @@ -0,0 +1,152 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_session_evaluation +title: 'Session Evaluation' +description: 'A session evaluation webform template.' +elements: | + overall: + '#type': radios + '#title': 'How was the session overall?' + '#options_display': side_by_side + '#options': likert_quality + '#required': true + speaker: + '#type': webform_likert + '#title': 'Please rate the speaker''s...' + '#questions': + mastery: 'Mastery of this topic' + presentation: 'Presentation skills' + quality: 'Quality of slides and visual aids' + '#answers': likert_quality + '#na_answer': true + '#required': true + learn: + '#type': radios + '#title': 'Did you learn something in this session you can use in real life?' + '#options_display': side_by_side + '#options': + 'Yes': 'Yes' + 'No': 'No' + N/A: N/A + '#required': true + comments: + '#type': textarea + '#title': Comments + '#description': 'What did you like most? What would you change? Advice for the speaker to make this session better?' +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: '' + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: false + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 1 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for your feedback.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml new file mode 100644 index 000000000..5f8282d3a --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_subscribe.yml @@ -0,0 +1,135 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_subscribe +title: Subscribe +description: 'A subscribe to mailing list webform template.' +elements: | + first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + last_name: + '#title': 'Last Name' + '#type': textfield + '#required': true + email: + '#title': Email + '#type': email + '#required': true +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Subscribe + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + draft_saved_message: '' + draft_loaded_message: '' + confirmation_type: page + confirmation_title: '' + confirmation_message: 'Thank you for subscribing.' + confirmation_url: '' + confirmation_attributes: { } + confirmation_back: true + confirmation_back_label: '' + confirmation_back_attributes: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml new file mode 100644 index 000000000..001248507 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/config/install/webform.webform.template_user_profile.yml @@ -0,0 +1,229 @@ +langcode: en +status: closed +dependencies: + enforced: + module: + - webform_templates +open: null +close: null +uid: null +template: true +id: template_user_profile +title: 'User Profile' +description: 'A user profile webform template.' +elements: | + account_information: + '#title': 'Your Account Information' + '#type': fieldset + user_name: + '#type': textfield + '#title': 'User Name' + picture: + '#type': managed_file + '#title': Picture + '#description': 'Your virtual face or picture. Pictures larger than 1024x1024 pixels will be scaled down.' + '#max_filesize': '2' + '#file_extensions': 'gif jpg png svg' + personal_information: + '#title': 'Your Personal Information' + '#type': fieldset + first_name: + '#title': 'First Name' + '#type': textfield + '#required': true + last_name: + '#type': textfield + '#title': 'Last Name' + '#required': true + country: + '#type': select + '#title': Country + '#options': country_names + location: + '#type': webform_location + '#title': Location + '#title_display': invisible + '#description': 'Your location will be saved and may be shared.' + '#geolocation': true + '#address__title': Locations + languages: + '#type': select + '#title': 'Languages Spoken' + '#description': 'Select one or more languages you speak.' + '#multiple': true + '#select2': true + '#options': languages + gender: + '#type': radios + '#title': Gender + '#options': gender + biography: + '#type': text_format + '#title': Biography + '#description': 'Please include a short blurb about yourself to let us know who you are outside of Drupal.' + website: + '#type': url + '#title': Website + irc: + '#type': textfield + '#title': IRC + '#description': 'The nickname you use on various channels of irc.freenode.net' + twitter: + '#type': textfield + '#title': Twitter + '#description': 'Your Twitter handle.' + github: + '#type': textfield + '#title': GitHub + '#description': 'Your GitHub user name.' + work_information: + '#title': 'Your Work Information' + '#type': fieldset + current_organization: + '#type': textfield + '#title': 'Current Organization' + current_title: + '#type': textfield + '#title': 'Current Job Title' + organizations: + '#type': textarea + '#title': 'Past Organizations' + '#description': 'List companies or institutions you have worked for.' + industries: + '#type': webform_select_other + '#title': 'Industries Worked In' + '#options': industry + '#multiple': true + '#select2': true + email_settings: + '#title': 'E-mail addresses' + '#type': fieldset + email: + '#type': email + '#title': 'Primary E-mall Address' + '#description': 'Enter your primary e-mail addresses, which will be used for all e-mail communications.' + emails: + '#type': webform_email_multiple + '#title': 'Secondary E-mall Addresses' + '#description': 'Enter multiple e-mail addresses separated by commas.' + regional_settings: + '#title': 'Regional Settings' + '#type': fieldset + time_zone: + '#type': select + '#title': Timezone + '#options': time_zones + language: + '#type': select + '#title': 'Preferred Language' + '#description': 'This account''s default language for e-mails, and preferred language for site presentation.' + '#options': languages +css: '' +javascript: '' +settings: + page: true + page_submit_path: '' + page_confirm_path: '' + form_submit_label: Register + form_submit_once: false + form_submit_attributes: { } + form_exception_message: '' + form_closed_message: '' + form_previous_submissions: true + form_confidential: false + form_confidential_message: '' + form_prepopulate: false + form_prepopulate_source_entity: false + form_disable_autocomplete: false + form_novalidate: false + form_unsaved: false + form_disable_back: false + form_autofocus: false + form_details_toggle: false + wizard_progress_bar: true + wizard_progress_pages: false + wizard_progress_percentage: false + wizard_next_button_label: '' + wizard_next_button_attributes: { } + wizard_prev_button_label: '' + wizard_prev_button_attributes: { } + wizard_start_label: '' + wizard_complete: true + wizard_complete_label: '' + preview: 0 + preview_next_button_label: '' + preview_next_button_attributes: { } + preview_prev_button_label: '' + preview_prev_button_attributes: { } + preview_message: '' + draft: false + draft_auto_save: false + draft_button_label: '' + draft_button_attributes: { } + 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: { } + limit_total: null + limit_total_message: '' + limit_user: null + limit_user_message: '' + purge: none + purge_days: null + entity_limit_total: null + entity_limit_user: null + results_disabled: false + results_disabled_ignore: false + token_update: false +access: + create: + roles: + - anonymous + - authenticated + users: { } + view_any: + roles: { } + users: { } + update_any: + roles: { } + users: { } + delete_any: + roles: { } + users: { } + purge_any: + roles: { } + users: { } + view_own: + roles: { } + users: { } + update_own: + roles: { } + users: { } + delete_own: + roles: { } + users: { } +handlers: + email_notification: + id: email + label: 'Email Notification' + handler_id: email_notification + status: true + weight: 1 + settings: + to_mail: default + cc_mail: '' + bcc_mail: '' + from_mail: '[webform_submission:values:email:raw]' + from_name: '[webform_submission:values:first_name] [webform_submission:values:last_name]' + subject: default + body: default + excluded_elements: { } + html: true + attachments: false + debug: false diff --git a/web/modules/contrib/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php b/web/modules/contrib/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php new file mode 100644 index 000000000..ad8b72b53 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/src/Controller/WebformTemplatesController.php @@ -0,0 +1,235 @@ +currentUser = $current_user; + $this->formBuilder = $form_builder; + $this->webformStorage = $entity_type_manager->getStorage('webform'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('current_user'), + $container->get('form_builder'), + $container->get('entity_type.manager') + ); + } + + /** + * Returns the webform templates index page. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @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) { + $keys = $request->get('search'); + + // Handler autocomplete redirect. + if ($keys && preg_match('#\(([^)]+)\)$#', $keys, $match)) { + if ($webform = $this->webformStorage->load($match[1])) { + return new RedirectResponse($webform->toUrl()->setAbsolute(TRUE)->toString()); + } + } + + $header = [ + $this->t('Title'), + ['data' => $this->t('Description'), 'class' => [RESPONSIVE_PRIORITY_LOW]], + ['data' => $this->t('Operations'), 'colspan' => 2], + ]; + + $webforms = $this->getTemplates($keys); + $rows = []; + foreach ($webforms as $webform) { + $route_parameters = ['webform' => $webform->id()]; + + $row['title'] = $webform->toLink(); + $row['description']['data']['description']['#markup'] = $webform->get('description'); + if ($this->currentUser->hasPermission('create webform')) { + $row['select']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'duplicate' => [ + 'title' => $this->t('Select'), + 'url' => Url::fromRoute('entity.webform.duplicate_form', $route_parameters), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(640), + ], + ], + ]; + } + $row['preview']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'preview' => [ + 'title' => $this->t('Preview'), + 'url' => Url::fromRoute('entity.webform.preview', $route_parameters), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(800), + ], + ], + ]; + $rows[] = $row; + } + + $build = []; + $build['filter_form'] = $this->formBuilder->getForm('\Drupal\webform_templates\Form\WebformTemplatesFilterForm', $keys); + $build['table'] = [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => $this->t('There are no templates available.'), + '#cache' => [ + 'contexts' => $this->webformStorage->getEntityType()->getListCacheContexts(), + 'tags' => $this->webformStorage->getEntityType()->getListCacheTags(), + ], + ]; + + // Must preload libraries required by (modal) dialogs. + $build['#attached']['library'][] = 'webform/webform.admin.dialog'; + + return $build; + } + + /** + * Returns a webform to add a new submission to a webform. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface $webform + * The webform this submission will be added to. + * + * @return array|NotFoundHttpException + * The webform submission webform. + */ + public function previewForm(Request $request, WebformInterface $webform) { + if (!$webform->isTemplate()) { + return new NotFoundHttpException(); + } + + return $webform->getSubmissionForm([], 'preview'); + } + + /** + * Get webform templates. + * + * @param string $keys + * (optional) Filter templates by key word. + * + * @return array|\Drupal\Core\Entity\EntityInterface[] + * An array webform entity that are used as templates. + */ + protected function getTemplates($keys = '') { + $query = $this->webformStorage->getQuery(); + $query->condition('template', TRUE); + // Filter by key(word). + if ($keys) { + $or = $query->orConditionGroup() + ->condition('title', $keys, 'CONTAINS') + ->condition('description', $keys, 'CONTAINS') + ->condition('elements', $keys, 'CONTAINS'); + $query->condition($or); + } + + $query->sort('title'); + + $entity_ids = $query->execute(); + if (empty($entity_ids)) { + return []; + } + + /* @var $entities \Drupal\webform\WebformInterface[] */ + $entities = $this->webformStorage->loadMultiple($entity_ids); + + // If the user is not a webform admin, check view access to each webform. + if (!$this->isAdmin()) { + foreach ($entities as $entity_id => $entity) { + if (!$entity->access('view')) { + unset($entities[$entity_id]); + } + } + } + + return $entities; + + } + + /** + * Route preview title callback. + * + * @param \Drupal\webform\WebformInterface|null $webform + * A webform. + * + * @return string + * The webform label. + */ + public function previewTitle(WebformInterface $webform = NULL) { + return $this->t('Previewing @title template', ['@title' => $webform->label()]); + } + + /** + * Is the current user a webform administrator. + * + * @return bool + * TRUE if the current user has 'administer webform' or 'edit any webform' + * permission. + */ + protected function isAdmin() { + return ($this->currentUser->hasPermission('administer webform') || $this->currentUser->hasPermission('edit any webform')); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php b/web/modules/contrib/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php new file mode 100644 index 000000000..57602005d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/src/Form/WebformTemplatesFilterForm.php @@ -0,0 +1,80 @@ + ['webform-filter-form']]; + $form['filter'] = [ + '#type' => 'details', + '#title' => $this->t('Filter templates'), + '#open' => TRUE, + '#attributes' => ['class' => ['container-inline']], + ]; + $form['filter']['search'] = [ + '#type' => 'search', + '#title' => $this->t('Filter by title, description, or elements'), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Filter by title, description, or elements'), + '#maxlength' => 128, + '#size' => 40, + '#autocomplete_route_name' => 'entity.webform.templates.autocomplete', + '#default_value' => $search, + ]; + $form['filter']['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Filter'), + ]; + if (!empty($search)) { + $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')), + ]; + $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/contrib/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php b/web/modules/contrib/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php new file mode 100644 index 000000000..59af9c3eb --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/src/Tests/WebformTemplatesTest.php @@ -0,0 +1,68 @@ +createUsers(); + } + + /** + * Tests webform template setting. + */ + public function testSettings() { + $template_webform = Webform::load('test_form_template'); + + // Check the templates always will remain closed. + $this->assertTrue($template_webform->isClosed()); + $template_webform->setStatus(WebformInterface::STATUS_OPEN)->save(); + $this->assertTrue($template_webform->isClosed()); + + // Login the own user. + $this->drupalLogin($this->ownWebformUser); + + // Check template is included in the 'Templates' list display. + $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->assertResponse(200); + $this->assertRaw('You are previewing the below template,'); + + // Login the admin user. + $this->drupalLogin($this->adminWebformUser); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_templates/src/WebformTemplatesSubmissionPreviewForm.php b/web/modules/contrib/webform/modules/webform_templates/src/WebformTemplatesSubmissionPreviewForm.php new file mode 100644 index 000000000..c9bfbd19d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/src/WebformTemplatesSubmissionPreviewForm.php @@ -0,0 +1,68 @@ +isModalDialog()) { + // Disable validation. + $form['#attributes']['novalidate'] = 'novalidate'; + + // Display webform title in modal. + $form['title'] = [ + '#markup' => $this->getWebform()->label(), + '#prefix' => '

          ', + '#suffix' => '

          ', + '#weight' => -101, + ]; + + // Remove type from 'actions' and add modal 'actions'. + unset($form['actions']['#type']); + $form['modal_actions'] = ['#type' => 'actions']; + $form['modal_actions']['select'] = [ + '#type' => 'link', + '#title' => $this->t('Select'), + '#url' => Url::fromRoute('entity.webform.duplicate_form', ['webform' => $this->getWebform()->id()]), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(640, ['button', 'button--primary']), + ]; + $form['modal_actions']['close'] = [ + '#type' => 'submit', + '#value' => $this->t('Close'), + '#ajax' => [ + 'callback' => '::closeDialog', + 'event' => 'click', + ], + ]; + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + if ($this->isModalDialog()) { + $form_state->clearErrors(); + } + else { + parent::validateForm($form, $form_state); + } + } + +} diff --git a/web/modules/contrib/webform/modules/webform_templates/webform_templates.info.yml b/web/modules/contrib/webform/modules/webform_templates/webform_templates.info.yml new file mode 100644 index 000000000..83086056b --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/webform_templates.info.yml @@ -0,0 +1,13 @@ +name: 'Webform Templates' +type: module +description: 'Provides starter templates that can be used create new webforms.' +package: Webform +# core: 8.x +dependencies: + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_templates/webform_templates.links.task.yml b/web/modules/contrib/webform/modules/webform_templates/webform_templates.links.task.yml new file mode 100644 index 000000000..f0cbe9e41 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/webform_templates.links.task.yml @@ -0,0 +1,5 @@ +entity.webform.templates: + title: 'Templates' + route_name: entity.webform.templates + base_route: entity.webform.collection + weight: -9 diff --git a/web/modules/contrib/webform/modules/webform_templates/webform_templates.module b/web/modules/contrib/webform/modules/webform_templates/webform_templates.module new file mode 100644 index 000000000..cc5e3b307 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/webform_templates.module @@ -0,0 +1,19 @@ +getHandlerClasses(); + $handlers['form']['preview'] = 'Drupal\webform_templates\WebformTemplatesSubmissionPreviewForm'; + $webform_submission_entity_type->setHandlerClass('form', $handlers['form']); + } +} diff --git a/web/modules/contrib/webform/modules/webform_templates/webform_templates.routing.yml b/web/modules/contrib/webform/modules/webform_templates/webform_templates.routing.yml new file mode 100644 index 000000000..3cc25dc6a --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_templates/webform_templates.routing.yml @@ -0,0 +1,23 @@ +entity.webform.templates: + path: '/admin/structure/webform/templates' + defaults: + _controller: '\Drupal\webform_templates\Controller\WebformTemplatesController::index' + _title: 'Webforms' + requirements: + _custom_access: '\Drupal\webform\Access\WebformAccess::checkOverviewAccess' + +entity.webform.preview: + path: '/webform/{webform}/preview' + defaults: + _controller: '\Drupal\webform_templates\Controller\WebformTemplatesController::previewForm' + _title_callback: '\Drupal\webform_templates\Controller\WebformTemplatesController::previewtitle' + requirements: + _entity_access: 'webform.submission_page' + +entity.webform.templates.autocomplete: + path: '/admin/structure/webform/templates/autocomplete' + defaults: + _controller: '\Drupal\webform\Controller\WebformController::autocomplete' + templates: TRUE + requirements: + _custom_access: '\Drupal\webform\Access\WebformAccess::checkOverviewAccess' diff --git a/web/modules/contrib/webform/modules/webform_ui/css/webform_ui.module.css b/web/modules/contrib/webform/modules/webform_ui/css/webform_ui.module.css new file mode 100644 index 000000000..f8f702a08 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/css/webform_ui.module.css @@ -0,0 +1,61 @@ +/** + * @file + * Webform UI styling + */ + +/** + * Local actions. + */ +.webform-ui-local-actions .button, +.webform-ui-local-actions .button:first-child { + margin-left: 0; + margin-right: 1em; +} + +/** + * Tables + */ +table .ajax-progress-throbber { + display: none; +} + +th.webform-ui-element-operations { + width: 140px; +} + +thead th .dropbutton { + text-transform: none; +} + +/** + * Elements (/admin/structure/webform/manage/{webform}) + */ +.webform-ui-elements-table .button { + white-space: nowrap; +} + +.webform-ui-elements-table .form-type-checkbox { + text-align: center; +} + +.webform-ui-elements-table .webform-ui-element-required { + width: 10%; +} + +.webform-ui-elements-table tr:first-child { + border-top: inherit !important; +} +.webform-ui-elements-table tr.webform-ui-element-type-webform_wizard_page { + border-top: 2px solid #a6a6a6; +} + +.webform-ui-elements-table tr.webform-ui-element-type-webform_flexbox { + border-top: 2px dotted #a6a6a6; + font-weight: bold; +} + +.webform-ui-elements-table tr.webform-ui-element-type-details, +.webform-ui-elements-table tr.webform-ui-element-type-fieldset { + border-top: 2px dashed #a6a6a6; + font-weight: bold; +} diff --git a/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.element.js b/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.element.js new file mode 100644 index 000000000..8c9fd6be4 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.element.js @@ -0,0 +1,42 @@ +/** + * @file + * Javascript behaviors for webform UI element. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Monitor the element's key (aka machine name). + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.webformUiElementKey = { + attach: function (context) { + if (!$(context).find(':input[name="key"]').length) { + return; + } + + // Monitor the machine name and display a warning when a reserved word is + // being used. + // There is no way to capture changes to the key val. + // @see core/misc/machine-name.js. + setInterval(function () { + var value = $(':input[name="key"]').val(); + if ($.inArray(value, drupalSettings.webform_ui.reserved_keys) !== -1) { + // Customize and display the warning message. + $('[data-drupal-selector="edit-key-warning"]').html( + Drupal.t("Please avoid using the reserved word '@key' as the element's key.", {'@key': value}) + ).show(); + } + else { + // Hide the warning message. + $('[data-drupal-selector="edit-key-warning"]').hide(); + } + }, 300); + + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.js b/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.js new file mode 100644 index 000000000..58a473038 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/js/webform_ui.js @@ -0,0 +1,46 @@ +/** + * @file + * Javascript behaviors for Webform UI. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Highlights the element that was just updated. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the element update. + * + * @see Drupal.behaviors.blockHighlightPlacement + */ + Drupal.behaviors.webformUiElementsUpdate = { + attach: function (context, settings) { + if (settings.webformUiElementUpdate) { + $(context).find('[data-drupal-selector="edit-webform-ui-elements"]').once('webform-ui-elements-update').each(function () { + var $container = $(this); + + // If the element is visible, don't scroll to it. + // @see http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling; + var $element = $('.js-webform-ui-element-update'); + var elementTop = $element.offset().top; + var elementBottom = elementTop + $element.height(); + var isVisible = (elementTop >= 0) && (elementBottom <= window.innerHeight); + if (isVisible) { + return; + } + + // Just scrolling the document.body will not work in Firefox. The html + // element is needed as well. + $('html, body').animate({ + scrollTop: $('.js-webform-ui-element-update').offset().top - $container.offset().top + $container.scrollTop() + }, 500); + }); + } + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Access/WebformUiAccess.php b/web/modules/contrib/webform/modules/webform_ui/src/Access/WebformUiAccess.php new file mode 100644 index 000000000..d4d774d86 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Access/WebformUiAccess.php @@ -0,0 +1,60 @@ +access('update', $account) && $account->hasPermission('edit webform source')); + } + + /** + * Check that webform option source can be updated by a user. + * + * @param \Drupal\webform\WebformOptionsInterface $webform_options + * A webform options entity. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * 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')); + } + + /** + * Check that webform can be updated by a user. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkWebformEditAccess(WebformInterface $webform, AccountInterface $account) { + return AccessResult::allowedIf($webform->access('update', $account)); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php new file mode 100644 index 000000000..00df956db --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementAddForm.php @@ -0,0 +1,37 @@ +webform = $webform; + $parent_key = $this->getRequest()->get('parent'); + + if ($parent_key) { + $parent_element = $webform->getElementDecoded($parent_key); + if (!$parent_element) { + throw new NotFoundHttpException(); + } + } + + $this->element['#type'] = $type; + $this->action = $this->t('created'); + $form = parent::buildForm($form, $form_state, $webform, NULL, $parent_key); + if (isset($form['properties']['element']['title'])) { + $form['properties']['element']['title']['#attributes']['autofocus'] = 'autofocus'; + } + return $form; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php new file mode 100644 index 000000000..722f4558c --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDeleteForm.php @@ -0,0 +1,222 @@ +renderer = $renderer; + $this->elementsValidator = $elements_validator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('renderer'), + $container->get('webform.elements_validator') + ); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + $t_args = [ + '%element' => $this->getElementTitle(), + '%webform' => $this->webform->label(), + ]; + + $build = []; + if ($this->webformElement->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), + ]; + } + else { + $build['warning'] = [ + '#markup' => $this->t('This will immediately delete the %element element from the %webform webform. This cannot be undone.', $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->render($build); + } + + /** + * 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, + ]; + } + + /** + * {@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()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return $this->webform->toUrl('edit-form'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_ui_element_delete_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL, $key = NULL) { + $this->webform = $webform; + $this->key = $key; + $this->element = $webform->getElement($key); + + if ($this->element === NULL) { + throw new NotFoundHttpException(); + } + + /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + $plugin_id = $element_manager->getElementPluginId($this->element); + $this->webformElement = $element_manager->createInstance($plugin_id, $this->element); + + $form = parent::buildForm($form, $form_state); + $form = $this->buildConfirmFormDialog($form, $form_state); + return $form; + } + + /** + * {@inheritdoc} + */ + 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()])); + $form_state->setRedirectUrl($this->webform->toUrl('edit-form')); + } + + /** + * Get the webform element's title or key. + * + * @return string + * The webform element's title or key, + */ + protected function getElementTitle() { + return (!empty($this->element['#title'])) ? $this->element['#title'] : $this->key; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php new file mode 100644 index 000000000..d927d59dc --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementDuplicateForm.php @@ -0,0 +1,37 @@ +element = $webform->getElementDecoded($key); + if ($this->element === NULL) { + throw new NotFoundHttpException(); + } + + $element_initialized = $webform->getElement($key); + + $form['#title'] = $this->t('Duplicate @title element', [ + '@title' => (!empty($this->element['#title'])) ? $this->element['#title'] : $key, + ]); + + $this->action = $this->t('created'); + return parent::buildForm($form, $form_state, $webform, NULL, $element_initialized['#webform_parent_key']); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php new file mode 100644 index 000000000..b0bc2d601 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementEditForm.php @@ -0,0 +1,46 @@ +element = $webform->getElementDecoded($key); + if ($this->element === NULL) { + throw new NotFoundHttpException(); + } + + // Handler changing element type. + if ($type = $this->getRequest()->get('type')) { + $webform_element = $this->getWebformElement(); + $related_types = $webform_element->getRelatedTypes($this->element); + if (!isset($related_types[$type])) { + throw new NotFoundHttpException(); + } + $this->originalType = $this->element['#type']; + $this->element['#type'] = $type; + } + + // Issue: #title is display as modal dialog's title and can't be escaped. + // Workaround: Filter and define @title as safe markup. + $form['#title'] = $this->t('Edit @title element', [ + '@title' => (!empty($this->element['#title'])) ? new FormattableMarkup(Xss::filterAdmin($this->element['#title']), []) : $key, + ]); + + $this->action = $this->t('updated'); + return parent::buildForm($form, $form_state, $webform, $key); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php new file mode 100644 index 000000000..676746fc8 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormBase.php @@ -0,0 +1,433 @@ +renderer = $renderer; + $this->entityFieldManager = $entity_field_manager; + $this->elementManager = $element_manager; + $this->elementsValidator = $elements_validator; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('renderer'), + $container->get('entity_field.manager'), + $container->get('plugin.manager.webform.element'), + $container->get('webform.elements_validator') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL, $key = NULL, $parent_key = '') { + $this->webform = $webform; + $this->key = $key; + $this->parent_key = $parent_key; + + $webform_element = $this->getWebformElement(); + + $form['properties'] = $webform_element->buildConfigurationForm([], $form_state); + + // Move messages to the top of the webform. + if (isset($form['properties']['messages'])) { + $form['messages'] = $form['properties']['messages']; + $form['messages']['#weight'] = -100; + unset($form['properties']['messages']); + } + + // Set parent key. + $form['parent_key'] = [ + '#type' => 'value', + '#value' => $parent_key, + ]; + + // Set element type. + $form['properties']['element']['type'] = [ + '#type' => 'item', + '#title' => $this->t('Type'), + 'label' => [ + '#markup' => $webform_element->getPluginLabel(), + ], + '#weight' => -100, + '#parents' => ['type'], + ]; + + // Set change element type. + if ($key && $webform_element->getRelatedTypes($this->element)) { + $route_parameters = ['webform' => $webform->id(), 'key' => $key]; + if ($this->originalType) { + $original_webform_element = $this->elementManager->createInstance($this->originalType); + $route_parameters = ['webform' => $webform->id(), 'key' => $key]; + $form['properties']['element']['type']['cancel'] = [ + '#type' => 'link', + '#title' => $this->t('Cancel'), + '#url' => new Url('entity.webform_ui.element.edit_form', $route_parameters), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['button', 'button--small']), + ]; + $form['properties']['element']['type']['#description'] = '(' . $this->t('Changing from %type', ['%type' => $original_webform_element->getPluginLabel()]) . ')'; + } + else { + $form['properties']['element']['type']['change_type'] = [ + '#type' => 'link', + '#title' => $this->t('Change'), + '#url' => new Url('entity.webform_ui.change_element', $route_parameters), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['button', 'button--small']), + ]; + } + } + + // Set element key reserved word warning message. + if (!$key) { + $reserved_keys = ['form_build_id', 'form_token', 'form_id', 'data', 'op']; + $reserved_keys = array_merge($reserved_keys, array_keys($this->entityFieldManager->getBaseFieldDefinitions('webform_submission'))); + $form['#attached']['drupalSettings']['webform_ui']['reserved_keys'] = $reserved_keys; + $form['#attached']['library'][] = 'webform_ui/webform_ui.element'; + $form['properties']['element']['key_warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t("Please avoid using the reserved word '@key' as the element's key."), + '#weight' => -99, + '#attributes' => ['style' => 'display:none'], + ]; + } + + // Set element key. + $form['properties']['element']['key'] = [ + '#type' => 'machine_name', + '#title' => $this->t('Key'), + '#machine_name' => [ + 'label' => $this->t('Key'), + 'exists' => [$this, 'exists'], + 'source' => ['title'], + ], + '#required' => TRUE, + '#parents' => ['key'], + '#disabled' => ($key) ? TRUE : FALSE, + '#default_value' => $key, + '#weight' => -98, + ]; + // Remove the key's help text (aka description) once it has been set. + if ($key) { + $form['properties']['element']['key']['#description'] = NULL; + } + // Use title for key (machine_name). + if (isset($form['properties']['element']['title'])) { + $form['properties']['element']['key']['#machine_name']['source'] = ['properties', 'element', 'title']; + $form['properties']['element']['title']['#id'] = 'title'; + } + + // Set flex. + // Hide #flex property if parent element is not a 'webform_flexbox'. + if (isset($form['properties']['flex']) && !$this->isParentElementFlexbox($key, $parent_key)) { + $form['properties']['flex']['#access'] = FALSE; + } + + // Set actions. + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#button_type' => 'primary', + '#_validate_form' => TRUE, + ]; + + $form = $this->buildFormDialog($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // Only validate the submit button. + $button = $form_state->getTriggeringElement(); + if (empty($button['#_validate_form'])) { + return; + } + + // The webform element configuration is stored in the 'properties' key in + // the webform, pass that through for validation. + $element_form_state = clone $form_state; + $element_form_state->setValues($form_state->getValue('properties')); + + // Validate configuration webform. + $webform_element = $this->getWebformElement(); + $webform_element->validateConfigurationForm($form, $element_form_state); + + // Get errors for element validation. + $element_errors = $element_form_state->getErrors(); + foreach ($element_errors as $element_error) { + $form_state->setErrorByName(NULL, $element_error); + } + + // Stop validation is the element properties has any errors. + if ($form_state->hasAnyErrors()) { + return; + } + + // Set element properties. + $properties = $webform_element->getConfigurationFormProperties($form, $element_form_state); + $parent_key = $form_state->getValue('parent_key'); + $key = $form_state->getValue('key'); + if ($key) { + $this->webform->setElementProperties($key, $properties, $parent_key); + + // Validate elements. + if ($messages = $this->elementsValidator->validate($this->webform)) { + $t_args = [':href' => Url::fromRoute('entity.webform.source_form', ['webform' => $this->webform->id()])->toString()]; + $form_state->setErrorByName('elements', $this->t('There has been error validating the elements. You may need to edit the YAML source to resolve the issue.', $t_args)); + foreach ($messages as $message) { + drupal_set_message($message, 'error'); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $webform_element = $this->getWebformElement(); + + if ($response = $this->validateDialog($form, $form_state)) { + return $response; + } + + // The webform element configuration is stored in the 'properties' key in + // the webform, pass that through for submission. + $element_form_state = clone $form_state; + $element_form_state->setValues($form_state->getValue('properties')); + + // Submit element configuration. + // Generally, elements will not be processing any submitted properties. + // It is possible that a custom element might need to call a third-party API + // to 'register' the element. + $webform_element->submitConfigurationForm($form, $element_form_state); + + // Save the webform with its updated element. + $this->webform->save(); + + // Display status message. + $properties = $form_state->getValue('properties'); + $t_args = [ + '%title' => (!empty($properties['title'])) ? $properties['title'] : $form_state->getValue('key'), + '@action' => $this->action, + ]; + drupal_set_message($this->t('%title has been @action.', $t_args)); + + // Redirect. + return $this->redirectForm($form, $form_state, $this->webform->toUrl('edit-form', ['query' => ['element-update' => $form_state->getValue('key')]])); + } + + /** + * Determines if the webform element key already exists. + * + * @param string $key + * The webform element key. + * + * @return bool + * TRUE if the webform element key, FALSE otherwise. + */ + public function exists($key) { + $elements = $this->webform->getElementsInitializedAndFlattened(); + return (isset($elements[$key])) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function isNew() { + return ($this instanceof WebformUiElementAddForm) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function getWebform() { + return $this->webform; + } + + /** + * {@inheritdoc} + */ + public function getWebformElement() { + return $this->elementManager->getElementInstance($this->element); + } + + /** + * {@inheritdoc} + */ + public function getElement() { + return $this->element; + } + + /** + * {@inheritdoc} + */ + public function getKey() { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function getParentKey() { + return $this->parent_key; + } + + /** + * Determine if the parent element is a 'webform_flexbox'. + * + * @param string|null $key + * The element's key. Only applicable for existing elements. + * @param string|null $parent_key + * The element's parent key. Only applicable for new elements. + * Parent key is set via query string parameter. (?parent={parent_key}) + * + * @return bool + * TRUE if the parent element is a 'webform_flexbox'. + */ + protected function isParentElementFlexbox($key = NULL, $parent_key = NULL) { + $elements = $this->webform->getElementsInitializedAndFlattened(); + + // Check the element #webform_parent_flexbox property. + if ($key && isset($elements[$key])) { + return $elements[$key]['#webform_parent_flexbox']; + } + + // Check the parent element #type. + if ($parent_key && isset($elements[$parent_key]) && isset($elements[$parent_key]['#type'])) { + return ($elements[$parent_key]['#type'] == 'webform_flexbox') ? TRUE : FALSE; + } + + return FALSE; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormInterface.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormInterface.php new file mode 100644 index 000000000..081d86339 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementFormInterface.php @@ -0,0 +1,61 @@ +webform = Webform::create(['id' => 'webform_ui_element_test_form']); + + $this->type = $type; + + if (!$this->elementManager->hasDefinition($type)) { + throw new NotFoundHttpException(); + } + + if ($test_element = \Drupal::request()->getSession()->get('webform_ui_test_element_' . $type)) { + $this->element = $test_element; + } + elseif (function_exists('_webform_test_get_example_element') && ($test_element = _webform_test_get_example_element($type))) { + $this->element = $test_element; + } + $this->element['#type'] = $type; + + $this->webformElement = $this->elementManager->getElementInstance($this->element); + + $form['#title'] = $this->t('Test %type element', ['%type' => $type]); + + if ($test_element) { + $webform_submission = WebformSubmission::create(['webform' => $this->webform]); + $this->webformElement->initialize($test_element); + $this->webformElement->initialize($this->element); + $this->webformElement->prepare($this->element, $webform_submission); + + $form['test'] = [ + '#type' => 'details', + '#title' => $this->t('Element test'), + '#open' => TRUE, + '#attributes' => [ + 'style' => 'background-color: #f5f5f2', + ], + 'element' => $this->element, + 'hr' => ['#markup' => '
          '], + ]; + + if (isset($test_element['#default_value'])) { + $html = $this->webformElement->formatHtml($test_element, $test_element['#default_value']); + $form['test']['html'] = [ + '#type' => 'item', + '#title' => $this->t('HTML'), + '#markup' => (is_array($html)) ? $this->renderer->render($html) : $html, + '#allowed_tag' => Xss::getAdminTagList(), + ]; + $form['test']['text'] = [ + '#type' => 'item', + '#title' => $this->t('Plain text'), + '#markup' => '
          ' . $this->webformElement->formatText($test_element, $test_element['#default_value']) . '
          ', + '#allowed_tag' => Xss::getAdminTagList(), + ]; + } + + $form['test']['code'] = [ + '#type' => 'item', + '#title' => $this->t('Source'), + 'source' => [ + '#theme' => 'webform_codemirror', + '#type' => 'yaml', + '#code' => Yaml::encode($this->convertTranslatableMarkupToStringRecursive($test_element)), + ], + ]; + + $form['test']['render_array'] = [ + '#type' => 'details', + '#title' => $this->t('Render array'), + '#desciption' => $this->t("Below is the element's final render array."), + 'source' => [ + '#theme' => 'webform_codemirror', + '#type' => 'yaml', + '#code' => Yaml::encode($this->convertTranslatableMarkupToStringRecursive($this->element)), + ], + ]; + } + + $form['key'] = [ + '#type' => 'value', + '#value' => 'element', + ]; + $form['parent_key'] = [ + '#type' => 'value', + '#value' => '', + ]; + + $form['properties'] = $this->webformElement->buildConfigurationForm([], $form_state); + $form['properties']['#tree'] = TRUE; + $form['properties']['custom']['#open'] = TRUE; + + $form['properties']['element']['type'] = [ + '#type' => 'item', + '#title' => $this->t('Type'), + '#markup' => $type, + '#weight' => -100, + '#parents' => ['type'], + ]; + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Test'), + '#button_type' => 'primary', + ]; + if (\Drupal::request()->getSession()->get('webform_ui_test_element_' . $type)) { + $form['actions']['reset'] = [ + '#type' => 'submit', + '#value' => $this->t('Reset'), + '#limit_validation_errors' => [], + '#submit' => ['::reset'], + ]; + } + + // Clear all messages including 'Unable to display this webform...' which is + // generated because we are using a temp webform. + // drupal_get_messages(); + return $form; + } + + /** + * {@inheritdoc} + */ + 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])); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // Rebuild is throwing the below error. + // LogicException: Settings can not be serialized. + // $form_state->setRebuild(); + // @todo Determine what object is being serialized with webform. + + // The webform element configuration is stored in the 'properties' key in + // the webform, pass that through for submission. + $element_form_state = clone $form_state; + $element_form_state->setValues($form_state->getValue('properties')); + + $properties = $this->webformElement->getConfigurationFormProperties($form, $element_form_state); + + // Set #default_value using 'test' element value. + if ($element_value = $form_state->getValue('element')) { + $properties['#default_value'] = $element_value; + } + + \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])); + } + + /** + * Determines if the webform element key already exists. + * + * @param string $key + * The webform element key. + * + * @return bool + * TRUE if the webform element key, FALSE otherwise. + */ + public function exists($key) { + return FALSE; + } + + /** + * Convert all translatable markup to strings. + * + * This allows element to be serialized. + * + * @param array $element + * An element. + * + * @return array + * The element with all translatable markup converted to strings. + */ + protected function convertTranslatableMarkupToStringRecursive(array $element) { + foreach ($element as $key => $value) { + if ($value instanceof TranslatableMarkup) { + $element[$key] = (string) $value; + } + elseif (is_array($value)) { + $element[$key] = $this->convertTranslatableMarkupToStringRecursive($value); + } + } + return $element; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php new file mode 100644 index 000000000..16a048e77 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php @@ -0,0 +1,98 @@ +getElement($key); + + /** @var \Drupal\webform\WebformElementInterface $webform_element */ + $webform_element = $this->elementManager->getElementInstance($element); + + $related_types = $webform_element->getRelatedTypes($element); + if (empty($related_types)) { + throw new NotFoundHttpException(); + } + + $headers = [ + ['data' => $this->t('Element')], + ['data' => $this->t('Category')], + ['data' => $this->t('Operations')], + ]; + + $definitions = $this->getDefinitions(); + $rows = []; + foreach ($related_types as $related_type_name => $related_type_label) { + $plugin_definition = $definitions[$related_type_name]; + + $row = []; + $row['title']['data'] = [ + '#type' => 'link', + '#title' => $plugin_definition['label'], + '#url' => Url::fromRoute('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key], ['query' => ['type' => $related_type_name]]), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['webform-tooltip-link', 'js-webform-tooltip-link']) + ['title' => $plugin_definition['description']], + ]; + $row['category']['data'] = (isset($plugin_definition['category'])) ? $plugin_definition['category'] : $this->t('Other'); + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'change' => [ + 'title' => $this->t('Change'), + 'url' => Url::fromRoute('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key], ['query' => ['type' => $related_type_name]]), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(800), + ], + ], + ]; + + // Issue #2741877 Nested modals don't work: when using CKEditor in a + // modal, then clicking the image button opens another modal, + // which closes the original modal. + // @todo Remove the below workaround once this issue is resolved. + if ($related_type_name == 'processed_text') { + unset($row['operations']['data']['#links']['change']['attributes']); + } + + $rows[] = $row; + } + + $form = []; + $form['elements'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#attributes' => [ + 'class' => ['webform-ui-element-type-table'], + ], + ]; + $form['actions'] = ['#type' => 'actions']; + $form['actions']['cancel'] = [ + '#type' => 'link', + '#title' => $this->t('Cancel'), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['button']), + '#url' => Url::fromRoute('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key]), + ]; + $form['#attached']['library'][] = 'webform/webform.tooltip'; + return $form; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php new file mode 100644 index 000000000..183c67671 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php @@ -0,0 +1,71 @@ +elementManager = $element_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.webform.element') + ); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + return parent::submitForm($form, $form_state); + } + + /** + * Gets the sorted definition of all WebformElement plugins. + * + * @return array + * An array of WebformElement plugin definitions. Keys are element types. + */ + protected function getDefinitions() { + $definitions = $this->elementManager->getDefinitions(); + $definitions = $this->elementManager->getSortedDefinitions($definitions, 'category'); + $grouped_definitions = $this->elementManager->getGroupedDefinitions($definitions); + + $sorted_definitions = []; + 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/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php new file mode 100644 index 000000000..b6ecc5bd8 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php @@ -0,0 +1,119 @@ +getRequest()->query->get('parent'); + + $headers = [ + ['data' => $this->t('Element')], + ['data' => $this->t('Category')], + ['data' => $this->t('Operations')], + ]; + + $elements = $this->elementManager->getInstances(); + $definitions = $this->getDefinitions(); + $rows = []; + foreach ($definitions as $plugin_id => $plugin_definition) { + /** @var \Drupal\webform\WebformElementInterface $webform_element */ + $webform_element = $elements[$plugin_id]; + + // Skip disabled or hidden plugins. + if ($webform_element->isDisabled() || $webform_element->isHidden()) { + continue; + } + + // Skip wizard page which has a dedicated URL. + if ($plugin_id == 'webform_wizard_page') { + continue; + } + + $route_parameters = ['webform' => $webform->id(), 'type' => $plugin_id]; + $route_options = ($parent) ? ['query' => ['parent' => $parent]] : []; + $row = []; + $row['title']['data'] = [ + '#type' => 'link', + '#title' => $plugin_definition['label'], + '#url' => Url::fromRoute('entity.webform_ui.element.add_form', $route_parameters, $route_options), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800), + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + $row['category']['data'] = $plugin_definition['category']; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => [ + 'add' => [ + 'title' => $this->t('Add element'), + 'url' => Url::fromRoute('entity.webform_ui.element.add_form', $route_parameters, $route_options), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(800), + ], + ], + ]; + // Issue #2741877 Nested modals don't work: when using CKEditor in a + // modal, then clicking the image button opens another modal, + // which closes the original modal. + // @todo Remove the below workaround once this issue is resolved. + if ($webform_element->getPluginId() == 'processed_text') { + unset($row['title']['data']['#attributes']); + unset($row['operations']['data']['#links']['add']['attributes']); + } + + $row['title']['data']['#attributes']['class'][] = 'js-webform-tooltip-link'; + $row['title']['data']['#attributes']['class'][] = 'webform-tooltip-link'; + $row['title']['data']['#attributes']['title'] = $plugin_definition['description']; + + $rows[] = $row; + } + + $form['#attached']['library'][] = 'webform/webform.form'; + $form['#attached']['library'][] = 'webform/webform.tooltip'; + + $form['filter'] = [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by element name'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-element' => '.webform-ui-element-type-table', + 'title' => $this->t('Enter a part of the element name to filter by.'), + 'autofocus' => 'autofocus', + ], + ]; + + $form['elements'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No element available.'), + '#attributes' => [ + 'class' => ['webform-ui-element-type-table'], + ], + ]; + + return $form; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php b/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php new file mode 100644 index 000000000..fdfff5cce --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementPropertiesTest.php @@ -0,0 +1,72 @@ +createUsers(); + + // Create filters. + $this->createFilters(); + } + + /** + * Tests element properties. + */ + public function testElementProperties() { + $this->drupalLogin($this->adminWebformUser); + + // Loops through all the elements, edits them via the UI, and checks that + // the element's render array has not been altered. + // This verifies that the edit element (via UI) form is not unexpectedly + // altering an element's render array. + foreach (static::$testWebforms as $webform_id) { + /** @var \Drupal\webform\WebformInterface $webform_elements */ + $webform_elements = Webform::load($webform_id); + $original_elements = $webform_elements->getElementsDecodedAndFlattened(); + foreach ($original_elements as $key => $original_element) { + $this->drupalPostForm('admin/structure/webform/manage/' . $webform_elements->id() . '/element/' . $key . '/edit', [], t('Save')); + + // Must reset the webform entity cache so that the update elements can + // be loaded. + \Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache(); + + /** @var \Drupal\webform\WebformInterface $webform_elements */ + $webform_elements = Webform::load($webform_id); + $updated_element = $webform_elements->getElementsDecodedAndFlattened()[$key]; + + $this->assertEqual($original_element, $updated_element, "'$key'' properties is equal."); + } + } + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php b/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php new file mode 100644 index 000000000..1143306d6 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/Tests/WebformUiElementTest.php @@ -0,0 +1,247 @@ +createUsers(); + } + + /** + * Tests element. + */ + public function testElements() { + global $base_path; + + $this->drupalLogin($this->adminWebformUser); + + $webform_contact = Webform::load('contact'); + + /**************************************************************************/ + // Multiple + /**************************************************************************/ + + // Check multiple enabled before submission. + $this->drupalGet('admin/structure/webform/manage/contact/element/name/edit'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Update element. + $this->drupalPostForm('admin/structure/webform/manage/contact/element/test/edit', ['properties[title]' => 'Test 123', 'properties[default_value]' => 'This is a default value'], t('Save')); + + // Check elements URL contains ?element-update query string parameter. + $this->assertUrl('admin/structure/webform/manage/contact', ['query' => ['element-update' => 'test']]); + + // Check elements element-update class exists. + $this->assertRaw('color-success js-webform-ui-element-update'); + + // Check element updated. + $this->drupalGet('webform/contact'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Check that 'test' element is being added to the webform_submission_data table. + $this->drupalPostForm('webform/contact/test', [], t('Send message')); + $this->assertEqual(1, db_query("SELECT COUNT(sid) FROM {webform_submission_data} WHERE webform_id='contact' AND name='test'")->fetchField()); + + // Check delete element. + $this->drupalPostForm('admin/structure/webform/manage/contact/element/test/delete', [], t('Delete')); + $this->drupalGet('webform/contact'); + $this->assertNoRaw(''); + $this->assertNoRaw(''); + + // Check that 'test' element values were deleted from the webform_submission_data table. + $this->assertEqual(0, db_query("SELECT COUNT(sid) FROM {webform_submission_data} WHERE webform_id='contact' AND name='test'")->fetchField()); + + /**************************************************************************/ + // Change type + /**************************************************************************/ + + // Check create element. + $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'); + // Check change element type link. + $this->assertRaw('Text fieldChange'); + // 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->assertRaw(t('Hidden')); + $this->assertRaw('Change'); + $this->assertRaw(t('value')); + $this->assertRaw('Change'); + $this->assertRaw(t('Search')); + $this->assertRaw('Change'); + $this->assertRaw(t('Telephone')); + $this->assertRaw('Change'); + $this->assertRaw(t('URL')); + $this->assertRaw('Change'); + + // Check change element type. + $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('ValueCancel'); + $this->assertRaw('(Changing from Text field)'); + + // 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'); + + // Check change element type link. + $this->assertRaw('ValueChange'); + + // 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->assertResponse(404); + + /**************************************************************************/ + // Date + /**************************************************************************/ + + // Check GNU Date Input Format validation. + $edit = [ + 'properties[default_value]' => 'not a valid date', + ]; + $this->drupalPostForm('admin/structure/webform/manage/test_element_dates/element/date_min_max_dynamic/edit', $edit, t('Save')); + $this->assertRaw('The Default value could not be interpreted in GNU Date Input Format.'); + } + + /** + * Tests permissions. + */ + public function testPermissions() { + $webform = Webform::load('contact'); + + // Check source page access not visible to user with 'administer webform' + // permission. + $account = $this->drupalCreateUser(['administer webform']); + $this->drupalLogin($account); + $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/source'); + $this->assertResponse(403); + $this->drupalLogout(); + + // Check source page access not visible to user with 'edit webform source' + // without 'administer webform' permission. + $account = $this->drupalCreateUser(['edit webform source']); + $this->drupalLogin($account); + $this->drupalGet('admin/structure/webform/manage/' . $webform->id() . '/source'); + $this->assertResponse(403); + $this->drupalLogout(); + + // Check source page access visible to user with 'edit webform source' + // 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->assertResponse(200); + $this->drupalLogout(); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/WebformUiEntityForm.php b/web/modules/contrib/webform/modules/webform_ui/src/WebformUiEntityForm.php new file mode 100644 index 000000000..57237bc3d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/WebformUiEntityForm.php @@ -0,0 +1,448 @@ +getEntity(); + + if ($webform->isNew()) { + return $form; + } + + $element_dialog_attributes = WebformDialogHelper::getModalDialogAttributes(800); + + // Track which element has been updated. + $element_update = FALSE; + if ($this->getRequest()->query->has('element-update')) { + $element_update = $this->getRequest()->query->get('element-update'); + $form['#attached']['drupalSettings']['webformUiElementUpdate'] = $element_update; + } + + // Build table header. + $header = []; + $header['title'] = $this->t('Title'); + $header['add'] = [ + 'data' => '', + 'class' => [RESPONSIVE_PRIORITY_MEDIUM, 'webform-ui-element-operations'], + ]; + $header['key'] = [ + 'data' => $this->t('Key'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + $header['type'] = [ + 'data' => $this->t('Type'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + if ($webform->hasFlexboxLayout()) { + $header['flex'] = [ + 'data' => $this->t('Flex'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + } + $header['required'] = [ + 'data' => $this->t('Required'), + 'class' => ['webform-ui-element-required', RESPONSIVE_PRIORITY_LOW], + ]; + $header['weight'] = $this->t('Weight'); + $header['parent'] = $this->t('Parent'); + if (!$webform->isNew()) { + $header['operations'] = [ + 'data' => $this->t('Operations'), + 'class' => ['webform-ui-element-operations'], + ]; + } + + // Build table rows for elements. + $rows = []; + $elements = $this->getOrderableElements(); + $delta = count($elements); + foreach ($elements as $element) { + $key = $element['#webform_key']; + + $plugin_id = $this->elementManager->getElementPluginId($element); + + /** @var \Drupal\webform\WebformElementInterface $webform_element */ + $webform_element = $this->elementManager->createInstance($plugin_id); + + $is_container = $webform_element->isContainer($element); + $is_root = $webform_element->isRoot(); + + // If disabled, display warning. + if ($webform_element->isDisabled()) { + $webform_element->displayDisabledWarning($element); + } + + // Get row class names. + $row_class = ['draggable']; + if ($is_root) { + $row_class[] = 'tabledrag-root'; + $row_class[] = 'webform-ui-element-root'; + } + if (!$is_container) { + $row_class[] = 'tabledrag-leaf'; + } + if ($is_container) { + $row_class[] = 'webform-ui-element-container'; + } + if (!empty($element['#type'])) { + $row_class[] = 'webform-ui-element-type-' . $element['#type']; + } + $row_class[] = 'webform-ui-element-container'; + + // Add classes to updated element. + // @see Drupal.behaviors.webformUiElementsUpdate + if ($element_update && $element_update == $element['#webform_key']) { + $row_class[] = 'color-success'; + $row_class[] = 'js-webform-ui-element-update'; + } + + $rows[$key]['#attributes']['class'] = $row_class; + + $indentation = NULL; + if ($element['#webform_depth']) { + $indentation = [ + '#theme' => 'indentation', + '#size' => $element['#webform_depth'], + ]; + } + + $rows[$key]['title'] = [ + '#markup' => $element['#admin_title'] ?: $element['#title'], + '#prefix' => !empty($indentation) ? $this->renderer->render($indentation) : '', + ]; + if ($is_container) { + $route_parameters = [ + 'webform' => $webform->id(), + ]; + $route_options = ['query' => ['parent' => $key]]; + $rows[$key]['add'] = [ + '#type' => 'link', + '#title' => $this->t('Add element'), + '#url' => new Url('entity.webform_ui.element', $route_parameters, $route_options), + '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['button', 'button-action', 'button--primary', 'button--small']), + ]; + } + else { + $rows[$key]['add'] = ['#markup' => '']; + } + + $rows[$key]['name'] = [ + '#markup' => $element['#webform_key'], + ]; + + $rows[$key]['type'] = [ + '#markup' => $webform_element->getPluginLabel(), + ]; + + if ($webform->hasFlexboxLayout()) { + $rows[$key]['flex'] = [ + '#markup' => (empty($element['#flex'])) ? 1 : $element['#flex'], + ]; + } + + if ($webform_element->hasProperty('required')) { + $rows[$key]['required'] = [ + '#type' => 'checkbox', + '#default_value' => (empty($element['#required'])) ? FALSE : TRUE, + ]; + } + else { + $rows[$key]['required'] = ['#markup' => '']; + } + + $rows[$key]['weight'] = [ + '#type' => 'weight', + '#title' => $this->t('Weight for ID @id', ['@id' => $key]), + '#title_display' => 'invisible', + '#default_value' => $element['#weight'], + '#attributes' => [ + 'class' => ['row-weight'], + ], + '#delta' => $delta, + ]; + + $rows[$key]['parent']['key'] = [ + '#parents' => ['webform_ui_elements', $key, 'key'], + '#type' => 'hidden', + '#value' => $key, + '#attributes' => [ + 'class' => ['row-key'], + ], + ]; + $rows[$key]['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 (!$webform->isNew()) { + $rows[$key]['operations'] = [ + '#type' => 'operations', + ]; + $rows[$key]['operations']['#links']['edit'] = [ + 'title' => $this->t('Edit'), + 'url' => new Url('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key]), + 'attributes' => $element_dialog_attributes, + ]; + // Issue #2741877 Nested modals don't work: when using CKEditor in a + // modal, then clicking the image button opens another modal, + // which closes the original modal. + // @todo Remove the below workaround once this issue is resolved. + if ($webform_element->getPluginId() == 'processed_text') { + unset($rows[$key]['operations']['#links']['edit']['attributes']); + } + $rows[$key]['operations']['#links']['duplicate'] = [ + 'title' => $this->t('Duplicate'), + 'url' => new Url('entity.webform_ui.element.duplicate_form', [ + 'webform' => $webform->id(), + 'key' => $key, + ]), + 'attributes' => $element_dialog_attributes, + ]; + $rows[$key]['operations']['#links']['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => new Url('entity.webform_ui.element.delete_form', [ + 'webform' => $webform->id(), + 'key' => $key, + ]), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(640), + ]; + } + } + + // 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_action_attributes = WebformDialogHelper::getModalDialogAttributes(800, ['button', 'button-action', 'button--primary', 'button--small']); + $form['local_actions'] = [ + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + $form['local_actions']['add_element'] = [ + '#type' => 'link', + '#title' => $this->t('Add element'), + '#url' => new Url('entity.webform_ui.element', ['webform' => $webform->id()]), + '#attributes' => $local_action_attributes, + ]; + if ($this->elementManager->createInstance('webform_wizard_page')->isEnabled()) { + $form['local_actions']['add_page'] = [ + '#type' => 'link', + '#title' => $this->t('Add page'), + '#url' => new Url('entity.webform_ui.element.add_form', ['webform' => $webform->id(), 'type' => 'webform_wizard_page']), + '#attributes' => $local_action_attributes, + ]; + } + if ($webform->hasFlexboxLayout()) { + $form['local_actions']['add_layout'] = [ + '#type' => 'link', + '#title' => $this->t('Add layout'), + '#url' => new Url('entity.webform_ui.element.add_form', ['webform' => $webform->id(), 'type' => 'webform_flexbox']), + '#attributes' => $local_action_attributes, + ]; + } + + $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, + ], + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'row-weight', + ], + ], + ] + $rows; + + // Must preload libraries required by (modal) dialogs. + $form['#attached']['library'][] = 'webform/webform.admin.dialog'; + $form['#attached']['library'][] = 'webform_ui/webform_ui'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $this->getEntity(); + + // Don't validate new webforms because they don't have any initial + // elements. + if ($webform->isNew()) { + return; + } + + parent::validateForm($form, $form_state); + + // Get raw flattened elements that will be used to rebuild element's YAML + // hierarchy. + $elements_flattened = $webform->getElementsDecodedAndFlattened(); + + // Get the reordered elements and sort them by weight. + $webform_ui_elements = $form_state->getValue('webform_ui_elements') ?: []; + uasort($webform_ui_elements, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + + // Make sure the reordered element keys and match the existing element keys. + if (array_diff_key($webform_ui_elements, $elements_flattened)) { + $form_state->setError($form['webform_ui_elements'], $this->t('The elements have been unexpectedly altered. Please try again')); + } + + // 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; + } + + // Set #required or remove the property. + if (isset($webform_ui_elements[$key]['required'])) { + if (empty($webform_ui_elements[$key]['required'])) { + unset($elements_flattened[$key]['#required']); + } + else { + $elements_flattened[$key]['#required'] = TRUE; + } + } + + // Add this key to the parent's children. + $webform_ui_elements[$parent_key]['children'][$key] = $key; + } + + // Rebuild elements to reflect new hierarchy. + $elements_updated = []; + // Preserve the original elements root properties. + $elements_original = Yaml::decode($webform->get('elements')) ?: []; + foreach ($elements_original as $key => $value) { + if (Element::property($key)) { + $elements_updated[$key] = $value; + } + } + + $this->buildUpdatedElementsRecursive($elements_updated, '', $webform_ui_elements, $elements_flattened); + + // Update the webform's elements. + $webform->setElements($elements_updated); + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + $actions = parent::actions($form, $form_state); + $actions['submit']['#value'] = ($this->entity->isNew()) ? $this->t('Save') : $this->t('Save elements'); + return $actions; + } + + /** + * Build updated elements using the new parent child relationship. + * + * @param array $elements + * An associative array that will be populated with updated elements + * hierarchy. + * @param string $key + * The current element key. The blank empty key represents the elements + * root. + * @param array $webform_ui_elements + * An associative array contain the reordered elements parent child + * relationship. + * @param array $elements_flattened + * An associative array containing the raw flattened elements that will + * copied into the updated elements hierarchy. + */ + protected function buildUpdatedElementsRecursive(array &$elements, $key, array $webform_ui_elements, array $elements_flattened) { + if (!isset($webform_ui_elements[$key]['children'])) { + return; + } + + foreach ($webform_ui_elements[$key]['children'] as $key) { + $elements[$key] = $elements_flattened[$key]; + $this->buildUpdatedElementsRecursive($elements[$key], $key, $webform_ui_elements, $elements_flattened); + } + } + + /** + * Get webform's elements as an associative array of orderable elements. + * + * @return array + * An associative array of orderable elements. + */ + protected function getOrderableElements() { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $this->getEntity(); + + $elements = $webform->getElementsInitializedAndFlattened(); + $weights = []; + foreach ($elements as &$element) { + $parent_key = $element['#webform_parent_key']; + if (!isset($weights[$parent_key])) { + $element['#weight'] = $weights[$parent_key] = 0; + } + else { + $element['#weight'] = ++$weights[$parent_key]; + } + + if (empty($element['#type'])) { + if (isset($element['#theme'])) { + $element['#type'] = $element['#theme']; + } + elseif (isset($element['#markup'])) { + $element['#type'] = 'markup'; + } + else { + $element['#type'] = ''; + } + } + + if (empty($element['#title'])) { + if (!empty($element['#markup'])) { + $element['#title'] = Unicode::truncate(strip_tags($element['#markup']), 100, TRUE, TRUE); + } + else { + $element['#title'] = '[' . t('blank') . ']'; + } + } + } + return $elements; + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/src/WebformUiOptionsForm.php b/web/modules/contrib/webform/modules/webform_ui/src/WebformUiOptionsForm.php new file mode 100644 index 000000000..311a3dffa --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/src/WebformUiOptionsForm.php @@ -0,0 +1,61 @@ + 'webform_options', + '#mode' => 'yaml', + '#title' => $this->t('Options'), + '#title_display' => 'invisible', + '#empty_options' => 10, + '#add_more' => 10, + '#required' => TRUE, + '#default_value' => $this->getOptions(), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function afterBuild(array $element, FormStateInterface $form_state) { + // Overriding after \Drupal\Core\Entity\EntityForm::afterBuild because + // it calls ::buildEntity(), which calls ::copyFormValuesToEntity, which + // attempts to populate the entity even though the 'options' have not been + // validated and set,. + // @see \Drupal\Core\Entity\EntityForm::afterBuild + // @eee \Drupal\webform_ui\WebformUiOptionsForm::copyFormValuesToEntity + // @see \Drupal\webform\Element\WebformOptions + return $element; + } + + /** + * {@inheritdoc} + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + + if (is_array($values['options'])) { + $entity->setOptions($values['options']); + unset($values['options']); + } + + foreach ($values as $key => $value) { + $entity->set($key, $value); + } + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.info.yml b/web/modules/contrib/webform/modules/webform_ui/webform_ui.info.yml new file mode 100644 index 000000000..41ae03ec8 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.info.yml @@ -0,0 +1,13 @@ +name: 'Webform UI' +type: module +description: 'Provides a user interface for building and maintaining webforms.' +package: Webform +# core: 8.x +dependencies: + - 'drupal:webform' + +# Information added by Drupal.org packaging script on 2017-03-05 +version: '8.x-5.0-beta9' +core: '8.x' +project: 'webform' +datestamp: 1488753802 diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.libraries.yml b/web/modules/contrib/webform/modules/webform_ui/webform_ui.libraries.yml new file mode 100644 index 000000000..f10561c0f --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.libraries.yml @@ -0,0 +1,16 @@ +webform_ui: + version: 1.x + css: + theme: + css/webform_ui.module.css: {} + js: + js/webform_ui.js: {} + +webform_ui.element: + version: 1.x + js: + js/webform_ui.element.js: {} + dependencies: + - core/drupal + - core/jquery + - core/jquery.once diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.links.task.yml b/web/modules/contrib/webform/modules/webform_ui/webform_ui.links.task.yml new file mode 100644 index 000000000..a375c7bed --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.links.task.yml @@ -0,0 +1,24 @@ +# Forms + +entity.webform.source_form_elements: + title: 'Source (YAML)' + route_name: entity.webform.source_form + parent_id: entity.webform.edit_form + + +# Webform Options + +entity.webform_options: + title: 'Edit' + route_name: entity.webform_options.edit_form + base_route: entity.webform_options.edit_form + +entity.webform_options.edit_form: + title: 'Options' + route_name: entity.webform_options.edit_form + parent_id: entity.webform_options + +entity.webform_options.source_form: + title: 'Source (YAML)' + route_name: entity.webform_options.source_form + parent_id: entity.webform_options diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.module b/web/modules/contrib/webform/modules/webform_ui/webform_ui.module new file mode 100644 index 000000000..8bc92106c --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.module @@ -0,0 +1,39 @@ +getHandlerClasses(); + $handlers['form']['source'] = $handlers['form']['default']; + $handlers['form']['default'] = 'Drupal\webform_ui\WebformUiEntityForm'; + $handlers['form']['duplicate'] = 'Drupal\webform_ui\WebformUiEntityForm'; + $webform_entity_type->setHandlerClass('form', $handlers['form']); + } + + if (isset($entity_types['webform_options'])) { + /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $webform_entity_type */ + $webform_options_entity_type = $entity_types['webform_options']; + + // Swap the 'default' webform handler with the webform UI option form + // and move the old webform option source entity webform to a new 'source' + // webform handler. + $handlers = $webform_options_entity_type->getHandlerClasses(); + $handlers['form']['source'] = $handlers['form']['default']; + $handlers['form']['default'] = 'Drupal\webform_ui\WebformUiOptionsForm'; + $webform_options_entity_type->setHandlerClass('form', $handlers['form']); + } + +} diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.permissions.yml b/web/modules/contrib/webform/modules/webform_ui/webform_ui.permissions.yml new file mode 100644 index 000000000..5ac28ce90 --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.permissions.yml @@ -0,0 +1,3 @@ +'edit webform source': + title: 'Edit webform source code' + description: 'Warning: Give to site builder and developer roles only;
          Editing webform source code allows users alter and possibly break a webform''s render array.' diff --git a/web/modules/contrib/webform/modules/webform_ui/webform_ui.routing.yml b/web/modules/contrib/webform/modules/webform_ui/webform_ui.routing.yml new file mode 100644 index 000000000..68adc3e0d --- /dev/null +++ b/web/modules/contrib/webform/modules/webform_ui/webform_ui.routing.yml @@ -0,0 +1,71 @@ +entity.webform.source_form: + path: '/admin/structure/webform/manage/{webform}/source' + defaults: + _title_callback: '\Drupal\webform\Controller\WebformController::title' + _entity_form: 'webform.source' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformSourceAccess' + +entity.webform_options.source_form: + path: '/admin/structure/webform/settings/options/manage/{webform_options}/source' + defaults: + _title_callback: '\Drupal\webform\Controller\WebformOptionsController::title' + _entity_form: 'webform_options.source' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformOptionSourceAccess' + +entity.webform_ui.element: + path: '/admin/structure/webform/manage/{webform}/element/add' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementTypeSelectForm' + _title: 'Select an element' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +entity.webform_ui.change_element: + path: '/admin/structure/webform/manage/{webform}/element/{key}/change' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementTypeChangeForm' + _title: 'Select new element type' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +entity.webform_ui.element.add_form: + path: '/admin/structure/webform/manage/{webform}/element/add/{type}' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementAddForm' + _title: 'Add element' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +entity.webform_ui.element.edit_form: + path: '/admin/structure/webform/manage/{webform}/element/{key}/edit' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementEditForm' + _title: 'Edit element' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +entity.webform_ui.element.duplicate_form: + path: '/admin/structure/webform/manage/{webform}/element/{key}/duplicate' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementDuplicateForm' + _title: 'Duplicate element' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +entity.webform_ui.element.delete_form: + path: '/admin/structure/webform/manage/{webform}/element/{key}/delete' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementDeleteForm' + _title: 'Delete element' + requirements: + _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformEditAccess' + +webform.element_plugins.test: + path: '/admin/structure/webform/settings/elements/{type}/test' + defaults: + _form: '\Drupal\webform_ui\Form\WebformUiElementTestForm' + _title: 'Test element' + requirements: + _permission: 'administer webform' diff --git a/web/modules/contrib/webform/src/Access/WebformAccess.php b/web/modules/contrib/webform/src/Access/WebformAccess.php new file mode 100644 index 000000000..c94910fa8 --- /dev/null +++ b/web/modules/contrib/webform/src/Access/WebformAccess.php @@ -0,0 +1,146 @@ +getSetting('results_disabled')) { + $access_result = AccessResult::allowed(); + } + // If webform has any results return neutral. + elseif (\Drupal::entityTypeManager()->getStorage('webform_submission')->getTotal($webform, $source_entity)) { + $access_result = AccessResult::allowed(); + } + // Finally, forbid access to the results. + else { + $access_result = AccessResult::forbidden(); + } + return $access_result->addCacheableDependency($webform); + } + + /** + * Check whether the user has 'administer webform' or 'administer webform submission' permission. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkAdminAccess(AccountInterface $account) { + return AccessResult::allowedIf($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission')); + } + + /** + * Check whether the user can view submissions. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * 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')); + } + + /** + * Check whether the user has 'administer' or 'overview' permission. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * 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')); + } + + /** + * Check that webform submission has email and the user can update any webform submission. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkEmailAccess(WebformSubmissionInterface $webform_submission, AccountInterface $account) { + $webform = $webform_submission->getWebform(); + if ($webform->access('submission_update_any', $account)) { + $handlers = $webform->getHandlers(); + foreach ($handlers as $handler) { + if ($handler instanceof WebformHandlerMessageInterface) { + return AccessResult::allowed(); + } + } + } + return AccessResult::forbidden(); + } + + /** + * Check whether the user can access an entity's webform results. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public static function checkEntityResultsAccess(EntityInterface $entity, AccountInterface $account) { + $webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($entity); + return AccessResult::allowedIf($entity->access('update', $account) && $webform_field_name && $entity->$webform_field_name->entity); + } + + /** + * Check whether the webform has wizard pages. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + * + * @see \Drupal\webform\WebformSubmissionForm::buildForm + * @see \Drupal\webform\Entity\Webform::getPages + */ + public static function checkWebformWizardPagesAccess(WebformInterface $webform) { + $elements = $webform->getElementsInitialized(); + foreach ($elements as $key => $element) { + if (isset($element['#type']) && $element['#type'] == 'webform_wizard_page') { + return AccessResult::allowed(); + } + } + return AccessResult::forbidden(); + } + +} diff --git a/web/modules/contrib/webform/src/Ajax/ScrollTopCommand.php b/web/modules/contrib/webform/src/Ajax/ScrollTopCommand.php new file mode 100644 index 000000000..1b5412865 --- /dev/null +++ b/web/modules/contrib/webform/src/Ajax/ScrollTopCommand.php @@ -0,0 +1,41 @@ +selector = $selector; + } + + /** + * {@inheritdoc} + */ + public function render() { + return [ + 'command' => 'webformScrollTop', + 'selector' => $this->selector, + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Annotation/WebformElement.php b/web/modules/contrib/webform/src/Annotation/WebformElement.php new file mode 100644 index 000000000..bef90b9dd --- /dev/null +++ b/web/modules/contrib/webform/src/Annotation/WebformElement.php @@ -0,0 +1,97 @@ +moduleHandler = $module_handler; + $this->requestHandler = $request_handler; + $this->setStringTranslation($string_translation); + } + + /** + * {@inheritdoc} + */ + public function applies(RouteMatchInterface $route_match) { + $route_name = $route_match->getRouteName(); + // All routes must begin or contain 'webform. + if (strpos($route_name, 'webform') === FALSE) { + return FALSE; + } + + $args = explode('.', $route_name); + + // Skip all config_translation routes except the overview + // and allow Drupal to use the path as the breadcrumb. + if (strpos($route_name, 'config_translation') !== FALSE && $route_name != 'entity.webform.config_translation_overview') { + return FALSE; + } + + try { + $path = Url::fromRouteMatch($route_match)->toString(); + $base_path = base_path(); + } + catch (\Exception $exception) { + $path = ''; + $base_path = '/'; + } + if ((count($args) > 2) && $args[0] == 'entity' && ($args[2] == 'webform' || $args[2] == 'webform_submission')) { + $this->type = 'webform_source_entity'; + } + elseif (strpos($route_name, 'entity.webform_ui.element') === 0) { + $this->type = 'webform_element'; + } + 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) { + $this->type = 'webform_user_submission'; + } + elseif (strpos($route_match->getRouteName(), 'webform.user.submissions') !== FALSE) { + $this->type = 'webform_user_submissions'; + } + elseif ($route_match->getParameter('webform_submission') instanceof WebformSubmissionInterface && $route_match->getParameter('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'); + $this->type = ($webform->isTemplate() && $this->moduleHandler->moduleExists('webform_templates')) ? 'webform_template' : 'webform'; + } + elseif (strpos($path, $base_path . 'admin/structure/webform/test/') === 0) { + $this->type = 'webform_test'; + } + else { + $this->type = NULL; + } + + return ($this->type) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function build(RouteMatchInterface $route_match) { + if ($this->type == 'webform_source_entity') { + $source_entity = $this->requestHandler->getCurrentSourceEntity(['webform', 'webform_submission']); + $entity_type = $source_entity->getEntityTypeId(); + $entity_id = $source_entity->id(); + + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '')); + $breadcrumb->addLink($source_entity->toLink()); + if ($webform_submission = $route_match->getParameter('webform_submission')) { + if (strpos($route_match->getRouteName(), 'webform.user.submission') !== FALSE) { + $breadcrumb->addLink(Link::createFromRoute($this->t('Submissions'), "entity.$entity_type.webform.user.submissions", [$entity_type => $entity_id])); + } + elseif ($source_entity->access('webform_submission_view') || $webform_submission->access('view_any')) { + $breadcrumb->addLink(Link::createFromRoute($this->t('Results'), "entity.$entity_type.webform.results_submissions", [$entity_type => $entity_id])); + } + elseif ($webform_submission->access('view_own')) { + $breadcrumb->addLink(Link::createFromRoute($this->t('Results'), "entity.$entity_type.webform.user.submissions", [$entity_type => $entity_id])); + } + } + } + else { + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '')); + $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')); + switch ($this->type) { + case 'webform_test': + $breadcrumb->addLink(Link::createFromRoute($this->t('Testing'), 'webform_test.index')); + break; + + case 'webform_template': + $breadcrumb->addLink(Link::createFromRoute($this->t('Templates'), 'entity.webform.templates')); + break; + + case 'webform_element': + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $route_match->getParameter('webform'); + $breadcrumb->addLink(Link::createFromRoute($webform->label(), 'entity.webform.canonical', ['webform' => $webform->id()])); + $breadcrumb->addLink(Link::createFromRoute($this->t('Elements'), 'entity.webform.edit_form', ['webform' => $webform->id()])); + break; + + case 'webform_handler': + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $route_match->getParameter('webform'); + $breadcrumb->addLink(Link::createFromRoute($webform->label(), 'entity.webform.canonical', ['webform' => $webform->id()])); + $breadcrumb->addLink(Link::createFromRoute($this->t('Emails / Handlers'), 'entity.webform.handlers_form', ['webform' => $webform->id()])); + break; + + case 'webform_submission': + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $route_match->getParameter('webform_submission'); + $webform = $webform_submission->getWebform(); + $breadcrumb->addLink(Link::createFromRoute($webform->label(), 'entity.webform.canonical', ['webform' => $webform->id()])); + $breadcrumb->addLink(Link::createFromRoute($this->t('Results'), 'entity.webform.results_submissions', ['webform' => $webform->id()])); + break; + + case 'webform_user_submissions': + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $route_match->getParameter('webform'); + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($webform->label(), 'entity.webform.canonical', ['webform' => $webform->id()])); + break; + + case 'webform_user_submission': + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = $route_match->getParameter('webform_submission'); + $webform = $webform_submission->getWebform(); + $breadcrumb = new Breadcrumb(); + $breadcrumb->addLink(Link::createFromRoute($webform->label(), 'entity.webform.canonical', ['webform' => $webform->id()])); + $breadcrumb->addLink(Link::createFromRoute($this->t('Submissions'), 'entity.webform.user.submissions', ['webform' => $webform->id()])); + 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/contrib/webform/src/ContextProvider/WebformRouteContext.php b/web/modules/contrib/webform/src/ContextProvider/WebformRouteContext.php new file mode 100644 index 000000000..94b9b9ff8 --- /dev/null +++ b/web/modules/contrib/webform/src/ContextProvider/WebformRouteContext.php @@ -0,0 +1,67 @@ +routeMatch = $route_match; + } + + /** + * {@inheritdoc} + */ + public function getRuntimeContexts(array $unqualified_context_ids) { + $result = []; + $context_definition = new ContextDefinition('entity:webform', NULL, FALSE); + $value = NULL; + if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['webform'])) { + if ($webform = $this->routeMatch->getParameter('webform')) { + $value = $webform; + } + } + + $cacheability = new CacheableMetadata(); + $cacheability->setCacheContexts(['route']); + + $context = new Context($context_definition, $value); + $context->addCacheableDependency($cacheability); + $result['webform'] = $context; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + $context = new Context(new ContextDefinition('entity:webform', $this->t('Webform from URL'))); + return ['webform' => $context]; + } + +} diff --git a/web/modules/contrib/webform/src/ContextProvider/WebformSubmissionRouteContext.php b/web/modules/contrib/webform/src/ContextProvider/WebformSubmissionRouteContext.php new file mode 100644 index 000000000..c0c124bbf --- /dev/null +++ b/web/modules/contrib/webform/src/ContextProvider/WebformSubmissionRouteContext.php @@ -0,0 +1,67 @@ +routeMatch = $route_match; + } + + /** + * {@inheritdoc} + */ + public function getRuntimeContexts(array $unqualified_context_ids) { + $result = []; + $context_definition = new ContextDefinition('entity:webform_submission', NULL, FALSE); + $value = NULL; + if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['webform_submission'])) { + if ($webform_submission = $this->routeMatch->getParameter('webform_submission')) { + $value = $webform_submission; + } + } + + $cacheability = new CacheableMetadata(); + $cacheability->setCacheContexts(['route']); + + $context = new Context($context_definition, $value); + $context->addCacheableDependency($cacheability); + $result['webform_submission'] = $context; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getAvailableContexts() { + $context = new Context(new ContextDefinition('entity:webform_submission', $this->t('Webform submission from URL'))); + return ['webform_submission' => $context]; + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformAddonsController.php b/web/modules/contrib/webform/src/Controller/WebformAddonsController.php new file mode 100644 index 000000000..3434fa9da --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformAddonsController.php @@ -0,0 +1,76 @@ +addons = $addons; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.addons_manager') + ); + } + + /** + * Returns the Webform extend page. + * + * @return array + * The webform submission webform. + */ + public function index() { + $build = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['webform-addons', 'js-webform-details-toggle', 'webform-details-toggle'], + ], + ]; + $build['#attached']['library'][] = 'webform/webform.admin'; + $build['#attached']['library'][] = 'webform/webform.element.details.toggle'; + + $categories = $this->addons->getCategories(); + foreach ($categories as $category_name => $category) { + $build[$category_name] = [ + '#type' => 'details', + '#title' => $category['title'], + '#open' => TRUE, + ]; + $projects = $this->addons->getProjects($category_name); + foreach ($projects as &$project) { + $project['description'] .= ' ' . '
          ' . $project['url']->toString() . ''; + } + $build[$category_name]['content'] = [ + '#theme' => 'admin_block_content', + '#content' => $projects, + ]; + } + return $build; + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformController.php b/web/modules/contrib/webform/src/Controller/WebformController.php new file mode 100644 index 000000000..0fe9e2ae3 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformController.php @@ -0,0 +1,234 @@ +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') + ); + } + + /** + * Returns a webform to add a new submission to a webform. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface $webform + * The webform this submission will be added to. + * + * @return array + * The webform submission webform. + */ + public function addForm(Request $request, WebformInterface $webform) { + return $webform->getSubmissionForm(); + } + + /** + * Returns a webform's CSS. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response object. + */ + public function css(Request $request, WebformInterface $webform) { + $assets = $webform->getAssets(); + return new Response($assets['css'], 200, ['Content-Type' => 'text/css']); + } + + /** + * Returns a webform's JavaScript. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response object. + */ + public function javascript(Request $request, WebformInterface $webform) { + $assets = $webform->getAssets(); + return new Response($assets['javascript'], 200, ['Content-Type' => 'text/javascript']); + } + + /** + * Returns a webform confirmation page. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface|null $webform + * A webform. + * + * @return array + * A render array representing a webform confirmation page + */ + public function confirmation(Request $request, WebformInterface $webform = NULL) { + /** @var \Drupal\Core\Entity\EntityInterface $source_entity */ + if (!$webform) { + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + } + else { + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform'); + } + + /** @var \Drupal\webform\WebformSubmissionInterface $webform_submission */ + $webform_submission = NULL; + + if ($token = $request->get('token')) { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $webform_submission_storage */ + $webform_submission_storage = $this->entityTypeManager()->getStorage('webform_submission'); + if ($entities = $webform_submission_storage->loadByProperties(['token' => $token])) { + $webform_submission = reset($entities); + } + } + + // Get title. + $title = $webform->getSetting('confirmation_title') ?: (($source_entity) ? $source_entity->label() : $webform->label()); + + // Replace tokens in title. + $title = $this->tokenManager->replace($title, $webform_submission ?: $webform); + + $build = [ + '#title' => $title, + '#theme' => 'webform_confirmation', + '#webform' => $webform, + '#source_entity' => $source_entity, + '#webform_submission' => $webform_submission, + ]; + + $this->renderer->addCacheableDependency($build, $webform); + return $build; + } + + /** + * Returns a webform filter webform autocomplete matches. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param bool $templates + * If TRUE, limit autocomplete matches to webform templates. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + * The JSON response. + */ + public function autocomplete(Request $request, $templates = FALSE) { + $q = $request->query->get('q'); + + $webform_storage = $this->entityTypeManager()->getStorage('webform'); + + $query = $webform_storage->getQuery() + ->condition('title', $q, 'CONTAINS') + ->range(0, 10) + ->sort('title'); + + // Limit query to templates. + if ($templates) { + $query->condition('template', TRUE); + } + elseif ($this->moduleHandler()->moduleExists('webform_templates')) { + // Filter out templates if the webform_template.module is enabled. + $query->condition('template', FALSE); + } + + $entity_ids = $query->execute(); + + if (empty($entity_ids)) { + return new JsonResponse([]); + } + $webforms = $webform_storage->loadMultiple($entity_ids); + + $matches = []; + foreach ($webforms as $webform) { + if ($webform->access('view')) { + $value = new FormattableMarkup('@label (@id)', ['@label' => $webform->label(), '@id' => $webform->id()]); + $matches[] = ['value' => $value, 'label' => $value]; + } + } + + return new JsonResponse($matches); + } + + /** + * Route title callback. + * + * @param \Drupal\webform\WebformInterface|null $webform + * A webform. + * + * @return string + * The webform label as a render array. + */ + public function title(WebformInterface $webform = NULL) { + /** @var \Drupal\Core\Entity\EntityInterface $source_entity */ + if (!$webform) { + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + } + else { + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform'); + } + return ($source_entity) ? $source_entity->label() : $webform->label(); + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformElementController.php b/web/modules/contrib/webform/src/Controller/WebformElementController.php new file mode 100644 index 000000000..6682e0bf1 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformElementController.php @@ -0,0 +1,224 @@ +query->get('q') ?: ''; + if ($q == '') { + return new JsonResponse([]); + } + + // Get the initialized webform element. + $element = $webform->getElement($key); + if (!$element) { + return new JsonResponse([]); + } + + // Set default autocomplete properties. + $element += [ + '#autocomplete_existing' => FALSE, + '#autocomplete_items' => [], + '#autocomplete_match' => 3, + '#autocomplete_limit' => 10, + '#autocomplete_match_operator' => 'CONTAINS', + ]; + + // Check minimum number of characters. + if (Unicode::strlen($q) < (int) $element['#autocomplete_match']) { + return new JsonResponse([]); + } + + $matches = []; + + // Get existing matches. + if (!empty($element['#autocomplete_existing'])) { + $matches += $this->getMatchesFromExistingValues($q, $webform->id(), $key, $element['#autocomplete_match_operator'], $element['#autocomplete_limit']); + } + + // Get items (aka options) matches. + if (!empty($element['#autocomplete_items'])) { + $element['#options'] = $element['#autocomplete_items']; + $options = WebformOptions::getElementOptions($element); + $matches += $this->getMatchesFromOptions($q, $options, $element['#autocomplete_match_operator'], $element['#autocomplete_limit']); + } + + // Sort matches by label and enforce the limit. + if ($matches) { + uasort($matches, function (array $a, array $b) { + return $a['label'] > $b['label']; + }); + $matches = array_values($matches); + $matches = array_slice($matches, 0, $element['#autocomplete_limit']); + } + + return new JsonResponse($matches); + } + + /** + * Get matches from existing submission values. + * + * @param string $q + * String to filter option's label by. + * @param string $webform_id + * The webform id. + * @param string $key + * The element's key. + * @param string $operator + * Match operator either CONTAINS or STARTS_WITH. + * @param int $limit + * Limit number of matches. + * + * @return array + * An array of matches. + */ + protected function getMatchesFromExistingValues($q, $webform_id, $key, $operator = 'CONTAINS', $limit = 10) { + // Query webform submission for existing values. + $query = Database::getConnection()->select('webform_submission_data') + ->fields('webform_submission_data', ['value']) + ->condition('webform_id', $webform_id) + ->condition('name', $key) + ->condition('value', ($operator == 'START_WITH') ? "$q%" : "%$q%", 'LIKE') + ->orderBy('value'); + if ($limit) { + $query->range(0, $limit); + } + + // Convert query results values to matches array. + $values = $query->execute()->fetchCol(); + $matches = []; + foreach ($values as $value) { + $matches[$value] = ['value' => $value, 'label' => $value]; + } + return $matches; + } + + /** + * Get matches from options. + * + * @param string $q + * String to filter option's label by. + * @param array $options + * An associative array of webform options. + * @param string $operator + * Match operator either CONTAINS or STARTS_WITH. + * @param int $limit + * Limit number of matches. + * + * @return array + * An array of matches sorted by label. + */ + protected function getMatchesFromOptions($q, array $options, $operator = 'CONTAINS', $limit = 10) { + // Make sure options are populated. + if (empty($options)) { + return []; + } + + $matches = []; + + // Filter and convert options to autocomplete matches. + $this->getMatchesFromOptionsRecursive($q, $options, $matches, $operator); + + // Sort matches. + ksort($matches); + + // Apply match limit. + if ($limit) { + $matches = array_slice($matches, 0, $limit); + } + + return array_values($matches); + } + + /** + * Get matches from options recursive. + * + * @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. + * @param string $operator + * Match operator either CONTAINS or STARTS_WITH. + */ + protected function getMatchesFromOptionsRecursive($q, array $options, array &$matches, $operator = 'CONTAINS') { + foreach ($options as $value => $label) { + if (is_array($label)) { + $this->getMatchesFromOptionsRecursive($q, $label, $matches, $operator); + continue; + } + + // Cast TranslatableMarkup to string. + $label = (string) $label; + + if ($operator == 'STARTS_WITH' && stripos($label, $q) === 0) { + $matches[$label] = [ + 'value' => $label, + 'label' => $label, + ]; + } + // Default to CONTAINS even when operator is empty. + elseif (stripos($label, $q) !== FALSE) { + $matches[$label] = [ + 'value' => $label, + 'label' => $label, + ]; + } + + } + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformHelpController.php b/web/modules/contrib/webform/src/Controller/WebformHelpController.php new file mode 100644 index 000000000..881460ff6 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformHelpController.php @@ -0,0 +1,96 @@ +helpManager = $help_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.help_manager') + ); + } + + /** + * Returns dedicated help video page. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param string $id + * The video id. + * + * @return array + * A renderable array containing a help video player page. + */ + public function index(Request $request, $id) { + $id = str_replace('-', '_', $id); + $video = $this->helpManager->getVideo($id); + if (!$video) { + throw new NotFoundHttpException(); + } + + $build = []; + if (is_array($video['content'])) { + $build['content'] = $video['content']; + } + else { + $build['content'] = [ + '#markup' => $video['content'], + ]; + } + if ($video['youtube_id']) { + $build['video'] = [ + '#theme' => 'webform_help_video_youtube', + '#youtube_id' => $video['youtube_id'], + ]; + } + return $build; + } + + /** + * Route title callback. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param string $id + * The id of the dedicated help section. + * + * @return string + * The dedicated help section's title. + */ + public function title(Request $request, $id) { + $id = str_replace('-', '_', $id); + $video = $this->helpManager->getVideo($id); + return (isset($video)) ? $video['title'] : $this->t('Watch video'); + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformOptionsController.php b/web/modules/contrib/webform/src/Controller/WebformOptionsController.php new file mode 100644 index 000000000..06df154a2 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformOptionsController.php @@ -0,0 +1,95 @@ +query->get('q'); + + // Make sure the current user can access this webform. + if (!$webform->access('view')) { + return new JsonResponse([]); + } + + // Get the webform element element. + $elements = $webform->getElementsInitializedAndFlattened(); + if (!isset($elements[$key])) { + return new JsonResponse([]); + } + + // Get the element's webform options. + $element = $elements[$key]; + $element['#options'] = $element['#autocomplete']; + $options = WebformOptions::getElementOptions($element); + if (empty($options)) { + return new JsonResponse([]); + } + + // 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, + ]; + } + } + } + + /** + * Route title callback. + * + * @param \Drupal\webform\WebformOptionsInterface $webform_options + * The webform options. + * + * @return string + * The webform options label as a render array. + */ + public function title(WebformOptionsInterface $webform_options) { + return $webform_options->label(); + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformPluginElementController.php b/web/modules/contrib/webform/src/Controller/WebformPluginElementController.php new file mode 100644 index 000000000..9bc0d23c1 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformPluginElementController.php @@ -0,0 +1,315 @@ +moduleHandler = $module_handler; + $this->elementInfo = $element_info; + $this->elementManager = $element_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('module_handler'), + $container->get('plugin.manager.element_info'), + $container->get('plugin.manager.webform.element') + ); + } + + /** + * {@inheritdoc} + */ + public function index() { + $webform_form_element_rows = []; + $element_rows = []; + + $default_properties = [ + // Element settings. + 'title', + 'description', + 'default_value', + // Form display. + 'title_display', + 'description_display', + 'field_prefix', + 'field_suffix', + // Form validation. + 'required', + 'required_error', + 'unique', + // Submission display. + 'format', + // Attributes. + 'wrapper_attributes', + 'attributes', + // Administration. + 'admin_title', + 'private', + // Flexbox. + 'flex', + // Conditional logic. + 'states', + // Element access. + 'access_create_roles', + 'access_create_users', + 'access_update_roles', + 'access_update_users', + 'access_view_roles', + 'access_view_users', + ];; + $default_properties = array_combine($default_properties, $default_properties); + + // Test element is only enabled if the Webform Devel and UI module are + // enabled. + $test_element_enabled = ($this->moduleHandler->moduleExists('webform_devel') && $this->moduleHandler->moduleExists('webform_ui')) ? TRUE : FALSE; + + // Define a default element used to get default properties. + $element = ['#type' => 'element']; + + $element_plugin_definitions = $this->elementInfo->getDefinitions(); + foreach ($element_plugin_definitions as $element_plugin_id => $element_plugin_definition) { + if ($this->elementManager->hasDefinition($element_plugin_id)) { + + /** @var \Drupal\webform\WebformElementInterface $webform_element */ + $webform_element = $this->elementManager->createInstance($element_plugin_id); + $webform_element_plugin_definition = $this->elementManager->getDefinition($element_plugin_id); + $webform_element_info = $webform_element->getInfo(); + + $parent_classes = WebformReflectionHelper::getParentClasses($webform_element, 'WebformElementBase'); + + $default_format = $webform_element->getItemDefaultFormat(); + $format_names = array_keys($webform_element->getItemFormats()); + $formats = array_combine($format_names, $format_names); + if (isset($formats[$default_format])) { + $formats[$default_format] = '' . $formats[$default_format] . ''; + } + + $related_types = $webform_element->getRelatedTypes($element); + + $webform_info_definitions = [ + 'input' => $webform_element->isInput($element), + 'container' => $webform_element->isContainer($element), + 'root' => $webform_element->isRoot(), + 'hidden' => $webform_element->isHidden(), + 'multiple' => $webform_element->supportsMultipleValues(), + 'multiline' => $webform_element->isMultiline($element), + 'states_wrapper' => $webform_element_plugin_definition['states_wrapper'], + ]; + $webform_info = []; + foreach ($webform_info_definitions as $key => $value) { + $webform_info[] = '' . $key . ': ' . ($value ? $this->t('Yes') : $this->t('No')); + } + + $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', + 'theme_wrappers' => (isset($webform_element_info['#theme_wrappers'])) ? implode('; ', $webform_element_info['#theme_wrappers']) : 'N/A', + ]; + $element_info = []; + foreach ($element_info_definitions as $key => $value) { + $element_info[] = '' . $key . ': ' . $value; + } + + $properties = []; + $element_default_properties = array_keys($webform_element->getDefaultProperties()); + foreach ($element_default_properties as $key => $value) { + if (!isset($default_properties[$value])) { + $properties[$key] = '#' . $value . ''; + unset($element_default_properties[$key]); + } + else { + $element_default_properties[$key] = '#' . $value; + } + } + $properties += $element_default_properties; + if (count($properties) >= 20) { + $properties = array_slice($properties, 0, 20) + ['...' => '...']; + } + + $operations = []; + if ($test_element_enabled) { + $operations['test'] = [ + 'title' => $this->t('Test'), + 'url' => new Url('webform.element_plugins.test', ['type' => $element_plugin_id]), + ]; + } + if ($api_url = $webform_element->getPluginApiUrl()) { + $operations['documentation'] = [ + 'title' => $this->t('API Docs'), + 'url' => $api_url, + ]; + } + $webform_form_element_rows[$element_plugin_id] = [ + 'data' => [ + new FormattableMarkup('
          @id
          ', ['@id' => $element_plugin_id]), + new FormattableMarkup('@label
          @description', ['@label' => $webform_element->getPluginLabel(), '@description' => $webform_element->getPluginDescription()]), + ['data' => ['#markup' => implode('
          → ', $parent_classes)], 'nowrap' => 'nowrap'], + ['data' => ['#markup' => implode('
          ', $webform_info)], 'nowrap' => 'nowrap'], + ['data' => ['#markup' => implode('
          ', $element_info)], 'nowrap' => 'nowrap'], + ['data' => ['#markup' => implode('
          ', $properties)]], + $formats ? ['data' => ['#markup' => '• ' . implode('
          • ', $formats)], 'nowrap' => 'nowrap'] : '', + $related_types ? ['data' => ['#markup' => '• ' . implode('
          • ', $related_types)], 'nowrap' => 'nowrap'] : '<' . $this->t('none') . '>', + $element_plugin_definition['provider'], + $webform_element_plugin_definition['provider'], + $operations ? ['data' => ['#type' => 'operations', '#links' => $operations]] : '', + ], + ]; + } + else { + $element_rows[$element_plugin_id] = [ + $element_plugin_id, + $element_plugin_definition['provider'], + ]; + } + } + + $build = []; + + $build['filter'] = [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by element name'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-element' => '.webform-element-plugin', + 'title' => $this->t('Enter a part of the element type to filter by.'), + 'autofocus' => 'autofocus', + ], + ]; + + ksort($webform_form_element_rows); + $build['webform_elements'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Name'), + $this->t('Label/Description'), + $this->t('Class hierarchy'), + $this->t('Webform info'), + $this->t('Element info'), + $this->t('Properties'), + $this->t('Formats'), + $this->t('Related'), + $this->t('Provided by'), + $this->t('Integrated by'), + $this->t('Operations'), + ], + '#rows' => $webform_form_element_rows, + '#attributes' => [ + 'class' => ['webform-element-plugin'], + ], + ]; + + ksort($element_rows); + $build['elements'] = [ + '#type' => 'details', + '#title' => $this->t('Additional elements'), + '#description' => $this->t('Below are elements that are available but do not have a Webform Element integration plugin.'), + 'table' => [ + '#type' => 'table', + '#header' => [ + $this->t('Name'), + $this->t('Provided by'), + ], + '#rows' => $element_rows, + '#sticky' => TRUE, + ], + ]; + + $all_translatable_properties = $this->elementManager->getTranslatableProperties(); + $all_properties = $this->elementManager->getAllProperties(); + foreach ($all_translatable_properties as $key => $value) { + $all_translatable_properties[$key] = [ + '#markup' => $value, + '#prefix' => '', + '#suffix' => '', + '#weight' => -10, + ]; + } + foreach ($all_properties as $key => $value) { + // Remove all composite properties. + if (strpos($key, '__')) { + unset($all_properties[$key]); + } + } + $build['properties'] = [ + '#type' => 'details', + '#title' => $this->t('Element properties'), + '#description' => $this->t('Below are all available element properties with translatable properties in bold.'), + 'list' => [ + '#theme' => 'item_list', + '#items' => $all_translatable_properties + $all_properties, + ], + ]; + + $build['#attached']['library'][] = 'webform/webform.admin'; + $build['#attached']['library'][] = 'webform/webform.form'; + + return $build; + } + + /** + * Get a class's name without its namespace. + * + * @param string $class + * A class. + * + * @return string + * The class's name without its namespace. + */ + protected function getClassName($class) { + $parts = preg_split('#\\\\#', $class); + return end($parts); + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformPluginExporterController.php b/web/modules/contrib/webform/src/Controller/WebformPluginExporterController.php new file mode 100644 index 000000000..d105c6279 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformPluginExporterController.php @@ -0,0 +1,72 @@ +pluginManager = $plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.webform.exporter') + ); + } + + /** + * {@inheritdoc} + */ + public function index() { + $definitions = $this->pluginManager->getDefinitions(); + $definitions = $this->pluginManager->getSortedDefinitions($definitions); + + $rows = []; + foreach ($definitions as $plugin_id => $definition) { + $rows[$plugin_id] = [ + $plugin_id, + $definition['label'], + $definition['description'], + $definition['provider'], + ]; + } + + ksort($rows); + return [ + '#type' => 'table', + '#header' => [ + $this->t('ID'), + $this->t('Label'), + $this->t('Description'), + $this->t('Provided by'), + ], + '#rows' => $rows, + '#sticky' => TRUE, + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformPluginHandlerController.php b/web/modules/contrib/webform/src/Controller/WebformPluginHandlerController.php new file mode 100644 index 000000000..c60923b01 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformPluginHandlerController.php @@ -0,0 +1,175 @@ +pluginManager = $plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.webform.handler') + ); + } + + /** + * {@inheritdoc} + */ + public function index() { + $definitions = $this->pluginManager->getDefinitions(); + $definitions = $this->pluginManager->getSortedDefinitions($definitions); + + $rows = []; + foreach ($definitions as $plugin_id => $definition) { + $rows[$plugin_id] = [ + $plugin_id, + $definition['label'], + $definition['description'], + $definition['category'], + ($definition['cardinality'] == -1) ? $this->t('Unlimited') : $definition['cardinality'], + $definition['results'] ? $this->t('Processed') : $this->t('Ignored'), + $definition['provider'], + ]; + } + + ksort($rows); + return [ + '#type' => 'table', + '#header' => [ + $this->t('ID'), + $this->t('Label'), + $this->t('Description'), + $this->t('Category'), + $this->t('Cardinality'), + $this->t('Results'), + $this->t('Provided by'), + ], + '#rows' => $rows, + '#sticky' => TRUE, + ]; + } + + /** + * Shows a list of webform handlers that can be added to a webform. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return array + * A render array as expected by the renderer. + */ + public function listHandlers(Request $request, WebformInterface $webform) { + $headers = [ + ['data' => $this->t('Handler'), 'width' => '20%'], + ['data' => $this->t('Description'), 'width' => '40%'], + ['data' => $this->t('Category'), 'width' => '20%'], + ['data' => $this->t('Operations'), 'width' => '20%'], + ]; + + $definitions = $this->pluginManager->getDefinitions(); + $definitions = $this->pluginManager->getSortedDefinitions($definitions); + + $rows = []; + foreach ($definitions as $plugin_id => $definition) { + // Skip email handler which has dedicated button. + if ($plugin_id == 'email') { + continue; + } + + // Check cardinality. + $cardinality = $definition['cardinality']; + $is_cardinality_unlimited = ($cardinality == WebformHandlerInterface::CARDINALITY_UNLIMITED); + $is_cardinality_reached = ($webform->getHandlers($plugin_id)->count() >= $cardinality); + if (!$is_cardinality_unlimited && $is_cardinality_reached) { + continue; + } + + $row = []; + $row['title']['data'] = [ + '#type' => 'inline_template', + '#template' => '
          {{ label }}
          ', + '#context' => [ + 'label' => $definition['label'], + ], + ]; + $row['description'] = [ + 'data' => [ + '#markup' => $definition['description'], + ], + ]; + $row['category'] = $definition['category']; + $links['add'] = [ + 'title' => $this->t('Add handler'), + 'url' => Url::fromRoute('entity.webform.handler.add_form', ['webform' => $webform->id(), 'webform_handler' => $plugin_id]), + 'attributes' => WebformDialogHelper::getModalDialogAttributes(800), + ]; + $row['operations']['data'] = [ + '#type' => 'operations', + '#links' => $links, + ]; + $rows[] = $row; + } + + $build['#attached']['library'][] = 'webform/webform.form'; + + $build['filter'] = [ + '#type' => 'search', + '#title' => $this->t('Filter'), + '#title_display' => 'invisible', + '#size' => 30, + '#placeholder' => $this->t('Filter by handler name'), + '#attributes' => [ + 'class' => ['webform-form-filter-text'], + 'data-element' => '.webform-handler-add-table', + 'title' => $this->t('Enter a part of the handler name to filter by.'), + 'autofocus' => 'autofocus', + ], + ]; + + $build['handlers'] = [ + '#type' => 'table', + '#header' => $headers, + '#rows' => $rows, + '#empty' => $this->t('No handler available.'), + '#attributes' => [ + 'class' => ['webform-handler-add-table'], + ], + ]; + + return $build; + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformResultsExportController.php b/web/modules/contrib/webform/src/Controller/WebformResultsExportController.php new file mode 100644 index 000000000..ca8083c36 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformResultsExportController.php @@ -0,0 +1,368 @@ +mimeTypeGuesser = $mime_type_guesser; + $this->submissionExporter = $webform_submission_exporter; + $this->requestHandler = $request_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('file.mime_type.guesser'), + $container->get('webform_submission.exporter'), + $container->get('webform.request') + ); + } + + /** + * Returns webform submission as a CSV. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return array|null|\Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response + * A response that renders or redirects to the CSV file. + */ + public function index(Request $request) { + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + $this->submissionExporter->setWebform($webform); + $this->submissionExporter->setSourceEntity($source_entity); + + $query = $request->query->all(); + unset($query['destination']); + if (isset($query['filename'])) { + $build = $this->formBuilder()->getForm('Drupal\webform\Form\WebformResultsExportForm'); + + // Redirect to file export. + $file_path = $this->submissionExporter->getFileTempDirectory() . '/' . $query['filename']; + if (file_exists($file_path)) { + $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, download the file here. This file may only be downloaded once.', [':href' => $file_url])); + $build['#attached']['html_head'][] = [ + [ + '#tag' => 'meta', + '#attributes' => [ + 'http-equiv' => 'refresh', + 'content' => '0; url=' . $file_url, + ], + ], + 'webform_results_export_download_file_refresh', + ]; + } + + return $build; + } + elseif ($query && empty($query['ajax_form'])) { + if (!empty($query['excluded_columns']) && is_string($query['excluded_columns'])) { + $excluded_columns = explode(',', $query['excluded_columns']); + $query['excluded_columns'] = array_combine($excluded_columns, $excluded_columns); + } + + $export_options = $query + $this->submissionExporter->getDefaultExportOptions(); + $this->submissionExporter->setExporter($export_options); + if ($this->submissionExporter->isBatch()) { + self::batchSet($webform, $source_entity, $export_options); + $route_name = $this->requestHandler->getRouteName($webform, $source_entity, 'webform.results_export'); + $route_parameters = $this->requestHandler->getRouteParameters($webform, $source_entity); + return batch_process(Url::fromRoute($route_name, $route_parameters)); + } + else { + $this->submissionExporter->generate(); + $file_path = $this->submissionExporter->getExportFilePath(); + return $this->downloadFile($file_path, $export_options['download']); + } + + } + else { + return $this->formBuilder()->getForm('Drupal\webform\Form\WebformResultsExportForm', $webform); + } + } + + /** + * Returns webform submission results as CSV file. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param string $filename + * CSV file name. + * + * @return array|\Symfony\Component\HttpFoundation\Response + * A response that renders or redirects to the CSV file. + */ + public function file(Request $request, $filename) { + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + $this->submissionExporter->setWebform($webform); + $this->submissionExporter->setSourceEntity($source_entity); + + $file_path = $this->submissionExporter->getFileTempDirectory() . '/' . $filename; + if (!file_exists($file_path)) { + $route_name = $this->requestHandler->getRouteName($webform, $source_entity, 'webform.results_export'); + $route_parameters = $this->requestHandler->getRouteParameters($webform, $source_entity); + $t_args = [ + ':href' => Url::fromRoute($route_name, $route_parameters)->toString(), + ]; + $build = [ + '#markup' => $this->t('No export file ready for download. The file may have already been downloaded by your browser. Visit the download export webform to create a new export.', $t_args), + ]; + return $build; + } + else { + return $this->downloadFile($file_path); + } + } + + /** + * Download generated CSV file. + * + * @param string $file_path + * The paths the generate CSV file. + * @param bool $download + * Download the generated CSV file. Default to TRUE. + * + * @return \Symfony\Component\HttpFoundation\Response + * A response object containing the CSV file. + */ + public function downloadFile($file_path, $download = TRUE) { + // Return the export file. + $contents = file_get_contents($file_path); + unlink($file_path); + + $content_type = $this->mimeTypeGuesser->guess($file_path); + + if ($download) { + $headers = [ + 'Content-Length' => strlen($contents), + 'Content-Type' => $content_type, + 'Content-Disposition' => 'attachment; filename="' . basename($file_path) . '"', + ]; + } + else { + if ($content_type != 'text/html') { + $content_type = 'text/plain'; + } + $headers = [ + 'Content-Length' => strlen($contents), + 'Content-Type' => $content_type . '; charset=utf-8', + ]; + } + + return new Response($contents, 200, $headers); + } + + /****************************************************************************/ + // 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 array $export_options + * An array of export 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, array $export_options) { + if (!empty($export_options['excluded_columns']) && is_string($export_options['excluded_columns'])) { + $excluded_columns = explode(',', $export_options['excluded_columns']); + $export_options['excluded_columns'] = array_combine($excluded_columns, $excluded_columns); + } + + /** @var \Drupal\webform\WebformSubmissionExporterInterface $submission_exporter */ + $submission_exporter = \Drupal::service('webform_submission.exporter'); + $submission_exporter->setWebform($webform); + $submission_exporter->setSourceEntity($source_entity); + $submission_exporter->setExporter($export_options); + + $parameters = [ + $webform, + $source_entity, + $export_options, + ]; + $batch = [ + 'title' => t('Exporting submissions'), + 'init_message' => t('Creating export file'), + 'error_message' => t('The export file could not be created because an error occurred.'), + 'operations' => [ + [['\Drupal\webform\Controller\WebformResultsExportController', 'batchProcess'], $parameters], + ], + 'finished' => ['\Drupal\webform\Controller\WebformResultsExportController', '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 array $export_options + * An associative array of export options. + * @param mixed|array $context + * The batch current context. + */ + public static function batchProcess(WebformInterface $webform, EntityInterface $source_entity = NULL, array $export_options, &$context) { + /** @var \Drupal\webform\WebformSubmissionExporterInterface $submission_exporter */ + $submission_exporter = \Drupal::service('webform_submission.exporter'); + $submission_exporter->setWebform($webform); + $submission_exporter->setSourceEntity($source_entity); + $submission_exporter->setExporter($export_options); + + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['current_sid'] = 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. + // @see https://www.drupal.org/node/2822023 + $context['results']['webform_id'] = $webform->id(); + $context['results']['source_entity_type'] = ($source_entity) ? $source_entity->getEntityTypeId() : NULL; + $context['results']['source_entity_id'] = ($source_entity) ? $source_entity->id() : NULL; + $context['results']['export_options'] = $export_options; + $submission_exporter->writeHeader(); + } + + // Write CSV records. + $query = $submission_exporter->getQuery(); + $query->condition('sid', $context['sandbox']['current_sid'], '>'); + $query->range(0, $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['message'] = t('Exported @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']; + } + } + + /** + * 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). + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * Redirect to download the exported results. + */ + public static function batchFinish($success, array $results, array $operations) { + $webform_id = $results['webform_id']; + $entity_type = $results['source_entity_type']; + $entity_id = $results['source_entity_id']; + + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = Webform::load($webform_id); + /** @var \Drupal\Core\Entity\EntityInterface|null $source_entity */ + $source_entity = ($entity_type && $entity_id) ? \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id) : NULL; + /** @var array $export_options */ + $export_options = $results['export_options']; + + /** @var \Drupal\webform\WebformSubmissionExporterInterface $submission_exporter */ + $submission_exporter = \Drupal::service('webform_submission.exporter'); + $submission_exporter->setWebform($webform); + $submission_exporter->setSourceEntity($source_entity); + $submission_exporter->setExporter($export_options); + + if (!$success) { + $file_path = $submission_exporter->getExportFilePath(); + @unlink($file_path); + $archive_path = $submission_exporter->getArchiveFilePath(); + @unlink($archive_path); + drupal_set_message(t('Finished with an error.')); + } + else { + $submission_exporter->writeFooter(); + + $filename = $submission_exporter->getExportFileName(); + + if ($submission_exporter->isArchive()) { + $submission_exporter->writeExportToArchive(); + $filename = $submission_exporter->getArchiveFileName(); + } + + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + $route_name = $request_handler->getRouteName($webform, $source_entity, 'webform.results_export'); + $route_parameters = $request_handler->getRouteParameters($webform, $source_entity); + $redirect_url = Url::fromRoute($route_name, $route_parameters, ['query' => ['filename' => $filename], 'absolute' => TRUE]); + return new RedirectResponse($redirect_url->toString()); + } + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformSubmissionController.php b/web/modules/contrib/webform/src/Controller/WebformSubmissionController.php new file mode 100644 index 000000000..fce706c4f --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformSubmissionController.php @@ -0,0 +1,144 @@ +requestHandler = $request_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.request') + ); + } + + /** + * Returns a webform submission in a specified format type. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param string $type + * The format type. + * + * @return array + * A render array representing a webform submission in a specified format + * type. + */ + public function index(WebformSubmissionInterface $webform_submission, $type) { + if ($type == 'default') { + $type = 'html'; + } + + $build = []; + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform_submission'); + // Navigation. + $build['navigation'] = [ + '#theme' => 'webform_submission_navigation', + '#webform_submission' => $webform_submission, + ]; + + // Information. + $build['information'] = [ + '#theme' => 'webform_submission_information', + '#webform_submission' => $webform_submission, + '#source_entity' => $source_entity, + ]; + + // Submission. + $build['submission'] = [ + '#theme' => 'webform_submission_' . $type, + '#webform_submission' => $webform_submission, + '#source_entity' => $source_entity, + ]; + + // Wrap plain text and YAML in CodeMirror view widget. + if (in_array($type, ['text', 'yaml'])) { + $build['submission'] = [ + '#theme' => 'webform_codemirror', + '#code' => $build['submission'], + '#type' => $type, + ]; + } + + $build['#attached']['library'][] = 'webform/webform.admin'; + + return $build; + } + + /** + * Toggle webform submission sticky. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response that toggle the sticky icon. + */ + public function sticky(WebformSubmissionInterface $webform_submission) { + // Toggle sticky. + $webform_submission->setSticky(!$webform_submission->isSticky())->save(); + + // Get state. + $state = $webform_submission->isSticky() ? 'on' : 'off'; + + $response = new AjaxResponse(); + $response->addCommand(new HtmlCommand( + '#webform-submission-' . $webform_submission->id() . '-sticky', + new FormattableMarkup('', ['@state' => $state]) + )); + return $response; + } + + /** + * Route title callback. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * The webform submission. + * @param bool $duplicate + * Flag indicating if submission is being duplicated. + * + * @return array + * The webform submission as a render array. + */ + public function title(WebformSubmissionInterface $webform_submission, $duplicate = FALSE) { + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform_submission'); + $t_args = [ + '@form' => ($source_entity) ? $source_entity->label() : $webform_submission->getWebform()->label(), + '@id' => $webform_submission->serial(), + ]; + + $title = $this->t('@form: Submission #@id', $t_args); + return ($duplicate) ? $this->t('Duplicate @title', ['@title' => $title]) : $title; + } + +} diff --git a/web/modules/contrib/webform/src/Controller/WebformTestController.php b/web/modules/contrib/webform/src/Controller/WebformTestController.php new file mode 100644 index 000000000..3c869e712 --- /dev/null +++ b/web/modules/contrib/webform/src/Controller/WebformTestController.php @@ -0,0 +1,102 @@ +requestHandler = $request_handler; + $this->generate = $submission_generate; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.request'), + $container->get('webform_submission.generate') + ); + } + + /** + * Returns a webform to add a new test submission to a webform. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return array + * The webform submission webform. + */ + public function testForm(Request $request) { + /** @var \Drupal\webform\WebformInterface $webform */ + /** @var \Drupal\Core\Entity\EntityInterface $source_entity */ + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + $values = []; + + // Set source entity type and id. + if ($source_entity) { + $values['entity_type'] = $source_entity->getEntityTypeId(); + $values['entity_id'] = $source_entity->id(); + } + + if ($request->query->get('webform_id') == $webform->id()) { + return $webform->getSubmissionForm($values); + } + + // Generate date. + $values['data'] = $this->generate->getData($webform); + + return $webform->getSubmissionForm($values); + } + + /** + * Route title callback. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform. + * + * @return string + * The webform label as a render array. + */ + public function title(WebformInterface $webform) { + /** @var \Drupal\webform\WebformInterface $webform */ + /** @var \Drupal\Core\Entity\EntityInterface $source_entity */ + list($webform, $source_entity) = $this->requestHandler->getWebformEntities(); + return $this->t('Testing %title webform', ['%title' => ($source_entity) ? $source_entity->label() : $webform->label()]); + } + +} diff --git a/web/modules/contrib/webform/src/Element/Webform.php b/web/modules/contrib/webform/src/Element/Webform.php new file mode 100644 index 000000000..512e2d426 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/Webform.php @@ -0,0 +1,44 @@ + [ + [$class, 'preRenderWebformElement'], + ], + '#webform' => NULL, + '#default_data' => [], + '#cache' => ['max-age' => 0], + ]; + } + + /** + * Webform element pre render callback. + */ + public static function preRenderWebformElement($element) { + $webform = ($element['#webform'] instanceof WebformInterface) ? $element['#webform'] : WebformEntity::load($element['#webform']); + if (!$webform || !$webform->access('submission_create')) { + return $element; + } + + $values = ['data' => $element['#default_data']]; + return $element + $webform->getSubmissionForm($values); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformAddress.php b/web/modules/contrib/webform/src/Element/WebformAddress.php new file mode 100644 index 000000000..d4f0a7c0c --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformAddress.php @@ -0,0 +1,46 @@ + 'textfield', + '#title' => t('Address'), + ]; + $elements['address_2'] = [ + '#type' => 'textfield', + '#title' => t('Address 2'), + ]; + $elements['city'] = [ + '#type' => 'textfield', + '#title' => t('City/Town'), + ]; + $elements['state_province'] = [ + '#type' => 'select', + '#title' => t('State/Province'), + '#options' => 'state_province_names', + ]; + $elements['postal_code'] = [ + '#type' => 'textfield', + '#title' => t('Zip/Postal Code'), + ]; + $elements['country'] = [ + '#type' => 'select', + '#title' => t('Country'), + '#options' => 'country_names', + ]; + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformAudioFile.php b/web/modules/contrib/webform/src/Element/WebformAudioFile.php new file mode 100644 index 000000000..8b32b1362 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformAudioFile.php @@ -0,0 +1,17 @@ + 'text/css', + 'html' => 'text/html', + 'javascript' => 'text/javascript', + 'text' => 'text/plain', + 'yaml' => 'text/x-yaml', + ]; + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#input' => TRUE, + '#mode' => 'text', + '#skip_validation' => FALSE, + '#cols' => 60, + '#rows' => 5, + '#resizable' => 'vertical', + '#process' => [ + [$class, 'processWebformCodeMirror'], + [$class, 'processAjaxForm'], + [$class, 'processGroup'], + ], + '#pre_render' => [ + [$class, 'preRenderWebformCodeMirror'], + [$class, 'preRenderGroup'], + ], + '#theme' => 'textarea', + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + 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'])); + } + // Convert empty YAML into an empty string. + if ($element['#default_value'] == '{ }') { + $element['#default_value'] = ''; + } + return $element['#default_value']; + } + return NULL; + } + + /** + * Processes a 'webform_codemirror' element. + */ + public static function processWebformCodeMirror(&$element, FormStateInterface $form_state, &$complete_form) { + // Check that mode is defined and valid, if not default to (plain) text. + if (empty($element['#mode']) || !isset(self::$modes[$element['#mode']])) { + $element['#mode'] = 'text'; + } + + // Set validation. + if (isset($element['#element_validate'])) { + $element['#element_validate'] = array_merge([[get_called_class(), 'validateWebformCodeMirror']], $element['#element_validate']); + } + else { + $element['#element_validate'] = [[get_called_class(), 'validateWebformCodeMirror']]; + } + + return $element; + } + + /** + * Prepares a #type 'webform_code' render element for theme_element(). + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #description, #size, #maxlength, + * #placeholder, #required, #attributes. + * + * @return array + * The $element with prepared variables ready for theme_element(). + */ + public static function preRenderWebformCodeMirror(array $element) { + static::setAttributes($element, ['js-webform-codemirror', 'webform-codemirror', $element['#mode']]); + $element['#attributes']['data-webform-codemirror-mode'] = static::getMode($element['#mode']); + $element['#attached']['library'][] = 'webform/webform.element.codemirror.' . $element['#mode']; + return $element; + } + + /** + * Webform element validation handler for #type 'webform_codemirror'. + */ + public static function validateWebformCodeMirror(&$element, FormStateInterface $form_state, &$complete_form) { + if ($errors = static::getErrors($element, $form_state, $complete_form)) { + $build = [ + 'title' => [ + '#markup' => t('%title is not valid.', ['%title' => (isset($element['#title']) ? $element['#title'] : t('YAML'))]), + ], + 'errors' => [ + '#theme' => 'item_list', + '#items' => $errors, + ], + ]; + $form_state->setError($element, \Drupal::service('renderer')->render($build)); + } + + if ($element['#mode'] == 'yaml' && (isset($element['#default_value']) && is_array($element['#default_value']))) { + // Handle rare case where single array value is not parsed correctly. + if (preg_match('/^- (.*?)\s*$/', $element['#value'], $match)) { + $value = [$match[1]]; + } + else { + $value = $element['#value'] ? Yaml::decode($element['#value']) : []; + } + $form_state->setValueForElement($element, $value); + } + } + + /** + * Get validation errors. + */ + protected static function getErrors(&$element, FormStateInterface $form_state, &$complete_form) { + if (!empty($element['#skip_validation'])) { + return NULL; + } + + 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('' . $element['#value'] . '')) { + return NULL; + } + + $errors = libxml_get_errors(); + libxml_clear_errors(); + if (!$errors) { + return NULL; + } + + $messages = []; + foreach ($errors as $error) { + $messages[] = $error->message; + } + return $messages; + + case 'yaml': + try { + $value = trim($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()]; + } + + default: + return NULL; + } + } + + /** + * Get the CodeMirror mode for specified type. + * + * @param string $mode + * Mode (text, html, or yaml). + * + * @return string + * The CodeMirror mode (aka mime type). + */ + public static function getMode($mode) { + return (isset(static::$modes[$mode])) ? static::$modes[$mode] : static::$modes['text']; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformCompositeBase.php b/web/modules/contrib/webform/src/Element/WebformCompositeBase.php new file mode 100644 index 000000000..e4a28b077 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformCompositeBase.php @@ -0,0 +1,184 @@ + TRUE, + '#process' => [ + [$class, 'processWebformComposite'], + [$class, 'processAjaxForm'], + ], + '#pre_render' => [ + [$class, 'preRenderCompositeFormElement'], + ], + '#theme' => str_replace('webform_', 'webform_composite_', $this->getPluginId()), + '#theme_wrappers' => ['container'], + '#title_display' => 'invisible', + '#required' => FALSE, + '#flexbox' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + $composite_elements = static::getCompositeElements(); + $default_value = []; + foreach ($composite_elements as $composite_key => $composite_element) { + if (isset($composite_element['#type']) && $composite_element['#type'] != 'label') { + $default_value[$composite_key] = ''; + } + } + + if ($input === FALSE) { + if (empty($element['#default_value']) || !is_array($element['#default_value'])) { + $element['#default_value'] = []; + } + return $element['#default_value'] + $default_value; + } + return (is_array($input)) ? $input + $default_value : $default_value; + } + + /** + * Get a renderable array of webform elements. + * + * @return array + * A renderable array of webform elements, containing the base properties + * for the composite's webform elements. + */ + public static function getCompositeElements() { + return []; + } + + /** + * {@inheritdoc} + */ + public static function preRenderCompositeFormElement($element) { + $element = CompositeFormElementTrait::preRenderCompositeFormElement($element); + + // Add class name to wrapper attributes. + $class_name = str_replace('_', '-', $element['#type']); + $element['#attributes']['class'][] = 'js-' . $class_name; + $element['#attributes']['class'][] = $class_name; + + return $element; + } + + /** + * Processes a composite webform element. + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + if (isset($element['#initialize'])) { + return $element; + } + + $element['#initialize'] = TRUE; + $element['#tree'] = TRUE; + $composite_elements = static::getCompositeElements(); + foreach ($composite_elements as $composite_key => &$composite_element) { + // Transfer '#{composite_key}_{property}' from main element to composite + // element. + foreach ($element as $property_key => $property_value) { + if (strpos($property_key, '#' . $composite_key . '__') === 0) { + $composite_property_key = str_replace('#' . $composite_key . '__', '#', $property_key); + $composite_element[$composite_property_key] = $property_value; + } + } + + if (isset($element['#value'][$composite_key])) { + $composite_element['#value'] = $element['#value'][$composite_key]; + } + + // Always set #access which is used to hide/show the elements container. + $composite_element += [ + '#access' => TRUE, + ]; + + // Never required hidden composite elements. + if ($composite_element['#access'] == FALSE) { + unset($composite_element['#required']); + } + + // Load options. + if (isset($composite_element['#options'])) { + $composite_element['#options'] = WebformOptionsEntity::getElementOptions($composite_element); + } + + // Handle #type specific customizations. + if (isset($composite_element['#type'])) { + switch ($composite_element['#type']) { + case 'tel': + // Add international phone library. + // Add internation library and classes. + if (!empty($composite_element['#international'])) { + $composite_element['#attached']['library'][] = 'webform/webform.element.telephone'; + $composite_element['#attributes']['class'][] = 'js-webform-telephone-international'; + $composite_element['#attributes']['class'][] = 'webform-webform-telephone-international'; + } + break; + + case 'select': + case 'webform_select_other': + // Always include an empty option, even if the composite element + // is not required. + // @see https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!Element!Select.php/class/Select/8.2.x + // Use placeholder as empty option. + if (!isset($composite_element['#empty_option'])) { + if (isset($composite_element['#placeholder'])) { + $composite_element['#empty_option'] = $composite_element['#placeholder']; + } + elseif (empty($composite_element['#required'])) { + $composite_element['#empty_option'] = t('- None -'); + } + } + break; + } + } + } + + $element += $composite_elements; + $element['#element_validate'] = [[get_called_class(), 'validateWebformComposite']]; + + if (!empty($element['#flexbox'])) { + $element['#attached']['library'][] = 'webform/webform.element.flexbox'; + } + + return $element; + } + + /** + * Validates a composite element. + */ + public static function validateWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + + // Validate required composite elements. + $composite_elements = static::getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + if (!empty($element[$composite_key]['#required']) && $value[$composite_key] == '') { + if (isset($element[$composite_key]['#title'])) { + $form_state->setError($element[$composite_key], t('@name field is required.', ['@name' => $element[$composite_key]['#title']])); + } + } + } + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformContact.php b/web/modules/contrib/webform/src/Element/WebformContact.php new file mode 100644 index 000000000..99a66c2c5 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformContact.php @@ -0,0 +1,37 @@ + 'textfield', + '#title' => t('Name'), + ]; + $elements['company'] = [ + '#type' => 'textfield', + '#title' => t('Company'), + ]; + $elements['email'] = [ + '#type' => 'email', + '#title' => t('Email'), + ]; + $elements['phone'] = [ + '#type' => 'tel', + '#title' => t('Phone'), + ]; + $elements += parent::getCompositeElements(); + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformCreditCard.php b/web/modules/contrib/webform/src/Element/WebformCreditCard.php new file mode 100644 index 000000000..c144b4f87 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformCreditCard.php @@ -0,0 +1,68 @@ + 'webform_message', + '#message_type' => 'warning', + '#message_message' => t('The credit card element is experimental and insecure because it stores submitted information as plain text.'), + ]; + $elements['name'] = [ + '#type' => 'textfield', + '#title' => t("Name on Card"), + ]; + $elements['type'] = [ + '#type' => 'select', + '#title' => t('Type of Card'), + '#options' => 'creditcard_codes', + ]; + $elements['number'] = [ + '#type' => 'webform_creditcard_number', + '#title' => t('Card Number'), + ]; + $elements['civ'] = [ + '#type' => 'number', + '#title' => t('CIV Number'), + '#min' => 1, + '#size' => 4, + '#maxlength' => 4, + '#test' => [111, 222, 333], + ]; + $elements['expiration'] = [ + '#type' => 'label', + '#title' => t('Expiration Date'), + ]; + $elements['expiration_month'] = [ + '#title' => t('Expiration Month'), + '#title_display' => 'invisible', + '#type' => 'select', + '#options' => array_combine($month_options, $month_options), + '#prefix' => '
          ', + ]; + $elements['expiration_year'] = [ + '#title' => t('Expiration Year'), + '#title_display' => 'invisible', + '#type' => 'select', + '#options' => array_combine($year_options, $year_options), + '#suffix' => '
          ', + ]; + + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformCreditCardNumber.php b/web/modules/contrib/webform/src/Element/WebformCreditCardNumber.php new file mode 100644 index 000000000..81756b50a --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformCreditCardNumber.php @@ -0,0 +1,122 @@ + TRUE, + '#size' => self::CREDITCARD_MAX_LENGTH, + '#maxlength' => self::CREDITCARD_MAX_LENGTH, + '#autocomplete_route_name' => FALSE, + '#process' => [ + [$class, 'processAutocomplete'], + [$class, 'processAjaxForm'], + [$class, 'processPattern'], + ], + '#element_validate' => [ + [$class, 'validateWebformCreditCardNumber'], + ], + '#pre_render' => [ + [$class, 'preRenderWebformCreditCardNumber'], + ], + '#theme' => 'input__creditcard_number', + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * Webform element validation handler for #type 'creditcard_number'. + */ + public static function validateWebformCreditCardNumber(&$element, FormStateInterface $form_state, &$complete_form) { + $value = trim($element['#value']); + $form_state->setValueForElement($element, $value); + + if ($value !== '' && !self::validCreditCardNumber($value)) { + $form_state->setError($element, t('The credit card number is not valid.')); + } + } + + /** + * Validation rule for credit card number. + * + * Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org + * This code has been released into the public domain, however please + * give credit to the original author where possible. + * + * @param string $number + * A credit card number. + * + * @return bool + * TRUE is credit card number is valid. + * + * @see: http://stackoverflow.com/questions/174730/what-is-the-best-way-to-validate-a-credit-card-in-php + */ + public static function validCreditCardNumber($number) { + // If number is not 15 or 16 digits return FALSE. + if (!preg_match('/^\d{15,16}$/', $number)) { + return FALSE; + } + + // Set the string length and parity. + $number_length = strlen($number); + $parity = $number_length % 2; + + // Loop through each digit and do the maths. + $total = 0; + for ($i = 0; $i < $number_length; $i++) { + $digit = $number[$i]; + // Multiply alternate digits by two. + if ($i % 2 == $parity) { + $digit *= 2; + // If the sum is two digits, add them together (in effect). + if ($digit > 9) { + $digit -= 9; + } + } + // Total up the digits. + $total += $digit; + } + + // If the total mod 10 equals 0, the number is valid. + return ($total % 10 == 0) ? TRUE : FALSE; + } + + /** + * Prepares a #type 'creditcard_number' render element for theme_element(). + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #description, #size, #maxlength, + * #placeholder, #required, #attributes. + * + * @return array + * The $element with prepared variables ready for theme_element(). + */ + public static function preRenderWebformCreditCardNumber(array $element) { + $element['#attributes']['type'] = 'text'; + Element::setAttributes($element, ['id', 'name', 'value', 'size', 'maxlength', 'placeholder']); + static::setAttributes($element, ['form-textfield', 'form-creditcard-number']); + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformDocumentFile.php b/web/modules/contrib/webform/src/Element/WebformDocumentFile.php new file mode 100644 index 000000000..59239cc92 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformDocumentFile.php @@ -0,0 +1,12 @@ + TRUE, + '#process' => [ + [$class, 'processWebformElementAttributes'], + ], + '#theme_wrappers' => ['container'], + '#classes' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + $element += ['#default_value' => []]; + $element['#default_value'] += [ + 'class' => [], + 'style' => '', + ]; + return NULL; + } + + /** + * Processes element attributes. + */ + public static function processWebformElementAttributes(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + // Determine what type of HTML element the attributes are being applied to. + $type = t('element'); + $types = [preg_quote(t('webform')), preg_quote(t('link')), preg_quote(t('button'))]; + if (preg_match('/\b(' . implode('|', $types) . ')\b/i', $element['#title'], $match)) { + $type = $match[1]; + } + + $t_args = [ + '@title' => $element['#title'], + '@type' => Unicode::strtolower($type), + ]; + + // Class. + $element['#classes'] = trim($element['#classes']); + if ($element['#classes']) { + $classes = preg_split('/\r?\n/', $element['#classes']); + $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), + '#multiple' => TRUE, + '#options' => [WebformSelectOther::OTHER_OPTION => t('custom...')] + array_combine($classes, $classes), + '#other__option_delimiter' => ' ', + '#attributes' => [ + 'class' => [ + 'js-webform-select2', + 'webform-select2', + 'js-' . $element['#id'] . '-attributes-style', + ], + ], + '#attached' => ['library' => ['webform/webform.element.select2']], + '#default_value' => $element['#default_value']['class'], + ]; + + // ISSUE: + // Nested element with #element_validate callback that alter an + // element's value can break the returned value. + // + // WORKAROUND: + // Manually process the 'webform_select_other' element. + WebformSelectOther::valueCallback($element['class'], FALSE, $form_state); + WebformSelectOther::processWebformOther($element['class'], $form_state, $complete_form); + + $element['class']['#type'] = 'item'; + unset($element['class']['#element_validate']); + } + else { + $element['class'] = [ + '#type' => 'textfield', + '#title' => t('@title CSS classes', $t_args), + '#description' => t("Apply classes to the @type.", $t_args), + '#default_value' => implode(' ', $element['#default_value']['class']), + ]; + } + + // Custom options. + $element['custom'] = [ + '#type' => 'texfield', + '#placeholder' => t('Enter custom classes...'), + '#states' => [ + 'visible' => [ + 'select.js-' . $element['#id'] . '-attributes-style' => ['value' => '_custom_'], + ], + ], + '#error_no_message' => TRUE, + '#default_value' => '', + ]; + + // Style. + $element['style'] = [ + '#type' => 'textfield', + '#title' => t('@title CSS style', $t_args), + '#description' => t('Apply custom styles to the @type.', $t_args), + '#default_value' => $element['#default_value']['style'], + ]; + + // Attributes. + $attributes = $element['#default_value']; + unset($attributes['class'], $attributes['style']); + $element['attributes'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#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)), + ]; + + // Apply custom properties. Typically used for descriptions. + foreach ($element as $key => $value) { + if (strpos($key, '__') !== FALSE) { + list($element_key, $property_key) = explode('__', ltrim($key, '#')); + $element[$element_key]["#$property_key"] = $value; + } + } + + // Set validation. + if (isset($element['#element_validate'])) { + $element['#element_validate'] = array_merge([[get_called_class(), 'validateWebformElementAttributes']], $element['#element_validate']); + } + else { + $element['#element_validate'] = [[get_called_class(), 'validateWebformElementAttributes']]; + } + + return $element; + } + + /** + * Validates element attributes. + */ + public static function validateWebformElementAttributes(&$element, FormStateInterface $form_state, &$complete_form) { + $values = $element['#value']; + + $attributes = []; + + if ($values['class']) { + if (isset($element['class']['select'])) { + $class = $element['class']['select']['#value']; + $class_other = $element['class']['other']['#value']; + if (isset($class[WebformSelectOther::OTHER_OPTION])) { + unset($class[WebformSelectOther::OTHER_OPTION]); + $class[$class_other] = $class_other; + } + if ($class) { + $attributes['class'] = array_values($class); + } + } + else { + $attributes['class'] = [$values['class']]; + } + } + + if ($values['style']) { + $attributes['style'] = $values['style']; + } + + if (!empty($values['attributes'])) { + $attributes += Yaml::decode($values['attributes']); + } + + $form_state->setValueForElement($element['class'], NULL); + $form_state->setValueForElement($element['style'], NULL); + $form_state->setValueForElement($element['attributes'], NULL); + $form_state->setValueForElement($element, $attributes); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformElementMultiple.php b/web/modules/contrib/webform/src/Element/WebformElementMultiple.php new file mode 100644 index 000000000..08cdd0ad9 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformElementMultiple.php @@ -0,0 +1,137 @@ + TRUE, + '#min' => 1, + '#process' => [ + [$class, 'processWebformElementMultiple'], + ], + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (empty($element['#default_value'])) { + return 1; + } + elseif ($element['#default_value'] === TRUE) { + return WebformMultiple::CARDINALITY_UNLIMITED; + } + else { + return $element['#default_value']; + } + } + + return NULL; + } + + /** + * Processes element multiple. + */ + public static function processWebformElementMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + $cardinality = $element['#value']; + + $element['#tree'] = TRUE; + + $element['container'] = [ + '#type' => 'container', + '#attributes' => ['class' => [ + 'container-inline', + ]], + ]; + + $element['container']['cardinality'] = [ + '#type' => 'select', + '#title' => t('Allowed number of values'), + '#title_display' => 'invisible', + '#options' => [ + 'number' => t('Limited'), + WebformMultiple::CARDINALITY_UNLIMITED => t('Unlimited'), + ], + '#default_value' => ($cardinality == WebformMultiple::CARDINALITY_UNLIMITED) ? WebformMultiple::CARDINALITY_UNLIMITED : 'number', + ]; + $element['container']['cardinality_number'] = [ + '#type' => 'number', + '#default_value' => $cardinality != WebformMultiple::CARDINALITY_UNLIMITED ? $cardinality : $element['#min'], + '#min' => $element['#min'], + '#title' => t('Limit'), + '#title_display' => 'invisible', + '#size' => 2, + '#states' => [ + 'visible' => [ + ':input[data-drupal-selector="edit-' . implode('-', $element['#parents']) . '-container-cardinality"]' => ['value' => 'number'], + ], + ], + ]; + + // Set disabled + if (!empty($element['#disabled'])) { + $element['container']['cardinality']['#disabled'] = TRUE; + $element['container']['cardinality_number']['#disabled'] = TRUE; + } + + // Set validation. + if (isset($element['#element_validate'])) { + $element['#element_validate'] = array_merge([[get_called_class(), 'validateWebformElementMultiple']], $element['#element_validate']); + } + else { + $element['#element_validate'] = [[get_called_class(), 'validateWebformElementMultiple']]; + } + + return $element; + } + + /** + * Validates element multiple. + */ + public static function validateWebformElementMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + if (!empty($element['#disabled'])) { + $multiple = $element['#default_value']; + } + else { + $cardinality = $element['#value']['container']['cardinality']; + $cardinality_number = (int) $element['#value']['container']['cardinality_number']; + + if ($cardinality == WebformMultiple::CARDINALITY_UNLIMITED) { + $multiple = TRUE; + } + elseif ($cardinality_number === 1) { + $multiple = FALSE; + } + else { + $multiple = $cardinality_number; + } + } + + $form_state->setValueForElement($element['container']['cardinality'], NULL); + $form_state->setValueForElement($element['container']['cardinality_number'], NULL); + $form_state->setValueForElement($element, $multiple); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformElementOptions.php b/web/modules/contrib/webform/src/Element/WebformElementOptions.php new file mode 100644 index 000000000..d8a8d32bb --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformElementOptions.php @@ -0,0 +1,179 @@ + TRUE, + '#likert' => FALSE, + '#process' => [ + [$class, 'processWebformElementOptions'], + [$class, 'processAjaxForm'], + ], + '#theme_wrappers' => ['form_element'], + '#custom__type' => 'webform_options', + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (isset($element['#default_value'])) { + if (is_string($element['#default_value'])) { + return (WebformOptionsEntity::load($element['#default_value'])) ? $element['#default_value'] : []; + } + else { + return $element['#default_value']; + } + } + else { + return []; + } + } + elseif (!empty($input['options'])) { + return $input['options']; + } + elseif (isset($input['custom']['options'])) { + return $input['custom']['options']; + } + else { + return []; + } + } + + /** + * Processes a webform element options element. + */ + public static function processWebformElementOptions(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + // Predefined options. + // @see (/admin/structure/webform/settings/options/manage) + $options = []; + $webform_options = WebformOptionsEntity::loadMultiple(); + foreach ($webform_options as $id => $webform_option) { + // Filter likert options for answers to the likert element. + if ($element['#likert'] && strpos($id, 'likert_') !== 0) { + continue; + } + $options[$id] = $webform_option->label(); + } + asort($options); + + $t_args = [ + '@type' => ($element['#likert']) ? t('answers') : t('options'), + ':href' => Url::fromRoute('entity.webform_options.collection')->toString(), + ]; + + // Select options. + $element['options'] = [ + '#type' => 'select', + '#description' => t('Please select predefined @type or enter custom @type.', $t_args), + '#options' => [ + self::CUSTOM_OPTION => t('Custom...'), + ] + $options, + '#attributes' => [ + 'class' => ['js-' . $element['#id'] . '-options'], + ], + '#error_no_message' => TRUE, + '#default_value' => (isset($element['#default_value']) && !is_array($element['#default_value'])) ? $element['#default_value'] : '', + ]; + + // Custom options. + if ($element['#custom__type'] === 'webform_multiple') { + $element['custom'] = [ + '#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'] : [], + ]; + } + else { + $element['custom'] = [ + '#type' => 'webform_options', + '#title' => $element['#title'], + '#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, + '#default_value' => (isset($element['#default_value']) && !is_string($element['#default_value'])) ? $element['#default_value'] : [], + ]; + } + + $element['#element_validate'] = [[get_called_class(), 'validateWebformElementOptions']]; + + return $element; + } + + /** + * Validates a webform element options element. + */ + public static function validateWebformElementOptions(&$element, FormStateInterface $form_state, &$complete_form) { + $options_value = NestedArray::getValue($form_state->getValues(), $element['options']['#parents']); + $custom_value = NestedArray::getValue($form_state->getValues(), $element['custom']['#parents']); + + $value = $options_value; + if ($options_value == self::CUSTOM_OPTION) { + try { + $value = (is_string($custom_value)) ? Yaml::decode($custom_value) : $custom_value; + } + catch (\Exception $exception) { + // Do nothing since the 'webform_codemirror' element will have already + // captured the validation error. + } + } + + $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); + if ($element['#required'] && empty($value) && $has_access) { + if (isset($element['#required_error'])) { + $form_state->setError($element, $element['#required_error']); + } + elseif (isset($element['#title'])) { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + else { + $form_state->setError($element); + } + } + + $form_state->setValueForElement($element['options'], NULL); + $form_state->setValueForElement($element['custom'], NULL); + $form_state->setValueForElement($element, $value); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformElementStates.php b/web/modules/contrib/webform/src/Element/WebformElementStates.php new file mode 100644 index 000000000..9c8600a3f --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformElementStates.php @@ -0,0 +1,720 @@ + TRUE, + '#selector_options' => [], + '#empty_states' => 3, + '#process' => [ + [$class, 'processWebformStates'], + ], + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (isset($element['#default_value'])) { + if (is_string($element['#default_value'])) { + $default_value = Yaml::decode($element['#default_value']); + } + else { + $default_value = $element['#default_value'] ?: []; + } + return self::convertFormApiStatesToStatesArray($default_value); + } + else { + return []; + } + } + elseif (is_array($input) && isset($input['states'])) { + return (is_string($input['states'])) ? Yaml::decode($input['states']) : self::convertFormValuesToStatesArray($input['states']); + } + else { + return []; + } + } + + /** + * 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\WebformElementBase::form. + $element += [ + '#state_options' => [ + 'enabled' => t('Enabled'), + 'disabled' => t('Disabled'), + 'required' => t('Required'), + 'optional' => t('Optional'), + 'visible' => t('Visible'), + 'invisible' => t('Invisible'), + 'checked' => t('Checked'), + 'unchecked' => t('Unchecked'), + 'expanded' => t('Expanded'), + 'collapsed' => t('Collapsed'), + ], + '#trigger_options' => [ + 'empty' => t('Empty'), + 'filled' => t('Filled'), + 'checked' => t('Checked'), + 'unchecked' => t('Unchecked'), + 'expanded' => t('Expanded'), + 'collapsed' => t('Collapsed'), + 'value' => t('Value is'), + ], + ]; + + $element['#tree'] = TRUE; + + // Add validate callback that extracts the associative array of states. + $element['#element_validate'] = [[get_called_class(), 'validateWebformElementStates']]; + + // For customized #states display a CodeMirror YAML editor. + if ($warning_message = self::isDefaultValueCustomizedFormApiStates($element)) { + $warning_message .= ' ' . t('Form API #states must be manually entered.'); + $element['messages'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $warning_message, + ]; + $element['states'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#default_value' => WebformYaml::tidy(Yaml::encode($element['#default_value'])), + '#description' => t('Learn more about Drupal\'s Form API #states.', [':href' => 'https://www.lullabot.com/articles/form-api-states']), + ]; + return $element; + } + + $table_id = implode('_', $element['#parents']) . '_table'; + + // Store the number of rows. + $storage_key = self::getStorageKey($element, 'number_of_rows'); + if ($form_state->get($storage_key) === NULL) { + if (empty($element['#default_value']) || !is_array($element['#default_value'])) { + $number_of_rows = 2; + } + else { + $number_of_rows = count($element['#default_value']); + } + $form_state->set($storage_key, $number_of_rows); + } + $number_of_rows = $form_state->get($storage_key); + + // DEBUG: Disable AJAX callback by commenting out the below callback and + // wrapper. + $ajax_settings = [ + 'callback' => [get_called_class(), 'ajaxCallback'], + 'wrapper' => $table_id, + ]; + + // Build header. + $header = [ + ['data' => t('State'), 'width' => '20%'], + ['data' => t('Element/Selector'), 'width' => '45%'], + ['data' => t('Trigger'), 'width' => '20%'], + ['data' => t('Value'), 'width' => '10%'], + ['data' => ''], + ]; + + // Get states and number of rows. + if (($form_state->isRebuilding())) { + $states = $element['#value']; + } + else { + $states = (isset($element['#default_value'])) ? self::convertFormApiStatesToStatesArray($element['#default_value']) : []; + } + + // Build state and conditions rows. + $row_index = 0; + $rows = []; + foreach ($states as $state_settings) { + $rows[$row_index] = self::buildStateRow($element, $state_settings, $table_id, $row_index, $ajax_settings); + $row_index++; + foreach ($state_settings['conditions'] as $condition) { + $rows[$row_index] = self::buildConditionRow($element, $condition, $table_id, $row_index, $ajax_settings); + $row_index++; + } + } + + // Generator empty state with conditions rows. + if ($row_index < $number_of_rows) { + $rows[$row_index] = self::buildStateRow($element, [], $table_id, $row_index, $ajax_settings);; + $row_index++; + while ($row_index < $number_of_rows) { + $rows[$row_index] = self::buildConditionRow($element, [], $table_id, $row_index, $ajax_settings); + $row_index++; + } + } + + // Build table. + $element['states'] = [ + '#prefix' => '
          ', + '#suffix' => '
          ', + '#type' => 'table', + '#header' => $header, + ] + $rows; + + // Build add state action. + $element['add'] = [ + '#type' => 'submit', + '#value' => t('Add another state'), + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'addStateSubmit']], + '#ajax' => $ajax_settings, + '#name' => $table_id . '_add', + ]; + + $element['#attached']['library'][] = 'webform/webform.element.states'; + $element['#attached']['library'][] = 'webform/webform.element.select2'; + + return $element; + } + + /** + * Build state row. + * + * @param array $element + * The webform element. + * @param array $state + * The state. + * @param string $table_id + * The element's table id. + * @param int $row_index + * The row index. + * @param array $ajax_settings + * An array containing AJAX callback settings. + * + * @return array + * A render array containing a state table row. + */ + protected static function buildStateRow(array $element, array $state, $table_id, $row_index, array $ajax_settings) { + $state += ['state' => '', 'operator' => 'and']; + $row = [ + '#attributes' => [ + 'class' => ['webform-states-table--state'], + ], + ]; + $row['state'] = [ + '#type' => 'select', + '#options' => $element['#state_options'], + '#default_value' => $state['state'], + '#empty_option' => '', + '#empty_value' => '', + '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']], + ]; + $row['operator'] = [ + '#type' => 'select', + '#options' => [ + 'and' => t('All'), + 'or' => t('Any'), + ], + '#default_value' => $state['operator'], + '#field_prefix' => t('if'), + '#field_suffix' => t('of the following is met:'), + '#wrapper_attributes' => ['colspan' => 3, 'align' => 'left'], + ]; + $row['operations'] = self::buildOperations($table_id, $row_index, $ajax_settings); + return $row; + } + + /** + * Build condition row. + * + * @param array $element + * The webform element. + * @param array $condition + * The condition. + * @param string $table_id + * The element's table id. + * @param int $row_index + * The row index. + * @param array $ajax_settings + * An array containing AJAX callback settings. + * + * @return array + * A render array containing a condition table row. + */ + protected static function buildConditionRow(array $element, array $condition, $table_id, $row_index, array $ajax_settings) { + $condition += ['selector' => '', 'trigger' => '', 'value' => '']; + + $element_name = $element['#name']; + $trigger_selector = ":input[name=\"{$element_name}[states][{$row_index}][trigger]\"]"; + + $row = [ + '#attributes' => [ + 'class' => ['webform-states-table--condition'], + ], + ]; + $row['state'] = []; + $row['selector'] = [ + '#type' => 'webform_select_other', + '#options' => $element['#selector_options'], + '#default_value' => $condition['selector'], + '#empty_option' => '', + '#empty_value' => '', + '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']], + ]; + $row['trigger'] = [ + '#type' => 'select', + '#options' => $element['#trigger_options'], + '#default_value' => $condition['trigger'], + '#empty_option' => '', + '#empty_value' => '', + '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']], + ]; + $row['value'] = [ + '#type' => 'textfield', + '#title' => t('Value'), + '#title_display' => 'invisible', + '#size' => 25, + '#default_value' => $condition['value'], + '#states' => [ + 'visible' => [ + $trigger_selector => ['value' => 'value'], + ], + ], + ]; + $row['operations'] = self::buildOperations($table_id, $row_index, $ajax_settings); + return $row; + } + + /** + * Build a state's operations. + * + * @param string $table_id + * The option element's table id. + * @param int $row_index + * The option's row index. + * @param array $ajax_settings + * An array containing AJAX callback settings. + * + * @return array + * A render array containing state operations.. + */ + protected static function buildOperations($table_id, $row_index, array $ajax_settings) { + $operations = []; + $operations['add'] = [ + '#type' => 'image_button', + '#src' => drupal_get_path('module', 'webform') . '/images/icons/plus.svg', + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'addConditionSubmit']], + '#ajax' => $ajax_settings, + '#row_index' => $row_index, + '#name' => $table_id . '_add_' . $row_index, + ]; + $operations['remove'] = [ + '#type' => 'image_button', + '#src' => drupal_get_path('module', 'webform') . '/images/icons/ex.svg', + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'removeRowSubmit']], + '#ajax' => $ajax_settings, + '#row_index' => $row_index, + '#name' => $table_id . '_remove_' . $row_index, + ]; + return $operations; + } + + /****************************************************************************/ + // Callbacks. + /****************************************************************************/ + + /** + * Webform submission handler for adding another state. + * + * @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 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)); + + $values = $element['states']['#value']; + + // Add new state and condition. + $values[] = [ + 'state' => '', + 'operator' => 'and', + ]; + $values[] = [ + 'selector' => ['select' => '', 'other' => ''], + 'trigger' => '', + 'value' => '', + ]; + + // Update element's #value. + $form_state->setValueForElement($element['states'], $values); + NestedArray::setValue($form_state->getUserInput(), $element['states']['#parents'], $values); + + // Update the number of rows. + $form_state->set(self::getStorageKey($element, 'number_of_rows'), count($values)); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission handler for adding another condition. + * + * @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 addConditionSubmit(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, -4)); + + // The $row_index is not sequential so we need to rebuild the value instead + // of just using an array_slice(). + $row_index = $button['#row_index']; + $values = []; + foreach ($element['states']['#value'] as $index => $value) { + $values[] = $value; + if ($index == $row_index) { + $values[] = ['selector' => '', 'trigger' => '', 'value' => '']; + } + } + + // Reset values. + $values = array_values($values); + + // Set values. + $form_state->setValueForElement($element['states'], $values); + NestedArray::setValue($form_state->getUserInput(), $element['states']['#parents'], $values); + + // Update the number of rows. + $form_state->set(self::getStorageKey($element, 'number_of_rows'), count($values)); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission handler for removing a state or condition. + * + * @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 removeRowSubmit(array &$form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4)); + + $row_index = $button['#row_index']; + $values = $element['states']['#value']; + + if (isset($values[$row_index]['state'])) { + // Remove state. + do { + unset($values[$row_index]); + $row_index++; + } while (isset($values[$row_index]) && !isset($values[$row_index]['state'])); + } + else { + // Remove condition. + unset($values[$row_index]); + } + + // Reset values. + $values = array_values($values); + + // Set values. + $form_state->setValueForElement($element['states'], $values); + NestedArray::setValue($form_state->getUserInput(), $element['states']['#parents'], $values); + + // Update the number of rows. + $form_state->set(self::getStorageKey($element, 'number_of_rows'), count($values)); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission AJAX callback the returns the states table. + */ + public static function ajaxCallback(array &$form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + $parent_length = (isset($button['#row_index'])) ? -4 : -1; + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, $parent_length)); + return $element['states']; + } + + /** + * Validates webform states element. + */ + public static function validateWebformElementStates(&$element, FormStateInterface $form_state, &$complete_form) { + if (isset($element['states']['#value']) && is_string($element['states']['#value'])) { + $states = Yaml::decode($element['states']['#value']); + } + else { + $states = self::convertFormValuesToFormApiStates($element['states']['#value']); + } + $form_state->setValueForElement($element, NULL); + $form_state->setValueForElement($element, $states); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Get unique key used to store the number of options for an element. + * + * @param array $element + * An element. + * @param $name + * The name. + * + * @return string + * A unique key used to store the number of options for an element. + */ + protected static function getStorageKey(array $element, $name) { + return 'webform_states__' . $element['#name'] . '__' . $name; + } + + /****************************************************************************/ + // Convert functions. + /****************************************************************************/ + + /** + * Convert Form API #states to states array. + * + * @param array $fapi_states + * An associative array containing Form API #states. + * + * @return array + * An associative array of states. + */ + protected static function convertFormApiStatesToStatesArray(array $fapi_states) { + $index = 0; + $states = []; + foreach ($fapi_states as $state => $conditions) { + $states[$index] = [ + 'state' => $state, + 'operator' => 'and', + 'conditions' => [], + ]; + + foreach ($conditions as $condition_key => $condition_value) { + if (is_string($condition_key)) { + $states[$index]['conditions'][] = [ + 'selector' => $condition_key, + 'trigger' => key($condition_value), + 'value' => reset($condition_value), + ]; + } + elseif (is_string($condition_value)) { + $states[$index]['operator'] = $condition_value; + } + else { + foreach ($condition_value as $subcondition_key => $subcondition_value) { + $states[$index]['conditions'][] = [ + 'selector' => $subcondition_key, + 'trigger' => key($subcondition_value), + 'value' => reset($subcondition_value), + ]; + } + } + } + $index++; + } + return $states; + } + + /** + * Convert states array to Form API #states. + * + * @param array $states_array + * An associative array containing states. + * + * @return array + * An associative array of states. + */ + protected static function convertStatesArrayToFormApiStates(array $states_array = []) { + $states = []; + foreach ($states_array as $state_array) { + if ($state = $state_array['state']) { + $operator = $state_array['operator']; + $conditions = $state_array['conditions']; + if (count($conditions) === 1) { + $condition = reset($conditions); + $selector = $condition['selector']; + $trigger = $condition['trigger']; + if ($selector && $trigger) { + $value = $condition['value'] ?: TRUE; + } + else { + $value = ''; + } + $states[$state][$selector][$trigger] = $value; + } + else { + foreach ($state_array['conditions'] as $index => $condition) { + $selector = $condition['selector']; + $trigger = $condition['trigger']; + $value = $condition['value'] ?: TRUE; + if ($selector && $trigger) { + if ($operator == 'or') { + if ($index !== 0) { + $states[$state][] = $operator; + } + $states[$state][] = [ + $selector => [ + $trigger => $value, + ], + ]; + } + else { + $states[$state][$selector] = [ + $trigger => $value, + ]; + } + } + } + } + } + } + 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 convertFormValuesToStatesArray(array $values = []) { + $index = 0; + + $states = []; + foreach ($values as $value) { + if (isset($value['state'])) { + $index++; + $states[$index] = [ + 'state' => $value['state'], + 'operator' => $value['operator'], + 'conditions' => [], + ]; + } + else { + $selector = $value['selector']['select']; + if ($selector == WebformSelectOther::OTHER_OPTION) { + $selector = $value['selector']['other']; + } + $value['selector'] = $selector; + $states[$index]['conditions'][] = $value; + } + } + 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 = self::convertFormValuesToStatesArray($values); + return self::convertStatesArrayToFormApiStates($values); + } + + /** + * Determine if an element's #states array is customized. + * + * @param array $element + * The webform element. + * + * @return bool|string + * FALSE if #states array is not customized or a warning message. + */ + public static function isDefaultValueCustomizedFormApiStates(array $element) { + // Empty default values are not customized. + if (empty($element['#default_value'])) { + return FALSE; + } + + // #states must always be an array. + if (!is_array($element['#default_value'])) { + return t('Conditional logic (Form API #states) is not an array.'); + } + + $states = $element['#default_value']; + foreach ($states as $state => $conditions) { + if (!isset($element['#state_options'][$state])) { + return t('Conditional logic (Form API #states) is using a custom %state state.', ['%state' => $state]); + } + + // If associative array we can assume that it not customized. + if (WebformArrayHelper::isAssociative(($conditions))) { + $trigger = reset($conditions); + if (count($trigger) > 1) { + return t('Conditional logic (Form API #states) is using multiple triggers.'); + } + continue; + } + + $operator = NULL; + foreach ($conditions as $condition) { + // Make sure only one condition is being specified. + if (is_array($condition) && count($condition) > 1) { + return t('Conditional logic (Form API #states) is using multiple nested conditions.'); + } + elseif (is_string($condition)) { + // Make sure only an 'and/or' operator is being used. XOR is not + // support in UI because it is confusing to none technicl users. + if (!in_array($condition, ['and', 'or'])) { + return t('Conditional logic (Form API #states) is using the %operator operator.', ['%operator' => Unicode::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)]); + } + + // Set the operator. + $operator = $condition; + } + } + } + return FALSE; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformEmailConfirm.php b/web/modules/contrib/webform/src/Element/WebformEmailConfirm.php new file mode 100644 index 000000000..5c2654d3f --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformEmailConfirm.php @@ -0,0 +1,161 @@ + TRUE, + '#size' => 60, + '#process' => [ + [$class, 'processWebformEmailConfirm'], + ], + '#pre_render' => [ + [$class, 'preRenderCompositeFormElement'], + ], + '#theme_wrappers' => ['container'], + '#required' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (!isset($element['#default_value'])) { + $element['#default_value'] = ''; + } + return [ + 'mail_1' => $element['#default_value'], + 'mail_2' => $element['#default_value'], + ]; + } + else { + return $input; + } + } + + /** + * Expand an email confirm field into two HTML5 email elements. + */ + public static function processWebformEmailConfirm(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + // Get shared properties. + $shared_properties = [ + '#title_display', + '#description_display', + '#size', + '#maxlength', + '#pattern', + '#required', + '#placeholder', + '#attributes', + ]; + $element_shared_properties = ['#type' => 'email'] + array_intersect_key($element, array_combine($shared_properties, $shared_properties)); + + // Get mail 1 email element. + $mail_1_properties = [ + '#title', + '#description', + ]; + $element['mail_1'] = $element_shared_properties + array_intersect_key($element, array_combine($mail_1_properties, $mail_1_properties)); + $element['mail_1']['#attributes']['class'][] = 'webform-email'; + $element['mail_1']['#value'] = empty($element['#value']) ? NULL : $element['#value']['mail_1']; + + // Build mail_2 confirm email element. + $element['mail_2'] = $element_shared_properties; + $element['mail_2']['#title'] = t('Confirm email'); + foreach ($element as $key => $value) { + if (strpos($key, '#confirm__') === 0) { + $element['mail_2'][str_replace('#confirm__', '#', $key)] = $value; + } + } + $element['mail_2']['#attributes']['class'][] = 'webform-email-confirm'; + $element['mail_2']['#value'] = empty($element['#value']) ? NULL : $element['#value']['mail_2']; + + // Remove properties that are being applied to the sub elements. + $element['#required'] = FALSE; + unset($element['#title']); + unset($element['#description']); + unset($element['#maxlength']); + unset($element['#atributes']); + + // Set validation. + if (isset($element['#element_validate'])) { + $element['#element_validate'] = array_merge([[get_called_class(), 'validateWebformEmailConfirm']], $element['#element_validate']); + } + else { + $element['#element_validate'] = [[get_called_class(), 'validateWebformEmailConfirm']]; + } + + return $element; + } + + /** + * 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.')); + } + else { + // NOTE: Only mail_1 needs to be validated since mail_2 is the same value. + // Verify the required value. + if ($element['mail_1']['#required'] && empty($mail_1)) { + if (isset($element['#required_error'])) { + $form_state->setError($element, $element['#required_error']); + } + elseif (isset($element['mail_1']['#title'])) { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['mail_1']['#title']])); + } + else { + $form_state->setError($element); + } + } + // Verify that the value is not longer than #maxlength. + if (isset($element['mail_1']['#maxlength']) && Unicode::strlen($mail_1) > $element['mail_1']['#maxlength']) { + $t_args = [ + '@name' => $element['mail_1']['#title'], + '%max' => $element['mail_1']['#maxlength'], + '%length' => Unicode::strlen($mail_1), + ]; + $form_state->setError($element, t('@name cannot be longer than %max characters but is currently %length characters long.', $t_args)); + } + } + } + + // Email field must be converted from a two-element array into a single + // string regardless of validation results. + $form_state->setValueForElement($element['mail_1'], NULL); + $form_state->setValueForElement($element['mail_2'], NULL); + $form_state->setValueForElement($element, $mail_1); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformEmailMultiple.php b/web/modules/contrib/webform/src/Element/WebformEmailMultiple.php new file mode 100644 index 000000000..255805a5b --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformEmailMultiple.php @@ -0,0 +1,83 @@ + TRUE, + '#description' => $this->t('Multiple email addresses may be separated by commas.'), + '#size' => 60, + '#allow_tokens' => FALSE, + '#process' => [ + [$class, 'processAutocomplete'], + [$class, 'processAjaxForm'], + [$class, 'processPattern'], + ], + '#element_validate' => [ + [$class, 'validateWebformEmailMultiple'], + ], + '#pre_render' => [ + [$class, 'preRenderWebformEmailMultiple'], + ], + '#theme' => 'input__email_multiple', + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * Webform element validation handler for #type 'email_multiple'. + */ + public static function validateWebformEmailMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + $value = trim($element['#value']); + $form_state->setValueForElement($element, $value); + + if ($value) { + $values = preg_split('/\s*,\s*/', $value); + foreach ($values as $value) { + // Allow tokens to be be include in multiple email list. + if (!empty($element['#allow_tokens'] && preg_match('/^\[.*\]$/', $value))) { + continue; + } + + if (!\Drupal::service('email.validator')->isValid($value)) { + $form_state->setError($element, t('The email address %mail is not valid.', ['%mail' => $value])); + return; + } + } + } + } + + /** + * Prepares a #type 'email_multiple' render element for theme_element(). + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #description, #size, #maxlength, + * #placeholder, #required, #attributes. + * + * @return array + * The $element with prepared variables ready for theme_element(). + */ + public static function preRenderWebformEmailMultiple(array $element) { + $element['#attributes']['type'] = 'text'; + Element::setAttributes($element, ['id', 'name', 'value', 'size', 'maxlength', 'placeholder']); + static::setAttributes($element, ['form-textfield', 'form-email-multiple']); + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformEntityCheckboxes.php b/web/modules/contrib/webform/src/Element/WebformEntityCheckboxes.php new file mode 100644 index 000000000..37aa50565 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformEntityCheckboxes.php @@ -0,0 +1,25 @@ + $element['#target_type'], + 'handler' => $element['#selection_handler'], + 'handler_settings' => (isset($element['#selection_settings'])) ? $element['#selection_settings'] : [], + ]; + + /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager */ + $selection_manager = \Drupal::service('plugin.manager.entity_reference_selection'); + $handler = $selection_manager->getInstance($selection_handler_options); + $referenceable_entities = $handler->getReferenceableEntities(); + + // Flatten all bundle grouping since they are not applicable to + // WebformEntity elements. + $options = []; + foreach ($referenceable_entities as $bundle_options) { + $options += $bundle_options; + } + + // Only select menu can support optgroups. + if ($element['#type'] !== 'webform_entity_select') { + $options = OptGroup::flattenOptions($options); + } + + // Issue #2826451: TermSelection returning HTML characters in select list. + $options = WebformOptionsHelper::decodeOptions($options); + + $element['#options'] = $options; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformExcludedBase.php b/web/modules/contrib/webform/src/Element/WebformExcludedBase.php new file mode 100644 index 000000000..cba494c21 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformExcludedBase.php @@ -0,0 +1,116 @@ + TRUE, + '#process' => [ + [$class, 'processWebformExcluded'], + ], + '#webform' => NULL, + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * Processes a webform elements webform element. + */ + public static function processWebformExcluded(&$element, FormStateInterface $form_state, &$complete_form) { + $options = static::getWebformExcludedOptions($element); + + $default_value = array_diff(array_keys($options), array_keys($element['#default_value'] ?: [])); + $element['#tree'] = TRUE; + $element['#element_validate'] = [[get_called_class(), 'validateWebformExcluded']]; + + $element['tableselect'] = [ + '#type' => 'tableselect', + '#header' => static::getWebformExcludedHeader(), + '#options' => $options, + '#js_select' => TRUE, + '#empty' => t('No elements are available.'), + '#default_value' => array_combine($default_value, $default_value), + ]; + + // Build tableselect element with selected properties. + $properties = [ + '#title', + '#title_display', + '#description', + '#description_display', + '#ajax', + '#states', + ]; + $element['tableselect'] += array_intersect_key($element, array_combine($properties, $properties)); + return $element; + } + + /** + * Validates a tablelselect element. + */ + public static function validateWebformExcluded(array &$element, FormStateInterface $form_state, &$complete_form) { + $value = array_filter($element['tableselect']['#value']); + + // Converted value to excluded elements. + $options = array_keys($element['tableselect']['#options']); + $excluded = array_diff($options, $value); + + // Unset tableselect and set the element's value to excluded. + $form_state->setValueForElement($element['tableselect'], NULL); + $form_state->setValueForElement($element, array_combine($excluded, $excluded)); + } + + /** + * Get options for excluded tableselect element. + * + * @param array $element + * An associative array containing the properties and children of the + * generic element element. + * + * @return array + * An array of options containing title, name, and type of items for a + * tableselect element. + */ + public static function getWebformExcludedOptions(array $element) { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $element['#webform']; + + $options = []; + $elements = $webform->getElementsInitializedFlattenedAndHasValue('view'); + foreach ($elements as $key => $element) { + $options[$key] = [ + ['title' => $element['#admin_title'] ?:$element['#title'] ?: $key], + ['name' => $key], + ['type' => isset($element['#type']) ? $element['#type'] : ''], + ]; + } + return $options; + } + + /** + * Get header for the excluded tableselect element. + * + * @return array + * An array container the header for the excluded tableselect element. + */ + public static function getWebformExcludedHeader() { + return [t('Title'), t('Name'), t('Type')]; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformExcludedColumns.php b/web/modules/contrib/webform/src/Element/WebformExcludedColumns.php new file mode 100644 index 000000000..f5dd0da4c --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformExcludedColumns.php @@ -0,0 +1,40 @@ +getStorage('webform_submission'); + $field_definitions = $submission_storage->getFieldDefinitions(); + $field_definitions = $submission_storage->checkFieldDefinitionAccess($element['#webform'], $field_definitions); + foreach ($field_definitions as $key => $field_definition) { + $options[$key] = [ + ['title' => $field_definition['title']], + ['name' => $key], + ['type' => $field_definition['type']], + ]; + } + $options += parent::getWebformExcludedOptions($element); + return $options; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformExcludedElements.php b/web/modules/contrib/webform/src/Element/WebformExcludedElements.php new file mode 100644 index 000000000..11c72683a --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformExcludedElements.php @@ -0,0 +1,10 @@ +get('ui.html_editor_disabled')) { + $element['#mode'] = 'html'; + $element = WebformCodeMirror::preRenderWebformCodeMirror($element); + } + else { + $element['#attached']['library'][] = 'webform/webform.element.html_editor'; + $element['#attached']['drupalSettings']['webform']['html_editor']['allowedContent'] = self::getAllowedContent(); + } + return $element; + } + + /** + * Webform element validation handler for #type 'webform_html_editor'. + */ + public static function validateWebformHtmlEditor(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + $form_state->setValueForElement($element, trim($value)); + } + + /** + * Get allowed content. + * + * @return array + * Allowed content (tags) for CKEditor. + */ + public static function getAllowedContent() { + $allowed_tags = \Drupal::config('webform.settings')->get('elements.allowed_tags'); + switch ($allowed_tags) { + case 'admin': + $allowed_tags = Xss::getAdminTagList(); + break; + + case 'html': + $allowed_tags = Xss::getHtmlTagList(); + break; + + default: + $allowed_tags = preg_split('/ +/', $allowed_tags); + break; + } + foreach ($allowed_tags as $index => $allowed_tag) { + $allowed_tags[$index] .= '(*)[*]{*}'; + } + return implode('; ', $allowed_tags); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformImageFile.php b/web/modules/contrib/webform/src/Element/WebformImageFile.php new file mode 100644 index 000000000..6fe4022da --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformImageFile.php @@ -0,0 +1,17 @@ + TRUE, + '#process' => [ + [$class, 'processWebformLikert'], + [$class, 'processAjaxForm'], + ], + '#theme_wrappers' => ['form_element'], + '#required' => FALSE, + '#questions' => [], + // Using #answers insteads of #options to prevent triggering + // \Drupal\Core\Form\FormValidator::performRequiredValidation(). + '#answers' => [], + '#na_answer' => FALSE, + '#na_answer_text' => '', + '#na_answer_value' => '', + ]; + } + + /** + * Processes a likert scale webform element. + */ + public static function processWebformLikert(&$element, FormStateInterface $form_state, &$complete_form) { + // Get answer with optional N/A. + self::processWebformLikertAnswers($element); + + // Build header. + $header = [ + 'likert_question' => ['question' => FALSE], + ] + $element['#answers']; + + // Randomize questions. + if (!empty($element['#questions_randomize'])) { + $element['#questions'] = WebformArrayHelper::shuffle($element['#questions']); + } + + // Build rows. + $rows = []; + foreach ($element['#questions'] as $question_key => $question_title) { + $value = (isset($element['#value'][$question_key])) ? $element['#value'][$question_key] : NULL; + $row = []; + // Must format the label as an item so that inline webform errors will be + // displayed. + $row['likert_question'] = [ + '#type' => 'item', + '#title' => $question_title, + // Must include an empty so that the item's value is + // not required. + '#value' => '', + '#required' => $element['#required'], + ]; + foreach ($element['#answers'] as $answer_key => $answer_title) { + $row[$answer_key] = [ + '#parents' => [$element['#name'], $question_key], + '#type' => 'radio', + '#title' => $answer_title, + '#title_display' => 'after', + // Must cast values as strings to prevent NULL and empty strings. + // from being evaluated as 0. + '#return_value' => (string) $answer_key, + '#value' => (string) $value, + ]; + } + $rows[$question_key] = $row; + } + + $element['table'] = [ + '#type' => 'table', + '#header' => $header, + '#attributes' => [ + 'class' => ['webform-likert-table'], + 'data-likert-answers-count' => count($element['#answers']), + ], + ] + $rows; + + // Build table element with selected properties. + $properties = [ + '#states', + '#sticky', + ]; + $element['table'] += array_intersect_key($element, array_combine($properties, $properties)); + + $element['#tree'] = TRUE; + $element['#element_validate'] = [[get_called_class(), 'validateWebformLikert']]; + $element['#attached']['library'][] = 'webform/webform.element.likert'; + + return $element; + } + + /** + * Get likert element's answer which can include an N/A option. + * + * @param array $element + * The element. + */ + public static function processWebformLikertAnswers(array &$element) { + if (empty($element['#na_answer']) || empty($element['#answers'])) { + return; + } + + $na_value = (!empty($element['#na_answer_value'])) ? $element['#na_answer_value'] : (string) t('N/A'); + $na_text = (!empty($element['#na_answer_text'])) ? $element['#na_answer_text'] : $na_value; + $element['#answers'] += [ + $na_value => $na_text, + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + $default_value = []; + foreach ($element['#questions'] as $question_key => $question_title) { + $default_value[$question_key] = NULL; + } + + if ($input === FALSE) { + $element += ['#default_value' => []]; + return $element['#default_value'] + $default_value; + } + $value = $default_value; + foreach ($value as $allowed_key => $default) { + if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) { + $value[$allowed_key] = (string) $input[$allowed_key]; + } + } + return $value; + } + + /** + * Validates a likert element. + */ + public static function validateWebformLikert(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + + if (!empty($element['#required'])) { + foreach ($element['#questions'] as $question_key => $question_title) { + if (empty($value[$question_key])) { + $form_state->setError($element['table'][$question_key]['likert_question'], t('@name field is required.', ['@name' => $question_title])); + } + } + } + + $form_state->setValueForElement($element, $value); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformLink.php b/web/modules/contrib/webform/src/Element/WebformLink.php new file mode 100644 index 000000000..8e1b2ad92 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformLink.php @@ -0,0 +1,29 @@ + 'textfield', + '#title' => t('Link Title'), + '#maxlength' => 255, + ]; + $elements['url'] = [ + '#type' => 'url', + '#title' => t('Link URL'), + ]; + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformLocation.php b/web/modules/contrib/webform/src/Element/WebformLocation.php new file mode 100644 index 000000000..7e5e02cac --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformLocation.php @@ -0,0 +1,170 @@ + '', + '#hidden' => FALSE, + '#geolocation' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public static function getCompositeElements() { + // @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['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'], + ], + ]; + $elements += $attributes; + return $elements; + } + + /** + * {@inheritdoc} + */ + public static function preRenderCompositeFormElement($element) { + $element = WebformCompositeBase::preRenderCompositeFormElement($element); + + // Hide location element webform display only if #geolocation is also set. + if (!empty($element['#hidden']) && !empty($element['#geolocation'])) { + $element['#attributes']['style'] = 'display: none'; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processWebformComposite($element, $form_state, $complete_form); + + // Composite elements should always be displayed and rendered so that + // location data can be populated, so #access is really just converting the + // readonly elements to hidden elements. + $composite_elements = static::getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + if ($composite_key != 'value') { + if (isset($element[$composite_key]['#access']) && $element[$composite_key]['#access'] === FALSE) { + unset($element[$composite_key]['#access']); + $element[$composite_key]['#type'] = 'hidden'; + } + elseif (!empty($element['#hidden']) && !empty($element['#geolocation'])) { + $element[$composite_key]['#type'] = 'hidden'; + } + else { + $element[$composite_key]['#attributes']['class'][] = 'webform-readonly'; + $element[$composite_key]['#readonly'] = 'readonly'; + } + } + } + + // Set required. + if (isset($element['#required'])) { + $element['value']['#required'] = $element['#required']; + } + + // Set Geolocation detection attribute. + if (!empty($element['#geolocation'])) { + $element['value']['#attributes']['data-webform-location-geolocation'] = 'data-webform-location-geolocation'; + } + + // Writing script tags (only once) directly into the page's output to ensure + // that Google Maps APi script is loaded using the proper API key. + static $google_api; + if (empty($google_api)) { + $api_key = (!empty($element['#api_key'])) ? $element['#api_key'] : \Drupal::config('webform.settings')->get('elements.default_google_maps_api_key'); + $element['script'] = [ + '#markup' => '', + '#allowed_tags' => ['script'], + ]; + $google_api = TRUE; + } + + $element['#attached']['library'][] = 'webform/webform.element.location'; + + $element['#element_validate'] = [[get_called_class(), 'validateWebformLocation']]; + + return $element; + } + + /** + * Validates location. + */ + public static function validateWebformLocation(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + + $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); + if ($has_access && !empty($element['#required']) && empty($value['location'])) { + $t_args = ['@title' => !empty($element['#title']) ? $element['#title'] : t('Location')]; + $form_state->setError($element, t('The @title is not valid.', $t_args)); + } + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformManagedFileBase.php b/web/modules/contrib/webform/src/Element/WebformManagedFileBase.php new file mode 100644 index 000000000..e090ebd5c --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformManagedFileBase.php @@ -0,0 +1,63 @@ + 'status', + '#message_message' => '', + '#message_close' => FALSE, + '#message_close_effect' => 'slide', + '#message_id' => '', + '#message_storage' => '', + '#status_headings' => [], + '#pre_render' => [ + [$class, 'preRenderWebformMessage'], + ], + '#theme_wrappers' => ['webform_message'], + ]; + } + + /** + * Create status message for rendering. + * + * @param array $element + * An associative array containing the properties and children of the + * element. + * + * @return array + * The modified element with status message. + */ + public static function preRenderWebformMessage(array $element) { + $message_type = $element['#message_type']; + $message_close = $element['#message_close']; + $message_close_effect = $element['#message_close_effect']; + $message_id = $element['#message_id']; + $message_storage = $element['#message_storage']; + $message_message = $element['#message_message']; + + $element['#attributes']['class'][] = 'webform-message'; + $element['#attributes']['class'][] = 'js-webform-message'; + + // Ignore 'user' and 'state' storage is current user is anonymous. + if (\Drupal::currentUser()->isAnonymous() && in_array($message_storage, [self::STORAGE_USER, self::STORAGE_STATE]) + ) { + $message_storage = ''; + } + + // Build the messages render array. + $messages = []; + + // Add close button as the first message. + if ($message_close) { + $element['#attributes']['data-message-close-effect'] = $message_close_effect; + $element['#attributes']['class'][] = 'webform-message--close'; + $element['#attributes']['class'][] = 'js-webform-message--close'; + + $close_attributes = [ + 'aria-label' => t('close'), + 'class' => ['js-webform-message__link', 'webform-message__link'], + ]; + if (in_array($message_storage, ['user', 'state'])) { + $close_url = Url::fromRoute('webform.element.message.close', [ + 'storage' => $message_storage, + 'id' => $message_id, + ]); + } + else { + $close_url = Url::fromRoute('', [], ['fragment' => 'close']); + } + + $messages[] = [ + '#type' => 'link', + '#title' => '×', + '#url' => $close_url, + '#attributes' => $close_attributes, + ]; + + // Add close attributes and check is message is closed. + if ($message_storage && $message_id) { + $element['#attributes']['data-message-id'] = $message_id; + $element['#attributes']['data-message-storage'] = $message_storage; + $element['#attributes']['class'][] = 'js-webform-message--close-storage'; + if (self::isClosed($message_storage, $message_id)) { + $element['#closed'] = TRUE; + } + } + } + + // Add messages to container children. + $messages[] = (!is_array($message_message)) ? ['#markup' => $message_message] : $message_message; + foreach (Element::children($element) as $key) { + $messages[] = $element[$key]; + unset($element[$key]); + } + + // Add status messages as the message. + $element['#message'] = [ + '#theme' => 'status_messages', + '#message_list' => [$message_type => [$messages]], + '#status_headings' => $element['#status_headings'] + [ + 'status' => t('Status message'), + 'error' => t('Error message'), + 'warning' => t('Warning message'), + ], + ]; + + $element['#attached']['library'][] = 'webform/webform.element.message'; + return $element; + } + + /****************************************************************************/ + // Manage closed functions. + /****************************************************************************/ + + /** + * Is message closed via User Data or State API. + * + * @param string $storage + * The storage mechanism to check if a message is closed. + * @param string $id + * The ID of the message. + * + * @return bool + * TRUE if the message is closed. + */ + public static function isClosed($storage, $id) { + $account = \Drupal::currentUser(); + $namespace = 'webform.element.message'; + switch ($storage) { + case self::STORAGE_STATE: + /** @var \Drupal\Core\State\StateInterface $state */ + $state = \Drupal::service('state'); + $values = $state->get($namespace, []); + return (isset($values[$id])) ? TRUE : FALSE; + + case self::STORAGE_USER: + /** @var \Drupal\user\UserDataInterface $user_data */ + $user_data = \Drupal::service('user.data'); + $values = $user_data->get('webform', $account->id(), $namespace) ?: []; + return (isset($values[$id])) ? TRUE : FALSE; + + } + return FALSE; + } + + /** + * Set message closed via User Data or State API. + * + * @param string $storage + * The storage mechanism save message closed. + * @param string $id + * The ID of the message. + * + * @see \Drupal\webform\Controller\WebformElementController::close + */ + public static function setClosed($storage, $id) { + $account = \Drupal::currentUser(); + $namespace = 'webform.element.message'; + switch ($storage) { + case self::STORAGE_STATE: + /** @var \Drupal\Core\State\StateInterface $state */ + $state = \Drupal::service('state'); + $values = $state->get($namespace, []); + $values[$id] = TRUE; + $state->set($namespace, $values); + break; + + case self::STORAGE_USER: + /** @var \Drupal\user\UserDataInterface $user_data */ + $user_data = \Drupal::service('user.data'); + $values = $user_data->get('webform', $account->id(), $namespace) ?: []; + $values[$id] = TRUE; + $user_data->set('webform', $account->id(), $namespace, $values); + } + } + + /** + * Reset message closed via User Data or State API. + * + * @param string $storage + * The storage mechanism save message closed. + * @param string $id + * The ID of the message. + * + * @see \Drupal\webform\Controller\WebformElementController::close + */ + public static function resetClosed($storage, $id) { + $account = \Drupal::currentUser(); + $namespace = 'webform.element.message'; + switch ($storage) { + case self::STORAGE_STATE: + /** @var \Drupal\Core\State\StateInterface $state */ + $state = \Drupal::service('state'); + $values = $state->get($namespace, []); + unset($values[$id]); + $state->set($namespace, $values); + break; + + case self::STORAGE_USER: + /** @var \Drupal\user\UserDataInterface $user_data */ + $user_data = \Drupal::service('user.data'); + $values = $user_data->get('webform', $account->id(), $namespace) ?: []; + unset($values[$id]); + $user_data->set('webform', $account->id(), $namespace, $values); + } + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformMultiple.php b/web/modules/contrib/webform/src/Element/WebformMultiple.php new file mode 100644 index 000000000..626e081e3 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformMultiple.php @@ -0,0 +1,576 @@ + TRUE, + '#label' => t('item'), + '#labels' => t('items'), + '#header' => NULL, + '#element' => [ + '#type' => 'textfield', + '#title' => t('Item value'), + '#title_display' => 'invisible', + '#placeholder' => t('Enter value'), + ], + '#cardinality' => FALSE, + '#empty_items' => 1, + '#add_more' => 1, + '#process' => [ + [$class, 'processWebformMultiple'], + ], + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + return (isset($element['#default_value'])) ? $element['#default_value'] : []; + } + elseif (is_array($input) && isset($input['items'])) { + return $input['items']; + } + else { + return NULL; + } + } + + /** + * Process items and build multiple elements widget. + */ + public static function processWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + // Add validate callback that extracts the array of items. + $element['#element_validate'] = [[get_called_class(), 'validateWebformMultiple']]; + + // Wrap this $element in a
          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 = self::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; + if ($number_of_items < 1) { + $number_of_items = 1; + } + $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'; + + // DEBUG: Disable AJAX callback by commenting out the below callback and + // wrapper. + $ajax_settings = [ + 'callback' => [get_called_class(), 'ajaxCallback'], + 'wrapper' => $table_id, + ]; + + $element['#child_keys'] = Element::children($element['#element']); + + // Build (single) element header. + $header = self::buildElementHeader($element); + + // Build (single) element rows. + $row_index = 0; + $weight = 0; + $rows = []; + + if (!$form_state->isProcessingInput() && isset($element['#default_value']) && is_array($element['#default_value'])) { + $default_values = $element['#default_value']; + } + elseif ($form_state->isProcessingInput() && isset($element['#value']) && is_array($element['#value'])) { + $default_values = $element['#value']; + } + else { + $default_values = []; + } + + foreach ($default_values as $default_value) { + $rows[$row_index] = self::buildElementRow($table_id, $row_index, $element, $default_value, $weight++, $ajax_settings); + $row_index++; + } + + while ($row_index < $number_of_items) { + $rows[$row_index] = self::buildElementRow($table_id, $row_index, $element, NULL, $weight++, $ajax_settings); + $row_index++; + } + + // Build table. + $element['items'] = [ + '#prefix' => '
          ', + '#suffix' => '
          ', + '#type' => 'table', + '#header' => $header, + '#tabledrag' => [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'webform-multiple-sort-weight', + ], + ], + ] + $rows; + + // Build add items actions. + if (empty($element['#cardinality'])) { + $element['add'] = [ + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + $element['add']['submit'] = [ + '#type' => 'submit', + '#value' => t('Add'), + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'addItemsSubmit']], + '#ajax' => $ajax_settings, + '#name' => $table_id . '_add', + ]; + $element['add']['more_items'] = [ + '#type' => 'number', + '#min' => 1, + '#max' => 100, + '#default_value' => $element['#add_more'], + '#field_suffix' => t('more @labels', ['@labels' => $element['#labels']]), + ]; + } + + $element['#attached']['library'][] = 'webform/webform.element.multiple'; + + return $element; + } + + /** + * Build a single element header. + * + * @param array $element + * The element. + * + * @return array + * A render array containing inputs for an element's header. + */ + public static function buildElementHeader(array $element) { + if (empty($element['#header'])) { + return [ + ['data' => '', 'colspan' => 4], + ]; + } + elseif (is_array($element['#header'])) { + return $element['#header']; + } + elseif (is_string($element['#header'])) { + return [ + ['data' => $element['#header'], 'colspan' => ($element['#child_keys']) ? count($element['#child_keys']) + 3 : 4], + ]; + } + else { + $header = []; + $header['_handle_'] = ''; + if ($element['#child_keys']) { + foreach ($element['#child_keys'] as $child_key) { + if (self::isHidden($element['#element'][$child_key])) { + continue; + } + $header[$child_key] = (!empty($element['#element'][$child_key]['#title'])) ? $element['#element'][$child_key]['#title'] : ''; + } + } + else { + $header['item'] = (isset($element['#element']['#title'])) ? $element['#element']['#title'] : ''; + } + $header['weight'] = t('Weight'); + if (empty($element['#cardinality'])) { + $header['_operations_'] = ''; + } + return $header; + } + } + + /** + * Build a single element row. + * + * @param string $table_id + * The element's table id. + * @param int $row_index + * The row index. + * @param array $element + * The element. + * @param string $default_value + * The default value. + * @param int $weight + * The weight. + * @param array $ajax_settings + * An array containing AJAX callback settings. + * + * @return array + * A render array containing inputs for an element's value and weight. + */ + public static function buildElementRow($table_id, $row_index, array $element, $default_value, $weight, array $ajax_settings) { + if ($element['#child_keys']) { + foreach ($element['#child_keys'] as $child_key) { + if (isset($default_value[$child_key])) { + if ($element['#element'][$child_key]['#type'] == 'value') { + $element['#element'][$child_key]['#value'] = $default_value[$child_key]; + } + else { + $element['#element'][$child_key]['#default_value'] = $default_value[$child_key]; + } + } + } + } + else { + $element['#element']['#default_value'] = $default_value; + } + + $row = []; + + $row['_handle_'] = []; + + if ($element['#child_keys'] && !empty($element['#header'])) { + foreach ($element['#child_keys'] as $child_key) { + // Store hidden element in the '_handle_' column. + // @see \Drupal\webform\Element\WebformMultiple::convertValuesToItems + if (self::isHidden($element['#element'][$child_key])) { + $row['_handle_'][$child_key] = $element['#element'][$child_key]; + // ISSUE: All elements in _handle_ are losing their value. + // WORKAROUND: Convert to element to rendered hidden field. + $row['_handle_'][$child_key]['#type'] = 'hidden'; + unset($row['_handle_'][$child_key]['#access']); + } + else { + $row[$child_key] = $element['#element'][$child_key]; + } + } + } + else { + $row['_item_'] = $element['#element']; + } + + $row['weight'] = [ + '#type' => 'weight', + '#delta' => 1000, + '#title' => t('Item weight'), + '#title_display' => 'invisible', + '#attributes' => [ + 'class' => ['webform-multiple-sort-weight'], + ], + '#default_value' => $weight, + ]; + + // Allow users to add & remove rows if cardinality is not set. + if (empty($element['#cardinality'])) { + $row['_operations_'] = []; + $row['_operations_']['add'] = [ + '#type' => 'image_button', + '#src' => drupal_get_path('module', 'webform') . '/images/icons/plus.svg', + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'addItemSubmit']], + '#ajax' => $ajax_settings, + // 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. + '#row_index' => $row_index, + '#name' => $table_id . '_add_' . $row_index, + ]; + $row['_operations_']['remove'] = [ + '#type' => 'image_button', + '#src' => drupal_get_path('module', 'webform') . '/images/icons/ex.svg', + '#limit_validation_errors' => [], + '#submit' => [[get_called_class(), 'removeItemSubmit']], + '#ajax' => $ajax_settings, + // 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. + '#row_index' => $row_index, + '#name' => $table_id . '_remove_' . $row_index, + ]; + } + + $row['#weight'] = $weight; + $row['#attributes']['class'][] = 'draggable'; + return $row; + } + + /** + * Determine if an element is hidden. + * + * @param array $element + * The element. + * + * @return bool + * TRUE if the element is hidden. + */ + protected static function isHidden(array $element) { + if (isset($element['#access']) && $element['#access'] === FALSE) { + return TRUE; + } + elseif (isset($element['#type']) && in_array($element['#type'], ['hidden', 'value'])) { + return TRUE; + } + else { + return FALSE; + } + } + + /****************************************************************************/ + // Callbacks. + /****************************************************************************/ + + /** + * Webform submission handler for adding more items. + * + * @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 addItemsSubmit(array &$form, FormStateInterface $form_state) { + // Get the webform list element by going up two levels. + $button = $form_state->getTriggeringElement(); + $element =& NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -2)); + + // Add more items to the number of items. + $number_of_items_storage_key = self::getStorageKey($element, 'number_of_items'); + $number_of_items = $form_state->get($number_of_items_storage_key); + $more_items = (int) $element['add']['more_items']['#value']; + $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']); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission handler for adding an item. + * + * @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 addItemSubmit(array &$form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4)); + + // Add item. + $values = []; + foreach ($element['items']['#value'] as $row_index => $value) { + $values[] = $value; + if ($row_index == $button['#row_index']) { + $values[] = ['item' => '', 'text' => '']; + } + } + + // Add one item to the 'number of items'. + $number_of_items_storage_key = self::getStorageKey($element, 'number_of_items'); + $number_of_items = $form_state->get($number_of_items_storage_key); + $form_state->set($number_of_items_storage_key, $number_of_items + 1); + + // Reset values. + $form_state->setValueForElement($element['items'], $values); + NestedArray::setValue($form_state->getUserInput(), $element['items']['#parents'], $values); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission handler for removing an item. + * + * @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 removeItemSubmit(array &$form, FormStateInterface $form_state) { + $button = $form_state->getTriggeringElement(); + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4)); + $values = $element['items']['#value']; + + // Remove item. + unset($values[$button['#row_index']]); + $values = array_values($values); + + // Remove one item from the 'number of items'. + $number_of_items_storage_key = self::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) { + $form_state->set($number_of_items_storage_key, $number_of_items - 1); + } + + // Reset values. + $form_state->setValueForElement($element['items'], $values); + NestedArray::setValue($form_state->getUserInput(), $element['items']['#parents'], $values); + + // Rebuild the webform. + $form_state->setRebuild(); + } + + /** + * Webform submission AJAX callback the returns the list table. + */ + 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']; + } + + /** + * Validates webform list element. + */ + public static function validateWebformMultiple(&$element, FormStateInterface $form_state, &$complete_form) { + // IMPORTANT: Must get values from the $form_states since sub-elements + // may call $form_state->setValueForElement() via their validation hook. + // @see \Drupal\webform\Element\WebformEmailConfirm::validateWebformEmailConfirm + // @see \Drupal\webform\Element\WebformOtherBase::validateWebformOther + $values = NestedArray::getValue($form_state->getValues(), $element['#parents']); + + // Convert values to items. + $items = self::convertValuesToItems($values['items']); + + // Validate required items. + if (!empty($element['#required']) && empty($items)) { + if (isset($element['#required_error'])) { + $form_state->setError($element, $element['#required_error']); + } + elseif (isset($element['#title'])) { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + else { + $form_state->setError($element); + } + return; + } + + $form_state->setValueForElement($element, $items); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Get unique key used to store the number of items for an element. + * + * @param array $element + * An element. + * + * @return string + * A unique key used to store the number of items for an element. + */ + public static function getStorageKey(array $element, $name) { + return 'webform_multiple__' . $element['#name'] . '__' . $name; + } + + /** + * Convert an array containing of values (elements or _item_ and weight) to an array of items. + * + * @param array $values + * An array containing of item and weight. + * + * @return array + * An array of items. + */ + public static function convertValuesToItems(array $values = []) { + // Sort the item values. + uasort($values, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + + // Now build the associative array of items. + $items = []; + foreach ($values as $value) { + if (isset($value['_item_'])) { + if (!self::isEmpty($value['_item_'])) { + $items[] = $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['_handle_']) && is_array($value['_handle_'])) { + $value += $value['_handle_']; + } + unset($value['weight'], $value['_operations_'], $value['_handle_']); + if (!self::isEmpty($value)) { + $items[] = $value; + } + } + } + + return $items; + } + + /** + * Check if array is empty. + * + * @param string|array $value + * An item. + * + * @return bool + * FALSE if item is an empty string or an empty array. + */ + public static function isEmpty($value = NULL) { + if (is_null($value)) { + return TRUE; + } + elseif (is_string($value)) { + return ($value === '') ? TRUE : FALSE; + } + elseif (is_array($value)) { + return !array_filter($value, function ($item) { + return !self::isEmpty($item); + }); + } + else { + return FALSE; + } + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformName.php b/web/modules/contrib/webform/src/Element/WebformName.php new file mode 100644 index 000000000..26e86f547 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformName.php @@ -0,0 +1,45 @@ + 'webform_select_other', + '#title' => t('Title'), + '#options' => 'titles', + ]; + $elements['first'] = [ + '#type' => 'textfield', + '#title' => t('First'), + ]; + $elements['middle'] = [ + '#type' => 'textfield', + '#title' => t('Middle'), + ]; + $elements['last'] = [ + '#type' => 'textfield', + '#title' => t('Last'), + ]; + $elements['suffix'] = [ + '#type' => 'textfield', + '#title' => t('Suffix'), + ]; + $elements['degree'] = [ + '#type' => 'textfield', + '#title' => t('Degree'), + ]; + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformOptions.php b/web/modules/contrib/webform/src/Element/WebformOptions.php new file mode 100644 index 000000000..47bba4d58 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformOptions.php @@ -0,0 +1,208 @@ + TRUE, + '#label' => t('option'), + '#labels' => t('options'), + '#empty_items' => 5, + '#add_more' => 1, + '#process' => [ + [$class, 'processWebformOptions'], + ], + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + if (!isset($element['#default_value'])) { + return []; + } + + $options = (is_string($element['#default_value'])) ? Yaml::decode($element['#default_value']) : $element['#default_value']; + if (self::hasOptGroup($options)) { + return $options; + } + return self::convertOptionsToValues($options); + } + elseif (is_array($input) && isset($input['options'])) { + return (is_string($input['options'])) ? Yaml::decode($input['options']) : $input['options']; + } + else { + return NULL; + } + } + + /** + * Process options and build options widget. + */ + public static function processWebformOptions(&$element, FormStateInterface $form_state, &$complete_form) { + $element['#tree'] = TRUE; + + // Add validate callback that extracts the associative array of options. + $element['#element_validate'] = [[get_called_class(), 'validateWebformOptions']]; + + // Wrap this $element in a
          that handle #states. + WebformElementHelper::fixStatesWrapper($element); + + // For options with optgroup display a CodeMirror YAML editor. + if (isset($element['#default_value']) && is_array($element['#default_value']) && self::hasOptGroup($element['#default_value'])) { + // Build table. + $element['options'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#default_value' => Yaml::encode($element['#default_value']), + '#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.') . '
          ' . + t('Option groups can be created by using just the group name followed by indented group options.'), + ]; + return $element; + } + else { + $properties = ['#label', '#labels', '#empty_items', '#add_more']; + $element['options'] = array_intersect_key($element, array_combine($properties, $properties)) + [ + '#type' => 'webform_multiple', + '#header' => TRUE, + '#element' => [ + 'value' => [ + '#type' => 'textfield', + '#title' => t('Option value'), + '#title_display' => t('invisible'), + '#placeholder' => t('Enter value'), + ], + 'text' => [ + '#type' => 'textfield', + '#title' => t('Option text'), + '#title_display' => t('invisible'), + '#placeholder' => t('Enter text'), + ], + ], + '#default_value' => (isset($element['#default_value'])) ? self::convertOptionsToValues($element['#default_value']) : [], + ]; + return $element; + } + } + + /** + * Validates webform options element. + */ + public static function validateWebformOptions(&$element, FormStateInterface $form_state, &$complete_form) { + $options_value = NestedArray::getValue($form_state->getValues(), $element['options']['#parents']); + + if (is_string($options_value)) { + $options = Yaml::decode($options_value); + } + else { + $options = self::convertValuesToOptions($options_value); + } + + // Validate required options. + if (!empty($element['#required']) && empty($options)) { + if (isset($element['#required_error'])) { + $form_state->setError($element, $element['#required_error']); + } + elseif (isset($element['#title'])) { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + else { + $form_state->setError($element); + } + return; + } + + $form_state->setValueForElement($element, $options); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Convert values from yamform_multiple element to options. + * + * @param array $values + * An array of values. + * + * @return array + * An array of options. + */ + public static function convertValuesToOptions(array $values = []) { + $options = []; + foreach ($values as $value) { + $option_value = $value['value']; + $option_text = $value['text']; + + // Populate empty option value or option text. + if ($option_value === '') { + $option_value = $option_text; + } + elseif ($option_text === '') { + $option_text = $option_value; + } + + $options[$option_value] = $option_text; + } + return $options; + } + + /** + * Convert options to values for yamform_multiple element. + * + * @param array $options + * An array of options. + * + * @return array + * An array of values. + */ + public static function convertOptionsToValues(array $options = []) { + $values = []; + foreach ($options as $value => $text) { + $values[] = ['value' => $value, 'text' => $text]; + } + return $values; + } + + /** + * Determine if options array contains an OptGroup. + * + * @param array $options + * An array of options. + * + * @return bool + * TRUE if options array contains an OptGroup. + */ + public static function hasOptGroup(array $options) { + foreach ($options as $option_text) { + if (is_array($option_text)) { + return TRUE; + } + } + return FALSE; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformOtherBase.php b/web/modules/contrib/webform/src/Element/WebformOtherBase.php new file mode 100644 index 000000000..d63770a93 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformOtherBase.php @@ -0,0 +1,300 @@ + TRUE, + '#process' => [ + [$class, 'processWebformOther'], + [$class, 'processAjaxForm'], + ], + '#theme_wrappers' => ['form_element'], + '#options' => [], + '#other__option_delimiter' => ', ', + '#states' => [], + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + // Remove 'webform_' prefix from type. + $type = str_replace('webform_', '', static::$type); + + if ($input === FALSE) { + $value = self::convertDefaultValueToElementValue($element); + $element[$type]['#default_value'] = $value[$type]; + if ($value['other'] !== NULL) { + $element['other']['#default_value'] = $value['other']; + } + return $value; + } + + // Return NULL so that current $input is used. + return NULL; + } + + /** + * Processes an 'other' element. + * + * See select list webform element for select list properties. + * + * @see \Drupal\Core\Render\Element\Select + */ + public static function processWebformOther(&$element, FormStateInterface $form_state, &$complete_form) { + // Remove 'webform_' prefix from type. + $type = str_replace('webform_', '', static::$type); + $properties = static::$properties; + + $element['#tree'] = TRUE; + + $element['#wrapper_attributes']['class'][] = "js-webform-$type-other"; + $element['#wrapper_attributes']['class'][] = "webform-$type-other"; + + $element[$type]['#type'] = static::$type; + $element[$type] += array_intersect_key($element, array_combine($properties, $properties)); + 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]['#error_no_message'] = TRUE; + + // Build other textfield. + $element['other']['#error_no_message'] = TRUE; + foreach ($element as $key => $value) { + if (strpos($key, '#other__') === 0) { + $other_key = str_replace('#other__', '#', $key); + if (!isset($element['other'][$other_key])) { + $element['other'][$other_key] = $value; + } + } + } + $element['other'] += [ + '#type' => 'textfield', + '#placeholder' => t('Enter other...'), + ]; + $element['other']['#wrapper_attributes']['class'][] = "js-webform-$type-other-input"; + $element['other']['#wrapper_attributes']['class'][] = "webform-$type-other-input"; + + // Remove options. + unset($element['#options']); + + // Set validation. + if (isset($element['#element_validate'])) { + $element['#element_validate'] = array_merge([[get_called_class(), 'validateWebformOther']], $element['#element_validate']); + } + else { + $element['#element_validate'] = [[get_called_class(), 'validateWebformOther']]; + } + + // Attach library. + $element['#attached']['library'][] = 'webform/webform.element.other'; + + // Process states. + webform_process_states($element, '#wrapper_attributes'); + + return $element; + } + + /** + * Validates an other element. + */ + public static function validateWebformOther(&$element, FormStateInterface $form_state, &$complete_form) { + // 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); + + // Get value. + $value = NestedArray::getValue($form_state->getValues(), $element['#parents']); + + // Get return value. + $return_value = []; + $element_value = $value[$type]; + $other_value = $value['other']; + if (static::isMultiple($element)) { + $element_value = array_filter($element_value); + $return_value += $element_value; + if (isset($element_value[static::OTHER_OPTION])) { + unset($return_value[static::OTHER_OPTION]); + if ($has_access && $other_value === '') { + static::setOtherError($element, $form_state); + return; + } + $return_value += [$other_value => $other_value]; + } + } + else { + $return_value = $element_value; + if ($element_value == static::OTHER_OPTION) { + if ($has_access && $other_value === '') { + static::setOtherError($element, $form_state); + return; + } + else { + $return_value = $other_value; + } + } + } + + // Determine if the return value is empty. + if (static::isMultiple($element)) { + $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) { + static::setElementError($element, $form_state); + } + + $form_state->setValueForElement($element[$type], NULL); + $form_state->setValueForElement($element['other'], NULL); + $form_state->setValueForElement($element, $return_value); + } + + /****************************************************************************/ + // Helper functions. + /****************************************************************************/ + + /** + * Determine if the webform element contains multiple values. + * + * @param array $element + * A webform element. + * + * @return bool + * TRUE if the webform element contains multiple values. + */ + protected static function isMultiple(array $element) { + return (!empty($element['#multiple']) || static::$type == 'checkboxes') ? TRUE : FALSE; + } + + /** + * Convert default value to element value. + * + * @param array $element + * A other form element. + * + * @return array + * An associative array container (element) type and other value. + */ + protected static function convertDefaultValueToElementValue($element) { + $type = str_replace('webform_', '', static::$type); + + $default_value = isset($element['#default_value']) ? $element['#default_value'] : NULL; + if (static::isMultiple($element)) { + // Handle edge case where $default_value is not an array. + if (!is_array($default_value)) { + return [$type => [], 'other' => NULL]; + } + + $default_options = array_combine($default_value, $default_value); + $flattened_options = OptGroup::flattenOptions($element['#options']); + if ($other_options = array_diff_key($default_options, $flattened_options)) { + return [ + $type => array_diff_key($default_options, $other_options) + [static::OTHER_OPTION => static::OTHER_OPTION], + 'other' => implode($element['#other__option_delimiter'], $other_options), + ]; + } + + return [$type => $default_options, 'other' => NULL]; + } + else { + if (!empty($default_value) && !WebformOptionsHelper::hasOption($default_value, $element['#options'])) { + return [$type => static::OTHER_OPTION, 'other' => $default_value]; + } + + return [$type => $default_value, 'other' => NULL]; + } + } + + /****************************************************************************/ + // Error functions. + /****************************************************************************/ + + /** + * Set element required error. + * + * @param array $element + * The webform element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected static function setElementError(array &$element, FormStateInterface $form_state) { + if (isset($element['#required_error'])) { + $form_state->setError($element, $element['#required_error']); + } + elseif (isset($element['#title'])) { + $form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']])); + } + else { + $form_state->setError($element); + } + } + + /** + * Set element required error. + * + * @param array $element + * The webform element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected static function setOtherError(array &$element, FormStateInterface $form_state) { + if (isset($element['#required_error'])) { + $form_state->setError($element['other'], $element['#required_error']); + } + elseif (isset($element['#title'])) { + $form_state->setError($element['other'], t('@name field is required.', ['@name' => $element['#title']])); + } + else { + $form_state->setError($element['other']); + } + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformRadiosOther.php b/web/modules/contrib/webform/src/Element/WebformRadiosOther.php new file mode 100644 index 000000000..805b593af --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformRadiosOther.php @@ -0,0 +1,17 @@ + 0, + '#max' => 5, + '#step' => 1, + '#star_size' => 'medium', + '#reset' => FALSE, + '#pre_render' => [ + [$class, 'preRenderWebformRating'], + ], + '#theme' => 'input__webform_rating', + ] + parent::getInfo(); + } + + /** + * Prepares a #type 'webform_rating' render element for input.html.twig. + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #description, #min, #max, #attributes, + * #step. + * + * @return array + * The $element with prepared variables ready for input.html.twig. + */ + public static function preRenderWebformRating(array $element) { + $element['#attributes']['type'] = 'range'; + Element::setAttributes($element, ['id', 'name', 'value', 'step', 'min', 'max']); + static::setAttributes($element, ['form-webform-rating']); + + // If value is an empty string set it the min. + if ($element['#attributes']['value'] == '') { + $element['#attributes']['value'] = $element['#attributes']['min']; + } + + $element['#children']['rateit'] = self::buildRateIt($element); + + return $element; + } + + /** + * Build RateIt div. + * + * @param array $element + * A rating element. + * + * @return string + * The RateIt div tag. + * + * @see https://github.com/gjunge/rateit.js/wiki + */ + public static function buildRateIt(array $element) { + // Add default properties since this element does not have to be a render + // element. + // @see \Drupal\webform\Plugin\WebformElement\WebformRating::formatHtml + $element += [ + '#min' => 0, + '#max' => 5, + '#step' => 1, + '#star_size' => 'medium', + '#reset' => FALSE, + ]; + $is_readonly = (!empty($element['#readonly']) || !empty($element['#attributes']['readonly'])); + + $attributes = [ + 'class' => ['rateit', 'svg'], + 'data-rateit-min' => $element['#min'], + 'data-rateit-max' => $element['#max'], + 'data-rateit-step' => $element['#step'], + 'data-rateit-resetable' => (!$is_readonly && $element['#reset']) ? 'true' : 'false', + 'data-rateit-readonly' => $is_readonly ? 'true' : 'false', + ]; + + // Set range element's #id. + if (isset($element['#id'])) { + $attributes['data-rateit-backingfld'] = '#' . $element['#id']; + } + + // Set value for HTML preview. + // @see \Drupal\webform\Plugin\WebformElement\WebformRating::formatHtml + if (isset($element['#value'])) { + $attributes['data-rateit-value'] = $element['#value']; + } + + if (isset($element['#starwidth']) && isset($element['#starheight'])) { + $attributes['data-rateit-starwidth'] = $element['#starwidth']; + $attributes['data-rateit-starheight'] = $element['#starheight']; + } + else { + // Set star width and height using the #star_size. + $sizes = ['large' => 32, 'medium' => 24, 'small' => 16]; + $size = (isset($sizes[$element['#star_size']])) ? $element['#star_size'] : 'small'; + $attributes['data-rateit-starwidth'] = $attributes['data-rateit-starheight'] = $sizes[$size]; + $attributes['class'][] = 'rateit-' . $size; + } + + return [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => $attributes, + '#attached' => [ + 'library' => ['webform/webform.element.rating'], + ], + ]; + + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformRoles.php b/web/modules/contrib/webform/src/Element/WebformRoles.php new file mode 100644 index 000000000..c7a14402b --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformRoles.php @@ -0,0 +1,52 @@ +getValue($element['#parents'], []); + $form_state->setValueForElement($element, array_values(array_filter($value))); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformSelectOther.php b/web/modules/contrib/webform/src/Element/WebformSelectOther.php new file mode 100644 index 000000000..f1773e636 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformSelectOther.php @@ -0,0 +1,34 @@ + TRUE, + '#process' => [ + [$class, 'processAjaxForm'], + [$class, 'processGroup'], + ], + '#pre_render' => [ + [$class, 'preRenderWebformSignature'], + ], + '#theme' => 'input__webform_signature', + '#theme_wrappers' => ['form_element'], + ]; + } + + /** + * Prepares a #type 'webform_signature' render element for input.html.twig. + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #description, #min, #max, #attributes, + * #step. + * + * @return array + * The $element with prepared variables ready for input.html.twig. + */ + public static function preRenderWebformSignature(array $element) { + $element['#attributes']['type'] = 'hidden'; + Element::setAttributes($element, ['name', 'value']); + static::setAttributes($element, ['js-webform-signature', 'form-webform-signature']); + + $build = [ + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + $build['reset'] = [ + '#type' => 'button', + '#value' => t('Reset'), + ]; + $build['canvas'] = [ + '#type' => 'html_tag', + '#tag' => 'canvas', + ]; + $element['#children'] = $build; + + $element['#attached']['library'][] = 'webform/webform.element.signature'; + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformTableSelectSort.php b/web/modules/contrib/webform/src/Element/WebformTableSelectSort.php new file mode 100644 index 000000000..7db515cc8 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformTableSelectSort.php @@ -0,0 +1,284 @@ + TRUE, + '#js_select' => TRUE, + '#responsive' => TRUE, + '#sticky' => FALSE, + '#pre_render' => [ + [$class, 'preRenderTable'], + [$class, 'preRenderWebformTableSelectSort'], + ], + '#process' => [ + [$class, 'processWebformTableSelectSort'], + ], + '#options' => [], + '#empty' => '', + '#theme' => 'table__tableselect_sort', + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + $value = []; + $element += ['#default_value' => []]; + foreach ($element['#default_value'] as $key => $flag) { + if ($flag) { + $value[$key] = $key; + } + } + return $value; + } + else { + if (is_array($input)) { + uasort($input, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + $values = []; + foreach ($input as $key => $item) { + if (!empty($item['checkbox'])) { + $values[$item['checkbox']] = $item['checkbox']; + } + } + return $values; + } + else { + return []; + } + } + } + + /** + * Prepares a 'webform_tableselect_sort' #type element for rendering. + * + * @return array + * The processed element. + */ + public static function preRenderWebformTableSelectSort($element) { + $rows = []; + $header = $element['#header']; + + if (!empty($element['#options'])) { + // Generate a table row for each selectable item in #options. + foreach (Element::children($element) as $key) { + $row = []; + + $row['data'] = []; + + // Set the row to be draggable. + $row['class'] = ['draggable']; + if (isset($element['#options'][$key]['#attributes'])) { + $row += $element['#options'][$key]['#attributes']; + } + + // Render the checkbox element. + $row['data'][] = \Drupal::service('renderer')->render($element[$key]['checkbox']); + + // As table.html.twig only maps header and row columns by order, create + // the correct order by iterating over the header fields. + foreach ($element['#header'] as $fieldname => $title) { + // A row cell can span over multiple headers, which means less row + // cells than headers could be present. + if (isset($element['#options'][$key][$fieldname])) { + // A header can span over multiple cells and in this case the cells + // are passed in an array. The order of this array determines the + // order in which they are added. + if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) { + foreach ($element['#options'][$key][$fieldname] as $cell) { + $row['data'][] = $cell; + } + } + else { + $row['data'][] = $element['#options'][$key][$fieldname]; + } + } + } + + // Render the weight element. + $row['data'][] = \Drupal::service('renderer')->render($element[$key]['weight']); + + $rows[] = $row; + } + // Add an empty header or a "Select all" checkbox to provide room for the + // checkboxes in the first table column. + if ($element['#js_select']) { + // Add a "Select all" checkbox. + $element['#attached']['library'][] = 'core/drupal.tableselect'; + array_unshift($header, ['class' => ['select-all']]); + } + else { + // Add an empty header when radio buttons are displayed or a "Select all" + // checkbox is not desired. + array_unshift($header, ''); + } + } + + // Append weight to header. + $header[] = t('Weight'); + + // Set header and rows. + $element['#header'] = $header; + $element['#rows'] = $rows; + + // Attach table sort. + $element['#attributes']['class'][] = 'js-tableselect-sort'; + $element['#attributes']['class'][] = 'tableselect-sort'; + $element['#attached']['library'][] = 'webform/webform.element.tableselect_sort'; + + return $element; + } + + /** + * Creates checkbox and weights to populate a 'webform_tableselect_order' table. + * + * @param array $element + * An associative array containing the properties and children of the + * webform_tableselect_order element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete webform structure. + * + * @return array + * The processed element. + */ + public static function processWebformTableSelectSort(&$element, FormStateInterface $form_state, &$complete_form) { + $value = is_array($element['#value']) ? $element['#value'] : []; + + // Add validate callback that extracts the associative array of options. + if (isset($element['#element_validate'])) { + array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformTableSelectOrder']); + } + else { + $element['#element_validate'][] = [get_called_class(), 'validateWebformTableSelectOrder']; + } + + $element['#tree'] = TRUE; + + if (count($element['#options']) > 0) { + if (!isset($element['#default_value']) || $element['#default_value'] === 0) { + $element['#default_value'] = []; + } + + // Place checked options first. + $options = []; + foreach ($value as $checked_option_key) { + if (isset($element['#options'][$checked_option_key])) { + $options[$checked_option_key] = $element['#options'][$checked_option_key]; + unset($element['#options'][$checked_option_key]); + } + } + $options += $element['#options']; + $element['#options'] = $options; + + // Set delta and default weight. + $delta = count($element['#options']); + $weight = 0; + + // Create a checkbox and weight for each item in #options in such a way + // that the value of the webform_tableselect_order element behaves as if + // it had been of type checkboxes with weight. + 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'], + ]); + } + } + + $element[$key]['checkbox'] = [ + '#type' => 'checkbox', + '#title' => $checkbox_title, + '#title_display' => 'invisible', + '#return_value' => $key, + '#default_value' => isset($value[$key]) ? $key : NULL, + '#attributes' => $element['#attributes'], + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + ]; + $element[$key]['weight'] = [ + '#type' => 'weight', + '#title' => $weight_title, + '#title_display' => 'invisible', + '#delta' => $delta, + '#default_value' => $weight++, + '#attributes' => [ + 'class' => ['table-sort-weight'], + ], + ]; + } + } + } + else { + $element['#value'] = []; + } + + // Enable tabledrag. + $element['#tabledrag'] = [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'table-sort-weight', + ], + ]; + + return $element; + } + + /** + * Validates webform_tableselect_other. + */ + public static function validateWebformTableSelectOrder(&$element, FormStateInterface $form_state, &$complete_form) { + // Get and sort checked values. + $checked_values = []; + foreach (Element::children($element) as $key) { + if ($element[$key]['checkbox']['#value']) { + $checked_values[] = [ + 'value' => $element[$key]['checkbox']['#value'], + 'weight' => $element[$key]['weight']['#value'], + ]; + } + } + uasort($checked_values, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + + // Set values. + $values = []; + foreach ($checked_values as $item) { + $values[$item['value']] = $item['value']; + } + + // Clear the element's value by setting it to NULL. + $form_state->setValueForElement($element, NULL); + + // Now, set the values as the element's value. + $form_state->setValueForElement($element, $values); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformTableSort.php b/web/modules/contrib/webform/src/Element/WebformTableSort.php new file mode 100644 index 000000000..36eda3180 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformTableSort.php @@ -0,0 +1,256 @@ + TRUE, + '#js_select' => TRUE, + '#responsive' => TRUE, + '#sticky' => FALSE, + '#pre_render' => [ + [$class, 'preRenderTable'], + [$class, 'preRenderWebformTableSort'], + ], + '#process' => [ + [$class, 'processWebformTableSort'], + ], + '#options' => [], + '#empty' => '', + '#theme' => 'table__table_sort', + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + $value = []; + $element += ['#default_value' => []]; + foreach ($element['#default_value'] as $key => $flag) { + if ($flag) { + $value[$key] = $key; + } + } + return $value; + } + else { + if (is_array($input)) { + uasort($input, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + $values = []; + foreach ($input as $key => $item) { + if (!empty($item['value'])) { + $values[$item['value']] = $item['value']; + } + } + return $values; + } + else { + return []; + } + } + } + + /** + * Prepares a 'webform_table_sort' #type element for rendering. + * + * @return array + * The processed element. + */ + public static function preRenderWebformTableSort($element) { + $rows = []; + $header = $element['#header']; + if (!empty($element['#options'])) { + // Generate a table row for each selectable item in #options. + foreach (Element::children($element) as $key) { + $row = []; + + $row['data'] = []; + + // Set the row to be draggable. + $row['class'] = ['draggable']; + if (isset($element['#options'][$key]['#attributes'])) { + $row += $element['#options'][$key]['#attributes']; + } + + // As table.html.twig only maps header and row columns by order, create + // the correct order by iterating over the header fields. + foreach ($element['#header'] as $fieldname => $title) { + // A row cell can span over multiple headers, which means less row + // cells than headers could be present. + if (isset($element['#options'][$key][$fieldname])) { + // A header can span over multiple cells and in this case the cells + // are passed in an array. The order of this array determines the + // order in which they are added. + if (is_array($element['#options'][$key][$fieldname]) && !isset($element['#options'][$key][$fieldname]['data'])) { + foreach ($element['#options'][$key][$fieldname] as $cell) { + $row['data'][] = $cell; + } + } + else { + $row['data'][] = $element['#options'][$key][$fieldname]; + } + } + } + + // Render the weight and hidden value element. + $weight = [$element[$key]['weight'], $element[$key]['value']]; + $row['data'][] = \Drupal::service('renderer')->render($weight); + + $rows[] = $row; + } + } + + // Append weight to header. + $header[] = t('Weight'); + + // Set header and rows. + $element['#header'] = $header; + $element['#rows'] = $rows; + + // Attach table sort. + $element['#attributes']['class'][] = 'js-table-sort'; + $element['#attributes']['class'][] = 'table-sort'; + + return $element; + } + + /** + * Creates checkbox and weights to populate a 'webform_table_order' table. + * + * @param array $element + * An associative array containing the properties and children of the + * webform_table_order element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete webform structure. + * + * @return array + * The processed element. + */ + public static function processWebformTableSort(&$element, FormStateInterface $form_state, &$complete_form) { + $value = is_array($element['#value']) ? $element['#value'] : []; + + // Add validate callback that extracts the associative array of options. + if (isset($element['#element_validate'])) { + array_unshift($element['#element_validate'], [get_called_class(), 'validateWebformTableSelectOrder']); + } + else { + $element['#element_validate'][] = [get_called_class(), 'validateWebformTableSelectOrder']; + } + + $element['#tree'] = TRUE; + + if (count($element['#options']) > 0) { + if (!isset($element['#default_value']) || $element['#default_value'] === 0) { + $element['#default_value'] = []; + } + + // Place checked options first. + $options = []; + foreach ($value as $checked_option_key) { + if (isset($element['#options'][$checked_option_key])) { + $options[$checked_option_key] = $element['#options'][$checked_option_key]; + unset($element['#options'][$checked_option_key]); + } + } + $options += $element['#options']; + $element['#options'] = $options; + + // Set delta and default weight. + $delta = count($element['#options']); + $weight = 0; + + foreach ($element['#options'] as $key => $choice) { + // 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'], + ]); + } + } + + $element[$key]['value'] = [ + '#type' => 'hidden', + '#value' => $key, + ]; + $element[$key]['weight'] = [ + '#type' => 'weight', + '#title' => $weight_title, + '#title_display' => 'invisible', + '#delta' => $delta, + '#default_value' => $weight++, + '#attributes' => [ + 'class' => ['table-sort-weight'], + ], + ]; + } + } + } + else { + $element['#value'] = []; + } + + // Enable tabledrag. + $element['#tabledrag'] = [ + [ + 'action' => 'order', + 'relationship' => 'sibling', + 'group' => 'table-sort-weight', + ], + ]; + + return $element; + } + + /** + * Validates webform_table_other. + */ + public static function validateWebformTableSelectOrder(&$element, FormStateInterface $form_state, &$complete_form) { + // Get and sort checked values. + $checked_values = []; + foreach (Element::children($element) as $key) { + if ($element[$key]['value']['#value']) { + $checked_values[] = [ + 'value' => $element[$key]['value']['#value'], + 'weight' => $element[$key]['weight']['#value'], + ]; + } + } + uasort($checked_values, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']); + + // Set values. + $values = []; + foreach ($checked_values as $item) { + $values[$item['value']] = $item['value']; + } + + // Clear the element's value by setting it to NULL. + $form_state->setValueForElement($element, NULL); + + // Now, set the values as the element's value. + $form_state->setValueForElement($element, $values); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformTelephone.php b/web/modules/contrib/webform/src/Element/WebformTelephone.php new file mode 100644 index 000000000..551e6f988 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformTelephone.php @@ -0,0 +1,58 @@ + 'select', + '#title' => t('Type'), + '#title_display' => 'invisible', + '#options' => 'phone_types', + '#empty_option' => t('- Type -'), + ]; + $elements['phone'] = [ + '#type' => 'tel', + '#title' => t('Phone'), + '#title_display' => 'invisible', + '#international' => TRUE, + ]; + $elements['ext'] = [ + '#title' => t('Ext:'), + '#type' => 'number', + '#size' => 5, + '#min' => 0, + ]; + return $elements; + } + + /** + * Processes a composite webform element. + */ + public static function processWebformComposite(&$element, FormStateInterface $form_state, &$complete_form) { + $element = parent::processWebformComposite($element, $form_state, $complete_form); + $element['#attached']['library'][] = 'webform/webform.element.composite_telephone'; + return $element; + } +} diff --git a/web/modules/contrib/webform/src/Element/WebformTermSelect.php b/web/modules/contrib/webform/src/Element/WebformTermSelect.php new file mode 100644 index 000000000..e6c5f61ae --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformTermSelect.php @@ -0,0 +1,85 @@ + '', + '#tree_delimiter' => '-', + '#breadcrumb' => FALSE, + '#breadcrumb_delimiter' => ' › ', + '#tree_delimiter' => '-', + ] + parent::getInfo(); + } + + /** + * {@inheritdoc} + */ + public static function processSelect(&$element, FormStateInterface $form_state, &$complete_form) { + self::setOptions($element); + + $element = parent::processSelect($element, $form_state, $complete_form); + + // 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'; + + return $element; + } + + /** + * {@inheritdoc} + */ + public static function setOptions(array &$element) { + if (!empty($element['#options'])) { + return; + } + + if (!\Drupal::moduleHandler()->moduleExists('taxonomy')) { + return []; + } + + if (empty($element['#vocabulary'])) { + $element['#options'] = []; + return; + } + + /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $tree = $taxonomy_storage->loadTree($element['#vocabulary']); + + $options = []; + if (!empty($element['#breadcrumb'])) { + // Build term breadcrumbs. + $element += ['#breadcrumb_delimiter' => ' › ']; + $breadcrumb = []; + foreach ($tree as $item) { + $breadcrumb[$item->depth] = $item->name; + $breadcrumb = array_slice($breadcrumb, 0, $item->depth + 1); + $options[$item->tid] = implode($element['#breadcrumb_delimiter'], $breadcrumb); + } + } + else { + $element += ['#tree_delimiter' => '-']; + // Build hierarchical term tree. + foreach ($tree as $item) { + $options[$item->tid] = str_repeat($element['#tree_delimiter'], $item->depth) . $item->name; + } + } + $element['#options'] = $options; + } +} diff --git a/web/modules/contrib/webform/src/Element/WebformTime.php b/web/modules/contrib/webform/src/Element/WebformTime.php new file mode 100644 index 000000000..638b8e266 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformTime.php @@ -0,0 +1,153 @@ + 'time', + * '#title' => $this->t('Time'), + * '#default_value' => '12:00 AM' + * ); + * @endcode + * + * @FormElement("webform_time") + */ +class WebformTime extends FormElement { + + /** + * {@inheritdoc} + */ + public function getInfo() { + $class = get_class($this); + return [ + '#input' => TRUE, + '#theme' => 'input__time', + '#process' => [[$class, 'processWebformTime']], + '#pre_render' => [[$class, 'preRenderWebformTime']], + '#element_validate' => [[$class, 'validateWebformTime']], + '#theme_wrappers' => ['form_element'], + '#time_format' => 'H:i', + '#size' => 10, + '#maxlength' => 10, + ]; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + // 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'])); + return $element['#default_value']; + } + } + + return $input; + } + + /** + * Processes a time webform element. + * + * @param array $element + * The webform element to process. Properties used: + * - #time_format: The time format used in PHP formats. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete webform structure. + * + * @return array + * The processed element. + */ + public static function processWebformTime(&$element, FormStateInterface $form_state, &$complete_form) { + // Attach JS support for the time field, if we can determine which time + // format should be used. + if (!empty($element['#time_format'])) { + $element['#attached']['library'][] = 'webform/webform.element.time'; + $element['#attributes']['data-webform-time-format'] = [$element['#time_format']]; + } + return $element; + } + + /** + * Webform element validation handler for #type 'webform_time'. + * + * Note that #required is validated by _form_validate() already. + */ + public static function validateWebformTime(&$element, FormStateInterface $form_state, &$complete_form) { + $has_access = (!isset($element['#access']) || $element['#access'] === TRUE); + + $value = $element['#value']; + if ($value === '') { + return; + } + + $time = strtotime($value); + if ($time === FALSE) { + if ($has_access) { + if (isset($element['#title'])) { + $form_state->setError($element, t('%name must be a valid time.', ['%name' => $element['#title']])); + } + else { + $form_state->setError($element); + } + } + return; + } + + $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title']; + $time_format = (!empty($element['#time_format'])) ? $element['#time_format'] : DateFormat::load('html_time')->getPattern(); + + // Ensure that the input is greater than the #min property, if set. + if ($has_access && isset($element['#min'])) { + $min = strtotime($element['#min']); + if ($time < $min) { + $form_state->setError($element, t('%name must be on or after %min.', [ + '%name' => $name, + '%min' => date($time_format, $min), + ])); + } + } + + // Ensure that the input is less than the #max property, if set. + if ($has_access && isset($element['#max'])) { + $max = strtotime($element['#max']); + if ($time > $max) { + $form_state->setError($element, t('%name must be on or before %max.', [ + '%name' => $name, + '%max' => date($time_format, $max), + ])); + } + } + + $form_state->setValueForElement($element, date('H:i:s', $time)); + } + + /** + * Adds form-specific attributes to a 'date' #type element. + * + * @param array $element + * An associative array containing the properties of the element. + * + * @return array + * The $element with prepared variables ready for #theme 'input__time'. + */ + public static function preRenderWebformTime(array $element) { + $element['#attributes']['type'] = 'time'; + Element::setAttributes($element, ['id', 'name', 'type', 'value', 'size', 'min', 'max', 'step']); + static::setAttributes($element, ['form-time']); + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformToggle.php b/web/modules/contrib/webform/src/Element/WebformToggle.php new file mode 100644 index 000000000..6ffa7c497 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformToggle.php @@ -0,0 +1,93 @@ + 'light', + '#toggle_size' => 'medium', + '#on_text' => '', + '#off_text' => '', + ]; + } + + /** + * Prepares a #type 'checkbox' render element for input.html.twig. + * + * @param array $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #return_value, #description, #required, + * #attributes, #checked. + * + * @return array + * The $element with prepared variables ready for input.html.twig. + */ + public static function preRenderCheckbox($element) { + $element = parent::preRenderCheckbox($element); + + $element += [ + '#toggle_size' => 'medium', + '#toggle_theme' => 'light', + '#on_text' => '', + '#off_text' => '', + ]; + + // Toggle heights. + $sizes = ['large' => 36, 'medium' => 24, 'small' => 16]; + $height = $sizes[$element['#toggle_size']]; + if (!empty($element['#on_text']) || !empty($element['#off_text'])) { + $width = $height * 3; + } + else { + $width = $height * 2; + } + + $attributes = [ + 'class' => [ + 'js-webform-toggle', + 'webform-toggle', + 'toggle', + 'toggle-' . $element['#toggle_size'], + 'toggle-' . $element['#toggle_theme'], + ], + 'data-toggle-height' => $height, + 'data-toggle-width' => $width, + 'data-toggle-text-on' => $element['#on_text'], + 'data-toggle-text-off' => $element['#off_text'], + ]; + + $element['#children']['toggles'] = [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => $attributes, + '#attached' => [ + 'library' => ['webform/webform.element.toggle'], + ], + ]; + + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformToggles.php b/web/modules/contrib/webform/src/Element/WebformToggles.php new file mode 100644 index 000000000..31922a971 --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformToggles.php @@ -0,0 +1,38 @@ + FALSE]; + $info['#tags'] = TRUE; + $info['#default_value'] = []; + $info['#element_validate'] = [ + [$class, 'validateEntityAutocomplete'], + [$class, 'validateWebformUsers'], + ]; + return $info; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($element['#default_value']) { + if (!(reset($element['#default_value']) instanceof EntityInterface)) { + $element['#default_value'] = User::loadMultiple($element['#default_value']); + } + } + return parent::valueCallback($element, $input, $form_state); + } + + /** + * Webform element validation handler for webform_users elements. + */ + public static function validateWebformUsers(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $form_state->getValue($element['#parents'], []); + $uids = []; + if ($value) { + foreach ($value as $item) { + if (isset($item)) { + $uids[] = $item['target_id']; + } + } + } + $form_state->setValueForElement($element, $uids); + } + +} diff --git a/web/modules/contrib/webform/src/Element/WebformVideoFile.php b/web/modules/contrib/webform/src/Element/WebformVideoFile.php new file mode 100644 index 000000000..dd8ed3ded --- /dev/null +++ b/web/modules/contrib/webform/src/Element/WebformVideoFile.php @@ -0,0 +1,17 @@ +langcode; + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->uid ? User::load($this->uid) : NULL; + } + + /** + * Sets the entity owner's user entity. + * + * @param \Drupal\user\UserInterface $account + * The owner user entity. + * + * @return $this + */ + public function setOwner(UserInterface $account) { + $this->uid = $account->id(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->uid; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->uid = ($uid) ? $uid : NULL; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setStatus($status) { + if ($status === NULL || $status === WebformInterface::STATUS_SCHEDULED) { + $this->status = WebformInterface::STATUS_SCHEDULED; + } + elseif ($status === WebformInterface::STATUS_OPEN) { + $this->status = WebformInterface::STATUS_OPEN; + } + elseif ($status === WebformInterface::STATUS_CLOSED) { + $this->status = WebformInterface::STATUS_CLOSED; + } + else { + $this->status = ((bool) $status) ? WebformInterface::STATUS_OPEN : WebformInterface::STATUS_CLOSED; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function status() { + return $this->isOpen(); + } + + /** + * {@inheritdoc} + */ + public function isOpen() { + switch ($this->status) { + case WebformInterface::STATUS_OPEN: + return TRUE; + + case WebformInterface::STATUS_CLOSED: + return FALSE; + + case WebformInterface::STATUS_SCHEDULED: + $is_opened = TRUE; + if ($this->open && strtotime($this->open) > time()) { + $is_opened = FALSE; + } + + $is_closed = FALSE; + if ($this->close && strtotime($this->close) < time()) { + $is_closed = TRUE; + } + + if ($is_opened && !$is_closed) { + return TRUE; + } + return FALSE; + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isClosed() { + return !$this->isOpen(); + } + + /** + * {@inheritdoc} + */ + public function isScheduled() { + return ($this->status === WebformInterface::STATUS_SCHEDULED); + } + + /** + * {@inheritdoc} + */ + public function isTemplate() { + return $this->template ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function isConfidential() { + return $this->getSetting('form_confidential'); + } + + /** + * {@inheritdoc} + */ + public function hasSubmissions() { + /** @var \Drupal\webform\WebformSubmissionStorageInterface $submission_storage */ + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + return ($submission_storage->getTotal($this)) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function hasTranslations() { + if (isset($this->hasTranslations)) { + return $this->hasTranslations; + } + + // Make sure the config translation module is enabled. + if (!\Drupal::moduleHandler()->moduleExists('config_translation')) { + $this->hasTranslations = FALSE; + return $this->hasTranslations; + } + + /** @var \Drupal\locale\LocaleConfigManager $local_config_manager */ + $local_config_manager = \Drupal::service('locale.config_manager'); + $languages = \Drupal::languageManager()->getLanguages(); + foreach ($languages as $langcode => $language) { + if ($local_config_manager->hasTranslation('webform.webform.' . $this->id(), $langcode)) { + $this->hasTranslations = TRUE; + return $this->hasTranslations; + } + } + + $this->hasTranslations = FALSE; + return $this->hasTranslations; + } + + /** + * {@inheritdoc} + */ + public function hasPage() { + $settings = $this->getSettings(); + return $settings['page'] ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function hasManagedFile() { + $this->initElements(); + return $this->hasManagedFile; + } + + /** + * {@inheritdoc} + */ + public function hasFlexboxLayout() { + $this->initElements(); + return $this->hasFlexboxLayout; + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAssets() { + $shared_css = \Drupal::config('webform.settings')->get('assets.css') ?: ''; + $webform_css = $this->css ?: ''; + + $shared_javascript = \Drupal::config('webform.settings')->get('assets.javascript') ?: ''; + $webform_javascript = $this->javascript ?: ''; + + return [ + 'css' => $shared_css . (($shared_css && $webform_css) ? PHP_EOL : '') . $webform_css, + 'javascript' => $shared_javascript . (($shared_javascript && $webform_javascript) ? PHP_EOL : '') . $webform_javascript, + ]; + } + + /** + * {@inheritdoc} + */ + public function getCss() { + return $this->css; + } + + /** + * {@inheritdoc} + */ + public function setCss($css) { + $this->css = $css; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getJavaScript() { + return $this->javascript; + } + + /** + * {@inheritdoc} + */ + public function setJavaScript($javascript) { + $this->javascript = $javascript; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSettings() { + return $this->settings + self::getDefaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function setSettings(array $settings) { + // Always apply the default settings. + $this->settings = self::getDefaultSettings(); + // Now apply custom settings. + foreach ($settings as $name => $value) { + $this->settings[$name] = $value; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSetting($key) { + $settings = $this->getSettings(); + return (isset($settings[$key])) ? $settings[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public function setSetting($key, $value) { + $settings = $this->getSettings(); + $settings[$key] = $value; + $this->setSettings($settings); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccessRules() { + return $this->access; + } + + /** + * {@inheritdoc} + */ + public function setAccessRules(array $access) { + $this->access = $access; + return $this; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultSettings() { + return [ + 'page' => TRUE, + 'page_submit_path' => '', + 'page_confirm_path' => '', + 'form_submit_label' => '', + 'form_submit_once' => FALSE, + 'form_submit_attributes' => [], + 'form_exception_message' => '', + 'form_closed_message' => '', + 'form_previous_submissions' => TRUE, + 'form_confidential' => FALSE, + 'form_confidential_message' => '', + 'form_prepopulate' => FALSE, + 'form_prepopulate_source_entity' => FALSE, + 'form_disable_autocomplete' => FALSE, + 'form_novalidate' => FALSE, + 'form_unsaved' => FALSE, + 'form_disable_back' => FALSE, + 'form_autofocus' => FALSE, + 'form_details_toggle' => FALSE, + 'wizard_progress_bar' => TRUE, + 'wizard_progress_pages' => FALSE, + 'wizard_progress_percentage' => FALSE, + 'wizard_next_button_label' => '', + 'wizard_next_button_attributes' => [], + 'wizard_prev_button_label' => '', + 'wizard_prev_button_attributes' => [], + 'wizard_start_label' => '', + 'wizard_complete' => TRUE, + 'wizard_complete_label' => '', + 'preview' => DRUPAL_DISABLED, + 'preview_next_button_label' => '', + 'preview_next_button_attributes' => [], + 'preview_prev_button_label' => '', + 'preview_prev_button_attributes' => [], + 'preview_message' => '', + 'draft' => FALSE, + 'draft_auto_save' => FALSE, + 'draft_button_label' => '', + 'draft_button_attributes' => [], + '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' => [], + 'limit_total' => NULL, + 'limit_total_message' => '', + 'limit_user' => NULL, + 'limit_user_message' => '', + 'purge' => WebformSubmissionStorageInterface::PURGE_NONE, + 'purge_days' => NULL, + 'entity_limit_total' => NULL, + 'entity_limit_user' => NULL, + 'results_disabled' => FALSE, + 'results_disabled_ignore' => FALSE, + 'token_update' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public static function getDefaultAccessRules() { + return [ + 'create' => [ + 'roles' => [ + 'anonymous', + 'authenticated', + ], + 'users' => [], + ], + 'view_any' => [ + 'roles' => [], + 'users' => [], + ], + 'update_any' => [ + 'roles' => [], + 'users' => [], + ], + 'delete_any' => [ + 'roles' => [], + 'users' => [], + ], + 'purge_any' => [ + 'roles' => [], + 'users' => [], + ], + 'view_own' => [ + 'roles' => [], + 'users' => [], + ], + 'update_own' => [ + 'roles' => [], + 'users' => [], + ], + 'delete_own' => [ + 'roles' => [], + 'users' => [], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function checkAccessRules($operation, AccountInterface $account, WebformSubmissionInterface $webform_submission = NULL) { + // Always grant access to "admin" which are webform and form + // submission administrators. + if ($account->hasPermission('administer webform') || $account->hasPermission('administer webform submission')) { + return TRUE; + } + + // 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 FALSE; + } + else { + $operation = 'create'; + } + } + $access_rules = $this->getAccessRules(); + + if (isset($access_rules[$operation]) + && in_array($operation, ['create', 'view_any', 'update_any', 'delete_any', 'purge_any', 'view_own']) + && $this->checkAccessRule($access_rules[$operation], $account)) { + return TRUE; + } + elseif (isset($access_rules[$operation . '_any']) + && $this->checkAccessRule($access_rules[$operation . '_any'], $account)) { + return TRUE; + } + elseif (isset($access_rules[$operation . '_own']) + && $account->isAuthenticated() && $webform_submission + && $account->id() === $webform_submission->getOwnerId() + && $this->checkAccessRule($access_rules[$operation . '_own'], $account)) { + return TRUE; + } + else { + 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 + * The access result. Returns a TRUE if access is allowed. + */ + 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; + } + else { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function getSubmissionForm(array $values = [], $operation = 'default') { + // Set this webform's id. + $values['webform_id'] = $this->id(); + + $webform_submission = $this->entityTypeManager() + ->getStorage('webform_submission') + ->create($values); + + return \Drupal::service('entity.form_builder') + ->getForm($webform_submission, $operation); + } + + /** + * {@inheritdoc} + */ + public function getElementsRaw() { + return $this->elements; + } + + /** + * {@inheritdoc} + */ + public function getElementsOriginalRaw() { + return $this->elementsOriginal; + } + + /** + * {@inheritdoc} + */ + public function getElementsDecoded() { + $this->initElements(); + return $this->elementsDecoded; + } + + /** + * {@inheritdoc} + */ + public function getElementsInitialized() { + $this->initElements(); + return $this->elementsInitialized; + } + + /** + * {@inheritdoc} + */ + public function getElementsInitializedAndFlattened($operation = NULL) { + $this->initElements(); + return $this->checkElementsFlattenedAccess($operation, $this->elementsInitializedAndFlattened); + } + + /** + * {@inheritdoc} + */ + public function getElementsDecodedAndFlattened($operation = NULL) { + $this->initElements(); + return $this->checkElementsFlattenedAccess($operation, $this->elementsDecodedAndFlattened); + } + + /** + * {@inheritdoc} + */ + public function getElementsInitializedFlattenedAndHasValue($operation = NULL) { + $this->initElements(); + return $this->checkElementsFlattenedAccess($operation, $this->elementsInitializedFlattenedAndHasValue); + } + + /** + * Check operation access for each element. + * + * @param string $operation + * (optional) The operation that is to be performed on the element. + * @param array $elements + * An associative array of flattened form elements. + * + * @return array + * An associative array of flattened form elements with each element's + * operation access checked. + */ + protected function checkElementsFlattenedAccess($operation = NULL, array $elements) { + if ($operation === NULL) { + return $elements; + } + + /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + foreach ($elements as $key => $element) { + $element_handler = $element_manager->getElementInstance($element); + if (!$element_handler->checkAccessRules($operation, $element)) { + unset($elements[$key]); + } + } + return $elements; + } + + /** + * {@inheritdoc} + */ + public function getElementsSelectorOptions() { + /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + + $selectors = []; + $elements = $this->getElementsInitializedAndFlattened(); + foreach ($elements as $element) { + $element_handler = $element_manager->getElementInstance($element); + $selectors += $element_handler->getElementSelectorOptions($element); + } + return $selectors; + } + + /** + * {@inheritdoc} + */ + public function setElements(array $elements) { + $this->elements = Yaml::encode($elements); + $this->resetElements(); + return $this; + } + + /** + * Initialize parse webform elements. + */ + protected function initElements() { + if (isset($this->elementsInitialized)) { + return; + } + + $this->hasManagedFile = FALSE; + $this->hasFlexboxLayout = FALSE; + $this->elementsDecodedAndFlattened = []; + $this->elementsInitializedAndFlattened = []; + $this->elementsInitializedFlattenedAndHasValue = []; + $this->elementsTranslations = []; + try { + /** @var \Drupal\webform\WebformTranslationManagerInterface $translation_manager */ + $translation_manager = \Drupal::service('webform.translation_manager'); + /** @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 ($this->langcode != $language_manager->getCurrentLanguage()->getId()) { + $elements = $translation_manager->getConfigElements($this); + $this->elementsTranslations = Yaml::decode($this->elements); + } + else { + $elements = Yaml::decode($this->elements); + } + + // Since YAML supports simple values. + $elements = (is_array($elements)) ? $elements : []; + $this->elementsDecoded = $elements; + } + catch (\Exception $exception) { + $link = $this->link(t('Edit'), 'edit-form'); + \Drupal::logger('webform') + ->notice('%title elements are not valid. @message', [ + '%title' => $this->label(), + '@message' => $exception->getMessage(), + 'link' => $link, + ]); + $elements = FALSE; + } + + if ($elements !== FALSE) { + $this->initElementsRecursive($elements); + $this->invokeHandlers('alterElements', $elements, $this); + } + + $this->elementsInitialized = $elements; + } + + /** + * Reset parsed and cached webform elements. + */ + protected function resetElements() { + $this->pages = NULL; + $this->hasManagedFile = NULL; + $this->hasFlexboxLayout = NULL; + $this->elementsDecoded = NULL; + $this->elementsInitialized = NULL; + $this->elementsDecodedAndFlattened = NULL; + $this->elementsInitializedAndFlattened = NULL; + $this->elementsInitializedFlattenedAndHasValue = NULL; + $this->elementsTranslations = NULL; + } + + /** + * Initialize webform elements into a flatten array. + * + * @param array $elements + * The webform elements. + * @param string $parent + * The parent key. + * @param int $depth + * The element's depth. + */ + protected function initElementsRecursive(array &$elements, $parent = '', $depth = 0) { + /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + + /** @var \Drupal\Core\Render\ElementInfoManagerInterface $element_info */ + $element_info = \Drupal::service('plugin.manager.element_info'); + + // Remove ignored properties. + $elements = WebformElementHelper::removeIgnoredProperties($elements); + + foreach ($elements as $key => &$element) { + if (Element::property($key) || !is_array($element)) { + continue; + } + + // Apply translation to element. + if (isset($this->elementsTranslations[$key])) { + WebformElementHelper::applyTranslation($element, $this->elementsTranslations[$key]); + } + + // Copy only the element properties to decoded and flattened elements. + $this->elementsDecodedAndFlattened[$key] = WebformElementHelper::getProperties($element); + + // Set id, key, parent_key, depth, and parent children. + $element['#webform_id'] = $this->id() . '--' . $key; + $element['#webform_key'] = $key; + $element['#webform_parent_key'] = $parent; + $element['#webform_parent_flexbox'] = FALSE; + $element['#webform_depth'] = $depth; + $element['#webform_children'] = []; + $element['#webform_multiple'] = FALSE; + $element['#webform_composite'] = FALSE; + + if (!empty($parent)) { + $parent_element = $this->elementsInitializedAndFlattened[$parent]; + // Add element to the parent element's children. + $parent_element['#webform_children'][$key] = $key; + // Set #parent_flexbox to TRUE is the parent element is a + // 'webform_flexbox'. + $element['#webform_parent_flexbox'] = (isset($parent_element['#type']) && $parent_element['#type'] == 'webform_flexbox') ? TRUE : FALSE; + } + + // Set #title and #admin_title to NULL if it is not defined. + $element += [ + '#title' => NULL, + '#admin_title' => NULL, + ]; + + // If #private set #access. + if (!empty($element['#private'])) { + $element['#access'] = $this->access('submission_view_any'); + } + + $element_handler = NULL; + if (isset($element['#type'])) { + // Load the element's handler. + $element_handler = $element_manager->getElementInstance($element); + + // Initialize the element. + $element_handler->initialize($element); + + // Track managed file upload. + if ($element_handler instanceof WebformManagedFileBase) { + $this->hasManagedFile = TRUE; + } + + // Track flexbox. + if ($element['#type'] == 'flexbox' || $element['#type'] == 'webform_flexbox') { + $this->hasFlexboxLayout = TRUE; + } + + $element['#webform_multiple'] = $element_handler->hasMultipleValues($element); + $element['#webform_composite'] = $element_handler->isComposite(); + } + + // Copy only the element properties to initialized and flattened elements. + $this->elementsInitializedAndFlattened[$key] = WebformElementHelper::getProperties($element); + + // Check if element has value (aka can be exported) and add it to + // flattened has value array. + if ($element_handler && $element_handler->isInput($element)) { + $this->elementsInitializedFlattenedAndHasValue[$key] =& $this->elementsInitializedAndFlattened[$key]; + } + + $this->initElementsRecursive($element, $key, $depth + 1); + } + } + + /** + * {@inheritdoc} + */ + public function getElement($key) { + $elements_flattened = $this->getElementsInitializedAndFlattened(); + return (isset($elements_flattened[$key])) ? $elements_flattened[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getElementDecoded($key) { + $elements = $this->getElementsDecodedAndFlattened(); + return (isset($elements[$key])) ? $elements[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getElementInitialized($key) { + $elements = $this->getElementsInitializedAndFlattened(); + return (isset($elements[$key])) ? $elements[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public function setElementProperties($key, array $properties, $parent_key = '') { + $elements = $this->getElementsDecoded(); + // If element is was not added to elements, add it as the last element. + if (!$this->setElementPropertiesRecursive($elements, $key, $properties, $parent_key)) { + $elements[$key] = $properties; + } + $this->setElements($elements); + return $this; + } + + /** + * Set element properties. + * + * @param array $elements + * An associative nested array of elements. + * @param string $key + * The element's key. + * @param array $properties + * An associative array of properties. + * @param string $parent_key + * (optional) The element's parent key. Only used for new elements. + * + * @return bool + * TRUE when the element's properties has been set. FALSE when the element + * has not been found. + */ + protected function setElementPropertiesRecursive(array &$elements, $key, array $properties, $parent_key = '') { + foreach ($elements as $element_key => &$element) { + if (Element::property($element_key) || !is_array($element)) { + continue; + } + + if ($element_key == $key) { + $element = $properties + WebformElementHelper::removeProperties($element); + return TRUE; + } + + if ($element_key == $parent_key) { + $element[$key] = $properties; + return TRUE; + } + + if ($this->setElementPropertiesRecursive($element, $key, $properties, $parent_key)) { + return TRUE; + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function deleteElement($key) { + // Delete element from the elements render array. + $elements = $this->getElementsDecoded(); + $sub_element_keys = $this->deleteElementRecursive($elements, $key); + $this->setElements($elements); + + // Delete submission element key data. + \Drupal::database()->delete('webform_submission_data') + ->condition('webform_id', $this->id()) + ->condition('name', $sub_element_keys, 'IN') + ->execute(); + } + + /** + * Remove an element by key from a render array. + * + * @param array $elements + * An associative nested array of elements. + * @param string $key + * The element's key. + * + * @return bool|array + * An array containing the deleted element and sub element keys. + * FALSE is no sub elements are found. + */ + protected function deleteElementRecursive(array &$elements, $key) { + foreach ($elements as $element_key => &$element) { + if (Element::property($element_key) || !is_array($element)) { + continue; + } + + if ($element_key == $key) { + $sub_element_keys = [$element_key => $element_key]; + $this->collectSubElementKeysRecursive($sub_element_keys, $element); + unset($elements[$element_key]); + return $sub_element_keys; + } + + if ($sub_element_keys = $this->deleteElementRecursive($element, $key)) { + return $sub_element_keys; + } + } + + return FALSE; + } + + /** + * Collect sub element keys from a render array. + * + * @param array $sub_element_keys + * An array to be populated with sub element keys. + * @param array $elements + * A render array. + */ + protected function collectSubElementKeysRecursive(array &$sub_element_keys, array $elements) { + foreach ($elements as $key => &$element) { + if (Element::property($key) || !is_array($element)) { + continue; + } + $sub_element_keys[$key] = $key; + $this->collectSubElementKeysRecursive($sub_element_keys, $element); + } + } + + /** + * {@inheritdoc} + */ + public function getPages($disable_pages = FALSE) { + if (isset($this->pages)) { + return $this->pages; + } + + $wizard_properties = [ + '#title' => '#title', + '#prev_button_label' => '#prev_button_label', + '#next_button_label' => '#next_button_label', + ]; + + $elements = $this->getElementsInitialized(); + + // Add webform page containers. + $this->pages = []; + if (is_array($elements) && !$disable_pages) { + foreach ($elements as $key => $element) { + if (isset($element['#type']) && $element['#type'] == 'webform_wizard_page') { + $this->pages[$key] = array_intersect_key($element, $wizard_properties); + } + } + } + + // Add preview page. + $settings = $this->getSettings(); + if ($settings['preview'] != DRUPAL_DISABLED) { + // If there is no start page, we must define one. + if (empty($this->pages)) { + $this->pages['start'] = [ + '#title' => $this->getSetting('wizard_start_label') ?: \Drupal::config('webform.settings')->get('settings.default_wizard_start_label'), + ]; + } + $this->pages['preview'] = [ + '#title' => $this->t('Preview'), + ]; + } + + // Only add complete page, if there are some pages. + if ($this->pages && $this->getSetting('wizard_complete')) { + $this->pages['complete'] = [ + '#title' => $this->getSetting('wizard_complete_label') ?: \Drupal::config('webform.settings')->get('settings.default_wizard_complete_label'), + ]; + } + + return $this->pages; + } + + /** + * {@inheritdoc} + */ + public function getPage($key) { + $pages = $this->getPages(); + return (isset($pages[$key])) ? $pages[$key] : NULL; + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageInterface $storage, array &$values) { + $values += [ + 'status' => WebformInterface::STATUS_OPEN, + 'uid' => \Drupal::currentUser()->id(), + 'settings' => self::getDefaultSettings(), + 'access' => self::getDefaultAccessRules(), + ]; + + // Convert boolean status to STATUS constant. + if ($values['status'] === TRUE) { + $values['status'] = WebformInterface::STATUS_OPEN; + } + elseif ($values['status'] === FALSE) { + $values['status'] = WebformInterface::STATUS_CLOSED; + } + elseif ($values['status'] === NULL) { + $values['status'] = WebformInterface::STATUS_SCHEDULED; + } + } + + /** + * {@inheritdoc} + */ + public static function postLoad(EntityStorageInterface $storage, array &$entities) { + foreach ($entities as $entity) { + $entity->elementsOriginal = $entity->elements; + } + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageInterface $storage, array $entities) { + /** @var \Drupal\webform\WebformInterface[] $entities */ + parent::preDelete($storage, $entities); + + // Delete all paths and states associated with this webform. + foreach ($entities as $entity) { + // Delete all paths. + $entity->deletePaths(); + + // Delete the state. + \Drupal::state()->delete('webform.webform.' . $entity->id()); + } + + // Delete all submission associated with this webform. + $submission_ids = \Drupal::entityQuery('webform_submission') + ->condition('webform_id', array_keys($entities), 'IN') + ->sort('sid') + ->execute(); + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + $submissions = $submission_storage->loadMultiple($submission_ids); + $submission_storage->delete($submissions); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + $cache_tags = parent::getCacheTags(); + // Add webform to cache tags which are used by the WebformSubmissionForm. + $cache_tags[] = 'webform:' . $this->id(); + return $cache_tags; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + // Always unpublish templates. + if ($this->isTemplate()) { + $this->setStatus(WebformInterface::STATUS_CLOSED); + } + + // Serialize elements array to YAML. + if (is_array($this->elements)) { + $this->elements = Yaml::encode($this->elements); + } + + parent::preSave($storage); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + // Update paths. + $this->updatePaths(); + + // Reset elements. + $this->resetElements(); + } + + /** + * {@inheritdoc} + */ + public function updatePaths() { + // Path module must be enable for URL aliases to be updated. + if (!\Drupal::moduleHandler()->moduleExists('path')) { + return; + } + + // If 'Allow users to post submission from a dedicated URL' is disabled, + // delete all existing paths. + if (empty($this->settings['page'])) { + $this->deletePaths(); + return; + } + + // Update submit path. + $submit_path = $this->settings['page_submit_path'] ?: trim(\Drupal::config('webform.settings')->get('settings.default_page_base_path'), '/') . '/' . str_replace('_', '-', $this->id()); + $submit_source = '/webform/' . $this->id(); + $submit_alias = '/' . trim($submit_path, '/'); + $this->updatePath($submit_source, $submit_alias, $this->langcode); + $this->updatePath($submit_source, $submit_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); + + // Update confirm path. + $confirm_path = $this->settings['page_confirm_path'] ?: $submit_path . '/confirmation'; + $confirm_source = '/webform/' . $this->id() . '/confirmation'; + $confirm_alias = '/' . trim($confirm_path, '/'); + $this->updatePath($confirm_source, $confirm_alias, $this->langcode); + $this->updatePath($confirm_source, $confirm_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); + + // Update submissions path. + $submissions_path = $submit_path . '/submissions'; + $submissions_source = '/webform/' . $this->id() . '/submissions'; + $submissions_alias = '/' . trim($submissions_path, '/'); + $this->updatePath($submissions_source, $submissions_alias, $this->langcode); + $this->updatePath($submissions_source, $submissions_alias, LanguageInterface::LANGCODE_NOT_SPECIFIED); + } + + /** + * {@inheritdoc} + */ + public function deletePaths() { + /** @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']); + } + + /** + * Saves a path alias to the database. + * + * @param string $source + * The internal system path. + * @param string $alias + * The URL alias. + * @param string $langcode + * (optional) The language code of the alias. + */ + protected function updatePath($source, $alias, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED) { + /** @var \Drupal\Core\Path\AliasStorageInterface $path_alias_storage */ + $path_alias_storage = \Drupal::service('path.alias_storage'); + + $path = $path_alias_storage->load(['source' => $source, 'langcode' => $langcode]); + + // Check if the path alias is already setup. + if ($path && ($path['alias'] == $alias)) { + return; + } + + $path_alias_storage->save($source, $alias, $langcode, $path['pid']); + } + + /** + * {@inheritdoc} + */ + public function deleteWebformHandler(WebformHandlerInterface $handler) { + $this->getHandlers()->removeInstanceId($handler->getHandlerId()); + $this->save(); + return $this; + } + + /** + * Returns the webform handler plugin manager. + * + * @return \Drupal\Component\Plugin\PluginManagerInterface + * The webform handler plugin manager. + */ + protected function getWebformHandlerPluginManager() { + return \Drupal::service('plugin.manager.webform.handler'); + } + + /** + * {@inheritdoc} + */ + public function getHandler($handler) { + return $this->getHandlers()->get($handler); + } + + /** + * {@inheritdoc} + */ + public function getHandlers($plugin_id = NULL, $status = NULL, $results = NULL) { + if (!$this->handlersCollection) { + $this->handlersCollection = new WebformHandlerPluginCollection($this->getWebformHandlerPluginManager(), $this->handlers); + /** @var \Drupal\webform\WebformHandlerBase $handler */ + foreach ($this->handlersCollection as $handler) { + // Initialize the handler and pass in the webform. + $handler->init($this); + } + $this->handlersCollection->sort(); + } + + /** @var \Drupal\webform\WebformHandlerPluginCollection $handlers */ + $handlers = $this->handlersCollection; + + // Clone the handlers if they are being filtered. + if (isset($plugin_id) || isset($status) || isset($results)) { + /** @var \Drupal\webform\WebformHandlerPluginCollection $handlers */ + $handlers = clone $this->handlersCollection; + } + + // Filter the handlers by plugin id. + // This is used to limit track and enforce a handlers cardinality. + if (isset($plugin_id)) { + foreach ($handlers as $instance_id => $handler) { + if ($handler->getPluginId() != $plugin_id) { + $handlers->removeInstanceId($instance_id); + } + } + } + + // Filter the handlers by status. + // This is used to limit track and enforce a handlers cardinality. + if (isset($status)) { + foreach ($handlers as $instance_id => $handler) { + if ($handler->getStatus() != $status) { + $handlers->removeInstanceId($instance_id); + } + } + } + + // Filter the handlers by results. + // This is used to track is results are processed or ignored. + if (isset($results)) { + foreach ($handlers as $instance_id => $handler) { + $plugin_definition = $handler->getPluginDefinition(); + if ($plugin_definition['results'] != $results) { + $handlers->removeInstanceId($instance_id); + } + } + } + + return $handlers; + } + + /** + * {@inheritdoc} + */ + public function getPluginCollections() { + return ['handlers' => $this->getHandlers()]; + } + + /** + * {@inheritdoc} + */ + public function addWebformHandler(array $configuration) { + $this->getHandlers()->addInstanceId($configuration['handler_id'], $configuration); + return $configuration['handler_id']; + } + + /** + * {@inheritdoc} + */ + public function invokeHandlers($method, &$data, &$context1 = NULL, &$context2 = NULL) { + $handlers = $this->getHandlers(); + foreach ($handlers as $handler) { + if ($handler->isEnabled()) { + $handler->$method($data, $context1, $context2); + } + } + } + + /** + * {@inheritdoc} + */ + public function invokeElements($method, &$data, &$context1 = NULL, &$context2 = NULL) { + /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ + $element_manager = \Drupal::service('plugin.manager.webform.element'); + + $elements = $this->getElementsInitializedAndFlattened(); + foreach ($elements as $element) { + $element_manager->invokeMethod($method, $element, $data, $context1, $context2); + } + } + + /** + * {@inheritdoc} + * + * Overriding so that URLs pointing to webform default to 'canonical' + * submission webform and not the back-end 'edit-form'. + */ + public function url($rel = 'canonical', $options = []) { + // Do not remove this override: the default value of $rel is different. + return parent::url($rel, $options); + } + + /** + * {@inheritdoc} + * + * Overriding so that URLs pointing to webform default to 'canonical' + * submission webform and not the back-end 'edit-form'. + */ + public function toUrl($rel = 'canonical', array $options = []) { + return parent::toUrl($rel, $options); + } + + /** + * {@inheritdoc} + * + * Overriding so that URLs pointing to webform default to 'canonical' + * submission webform and not the back-end 'edit-form'. + */ + public function urlInfo($rel = 'canonical', array $options = []) { + return parent::urlInfo($rel, $options); + } + + /** + * {@inheritdoc} + * + * Overriding so that links to webform default to 'canonical' submission + * webform and not the back-end 'edit-form'. + */ + public function toLink($text = NULL, $rel = 'canonical', array $options = []) { + return parent::toLink($text, $rel, $options); + } + + /** + * {@inheritdoc} + * + * Overriding so that links to webform default to 'canonical' submission + * webform and not the back-end 'edit-form'. + */ + public function link($text = NULL, $rel = 'canonical', array $options = []) { + return parent::link($text, $rel, $options); + } + + /** + * {@inheritdoc} + */ + public function isDefaultRevision() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getState($key, $default = NULL) { + $namespace = 'webform.webform.' . $this->id(); + $values = \Drupal::state()->get($namespace, []); + return (isset($values[$key])) ? $values[$key] : $default; + } + + /** + * {@inheritdoc} + */ + public function setState($key, $value) { + $namespace = 'webform.webform.' . $this->id(); + $values = \Drupal::state()->get($namespace, []); + $values[$key] = $value; + \Drupal::state()->set($namespace, $values); + } + + /** + * {@inheritdoc} + */ + public function deleteState($key) { + $namespace = 'webform.webform.' . $this->id(); + $values = \Drupal::state()->get($namespace, []); + unset($values[$key]); + \Drupal::state()->set($namespace, $values); + } + + /** + * {@inheritdoc} + */ + public function hasState($key) { + $namespace = 'webform.webform.' . $this->id(); + $values = \Drupal::state()->get($namespace, []); + return (isset($values[$key])) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + + $handlers = $this->getHandlers(); + if (empty($handlers)) { + return $changed; + } + + foreach ($handlers as $handler) { + $plugin_definition = $handler->getPluginDefinition(); + $provider = $plugin_definition['provider']; + if (in_array($provider, $dependencies['module'])) { + $this->deleteWebformHandler($handler); + $changed = TRUE; + } + } + return $changed; + } + + /** + * Define empty array iterator. + * + * See: Issue #2759267: Undefined method Webform::getIterator(). + */ + public function getIterator() { + return new \ArrayIterator([]); + } + +} diff --git a/web/modules/contrib/webform/src/Entity/WebformOptions.php b/web/modules/contrib/webform/src/Entity/WebformOptions.php new file mode 100644 index 000000000..c7f0cc555 --- /dev/null +++ b/web/modules/contrib/webform/src/Entity/WebformOptions.php @@ -0,0 +1,185 @@ +optionsDecoded)) { + try { + $options = Yaml::decode($this->options); + // Since YAML supports simple values. + $options = (is_array($options)) ? $options : []; + } + catch (\Exception $exception) { + $link = $this->link(t('Edit'), 'edit-form'); + \Drupal::logger('webform')->notice('%title options are not valid. @message', ['%title' => $this->label(), '@message' => $exception->getMessage(), 'link' => $link]); + $options = FALSE; + } + $this->optionsDecoded = $options; + } + return $this->optionsDecoded; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) { + $this->options = Yaml::encode($options); + $this->optionsDecoded = NULL; + } + + /** + * {@inheritdoc} + */ + public function hasAlterHooks() { + $hook_name = 'webform_options_' . $this->id() . '_alter'; + $alter_hooks = \Drupal::moduleHandler()->getImplementations($hook_name); + return (count($alter_hooks)) ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + // If the submitted options match the altered options clear the submission + // options. + $altered_options = []; + $temp_element = []; + \Drupal::moduleHandler()->alter('webform_options_' . $this->id(), $altered_options, $temp_element); + $altered_options = WebformOptionsHelper::convertOptionsToString($altered_options); + if ($altered_options == $this->getOptions()) { + $this->options = ''; + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + // Clear cached properties. + $this->optionsDecoded = NULL; + } + + /** + * {@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 (is_array($element[$property_name])) { + return $element[$property_name]; + } + + // Return empty options if element does not define an options id. + if (empty($element[$property_name]) || !is_string($element[$property_name])) { + return []; + } + + // If options have been set return them. + // This allows dynamic options to be overridden. + $id = $element[$property_name]; + if ($webform_options = WebformOptions::load($id)) { + $options = $webform_options->getOptions(); + } + else { + $options = []; + } + + // Alter options using hook_webform_options_alter() + // and/or hook_webform_options_WEBFORM_OPTIONS_ID_alter() hook. + // @see webform.api.php + \Drupal::moduleHandler()->alter('webform_options_' . $id, $options, $element); + \Drupal::moduleHandler()->alter('webform_options', $options, $element, $id); + + // Log empty options. + if (empty($options)) { + \Drupal::logger('webform')->notice('Options %id do not exist.', ['%id' => $id]); + } + + return $options; + } + +} diff --git a/web/modules/contrib/webform/src/Entity/WebformSubmission.php b/web/modules/contrib/webform/src/Entity/WebformSubmission.php new file mode 100644 index 000000000..90affa78f --- /dev/null +++ b/web/modules/contrib/webform/src/Entity/WebformSubmission.php @@ -0,0 +1,699 @@ +setLabel(t('Serial number')) + ->setDescription(t('The serial number of the webform submission entity.')) + ->setReadOnly(TRUE); + + $fields['sid'] = BaseFieldDefinition::create('integer') + ->setLabel(t('Submission ID')) + ->setDescription(t('The ID of the webform submission entity.')) + ->setReadOnly(TRUE); + + $fields['uuid'] = BaseFieldDefinition::create('uuid') + ->setLabel(t('Submission UUID')) + ->setDescription(t('The UUID of the webform submission entity.')) + ->setReadOnly(TRUE); + + $fields['token'] = BaseFieldDefinition::create('string') + ->setLabel(t('Token')) + ->setDescription(t('A secure token used to look up a submission.')) + ->setSetting('max_length', 255) + ->setReadOnly(TRUE); + + $fields['uri'] = BaseFieldDefinition::create('string') + ->setLabel(t('Submission URI')) + ->setDescription(t('The URI the user submitted the webform.')) + ->setSetting('max_length', 2000) + ->setReadOnly(TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the webform submission was first saved as draft or submitted.')); + + $fields['completed'] = BaseFieldDefinition::create('timestamp') + ->setLabel(t('Completed')) + ->setDescription(t('The time that the webform submission was submitted as complete (not draft).')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the webform submission was last saved (complete or draft).')); + + $fields['in_draft'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Is draft')) + ->setDescription(t('Is this a draft of the submission?')) + ->setDefaultValue(FALSE); + + $fields['current_page'] = BaseFieldDefinition::create('string') + ->setLabel(t('Current page')) + ->setDescription(t('The current wizard page.')) + ->setSetting('max_length', 128); + + $fields['remote_addr'] = BaseFieldDefinition::create('string') + ->setLabel(t('Remote IP address')) + ->setDescription(t('The IP address of the user that submitted the webform.')) + ->setSetting('max_length', 128); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Submitted by')) + ->setDescription(t('The submitter.')) + ->setSetting('target_type', 'user'); + + $fields['langcode'] = BaseFieldDefinition::create('language') + ->setLabel(t('Language')) + ->setDescription(t('The submission language code.')); + + $fields['webform_id'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Webform')) + ->setDescription(t('The associated webform.')) + ->setSetting('target_type', 'webform'); + + $fields['entity_type'] = BaseFieldDefinition::create('string') + ->setLabel(t('Submitted to: Entity type')) + ->setDescription(t('The entity type to which this submission was submitted from.')) + ->setSetting('is_ascii', TRUE) + ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH); + + // 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...). + // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::propertyDefinitions() + $fields['entity_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Submitted to: Entity ID')) + ->setDescription(t('The ID of the entity of which this webform submission was submitted from.')) + ->setSetting('max_length', 255); + + $fields['sticky'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Sticky')) + ->setDescription(t('A flag that indicate the status of the webform submission.')) + ->setDefaultValue(FALSE); + + $fields['notes'] = BaseFieldDefinition::create('string_long') + ->setLabel(t('Notes')) + ->setDescription(t('Administrative notes about the webform submission.')) + ->setDefaultValue(''); + + return $fields; + } + + /** + * {@inheritdoc} + */ + public function serial() { + return $this->serial->value; + } + + /** + * {@inheritdoc} + */ + public function label() { + $t_args = ['@id' => $this->serial()]; + if ($source_entity = $this->getSourceEntity()) { + $t_args['@form'] = $source_entity->label(); + } + else { + $t_args['@form'] = $this->getWebform()->label(); + } + return $this->t('@form: Submission #@id', $t_args); + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + if (isset($this->get('created')->value)) { + return $this->get('created')->value; + } + return NULL; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($created) { + $this->set('created', $created); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getChangedTime() { + return $this->get('changed')->value; + } + + /** + * {@inheritdoc} + */ + public function setChangedTime($timestamp) { + $this->set('changed', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCompletedTime() { + return $this->get('completed')->value; + } + + /** + * {@inheritdoc} + */ + public function setCompletedTime($timestamp) { + $this->set('completed', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNotes() { + return $this->get('notes')->value; + } + + /** + * {@inheritdoc} + */ + public function setNotes($notes) { + $this->set('notes', $notes); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSticky() { + return $this->get('sticky')->value; + } + + /** + * {@inheritdoc} + */ + public function setSticky($sticky) { + $this->set('sticky', $sticky); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRemoteAddr() { + return $this->get('remote_addr')->value ?: $this->t('(unknown)'); + } + + /** + * {@inheritdoc} + */ + public function setRemoteAddr($ip_address) { + $this->set('remote_addr', $ip_address); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrentPage() { + return $this->get('current_page')->value; + } + + /** + * {@inheritdoc} + */ + public function setCurrentPage($current_page) { + $this->set('current_page', $current_page); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCurrentPageTitle() { + $current_page = $this->getCurrentPage(); + $page = $this->getWebform()->getPage($current_page); + return ($page && isset($page['#title'])) ? $page['#title'] : $current_page; + } + + /** + * {@inheritdoc} + */ + public function getData($key = NULL) { + if (isset($key)) { + return (isset($this->data[$key])) ? $this->data[$key] : NULL; + } + else { + return $this->data; + } + } + + /** + * {@inheritdoc} + */ + public function setData(array $data) { + $this->data = $data; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOriginalData($key = NULL) { + if ($key !== NULL) { + return (isset($this->originalData[$key])) ? $this->originalData[$key] : NULL; + } + else { + return $this->originalData; + } + } + + /** + * {@inheritdoc} + */ + public function setOriginalData(array $data) { + $this->originalData = $data; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getToken() { + return $this->token->value; + } + + /** + * {@inheritdoc} + */ + public function getWebform() { + if (isset($this->webform_id->entity)) { + return $this->webform_id->entity; + } + else { + return self::$webform; + } + } + + /** + * {@inheritdoc} + */ + public function getSourceEntity() { + 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); + } + else { + return NULL; + } + } + + /** + * {@inheritdoc} + */ + public function getSourceUrl() { + $uri = $this->uri->value; + if ($uri !== NULL && ($url = \Drupal::pathValidator()->getUrlIfValid($uri))) { + return $url->setOption('absolute', TRUE); + } + elseif (($entity = $this->getSourceEntity()) && $entity->hasLinkTemplate('canonical')) { + return $entity->toUrl()->setOption('absolute', TRUE); + } + else { + return $this->getWebform()->toUrl()->setOption('absolute', TRUE); + } + } + + /** + * {@inheritdoc} + */ + public function getTokenUrl() { + return $this->getSourceUrl() + ->setOption('query', ['token' => $this->token->value]); + } + + /** + * {@inheritdoc} + */ + public function invokeWebformHandlers($method, &$context1 = NULL, &$context2 = NULL) { + if ($webform = $this->getWebform()) { + $webform->invokeHandlers($method, $this, $context1, $context2); + } + } + + /** + * {@inheritdoc} + */ + public function invokeWebformElements($method, &$context1 = NULL, &$context2 = NULL) { + if ($webform = $this->getWebform()) { + $webform->invokeElements($method, $this, $context1, $context2); + } + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + $user = $this->get('uid')->entity; + if (!$user || $user->isAnonymous()) { + $user = User::getAnonymousUser(); + } + return $user; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('uid')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('uid', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('uid', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isDraft() { + return $this->get('in_draft')->value ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function isCompleted() { + return $this->get('completed')->value ? TRUE : FALSE; + } + + /** + * {@inheritdoc} + */ + public function isSticky() { + return (bool) $this->get('sticky')->value; + } + + /** + * {@inheritdoc} + */ + public function hasNotes() { + return $this->notes ? TRUE : FALSE; + } + + /** + * Track the state of a submission. + * + * @return int + * Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, or STATE_UPDATED, + * depending on the last save operation performed. + */ + public function getState() { + if (!$this->id()) { + return self::STATE_UNSAVED; + } + elseif ($this->isDraft()) { + return self::STATE_DRAFT; + } + elseif ($this->completed->value == $this->changed->value) { + return self::STATE_COMPLETED; + } + else { + return self::STATE_UPDATED; + } + } + + /** + * {@inheritdoc} + */ + protected function urlRouteParameters($rel) { + $uri_route_parameters = parent::urlRouteParameters($rel); + $uri_route_parameters['webform'] = $this->getWebform()->id(); + return $uri_route_parameters; + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageInterface $storage, array &$values) { + if (empty($values['webform_id']) && empty($values['webform'])) { + if (empty($values['webform_id'])) { + throw new \Exception('Webform id (webform_id) is required to create a webform submission.'); + } + elseif (empty($values['webform'])) { + throw new \Exception('Webform (webform) is required to create a webform submission.'); + } + } + + // Get temporary webform entity and store it in the static + // WebformSubmission::$webform property. + // This could be reworked to use \Drupal\user\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() + // @see \Drupal\webform_ui\Form\WebformUiElementTestForm::buildForm() + if (isset($values['webform']) && ($values['webform'] instanceof WebformInterface)) { + $webform = $values['webform']; + self::$webform = $values['webform']; + $values['webform_id'] = 'temp'; + } + else { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = Webform::load($values['webform_id']); + self::$webform = NULL; + } + + // Get request's source entity parameter. + /** @var \Drupal\webform\WebformRequestInterface $request_handler */ + $request_handler = \Drupal::service('webform.request'); + $source_entity = $request_handler->getCurrentSourceEntity('webform'); + $values += [ + 'entity_type' => ($source_entity) ? $source_entity->getEntityTypeId() : NULL, + 'entity_id' => ($source_entity) ? $source_entity->id() : NULL, + ]; + + // Decode all data in an array. + if (empty($values['data'])) { + $values['data'] = []; + } + elseif (is_string($values['data'])) { + $values['data'] = Yaml::decode($values['data']); + } + + // Get default date from source entity 'webform' field. + if ($values['entity_type'] && $values['entity_id']) { + $source_entity = \Drupal::entityTypeManager() + ->getStorage($values['entity_type']) + ->load($values['entity_id']); + if ($webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($source_entity)) { + if ($source_entity->$webform_field_name->target_id == $webform->id() && $source_entity->$webform_field_name->default_data) { + $values['data'] += Yaml::decode($source_entity->$webform_field_name->default_data); + } + } + } + + // Set default uri and remote_addr. + $current_request = \Drupal::requestStack()->getCurrentRequest(); + $values += [ + 'uri' => preg_replace('#^' . base_path() . '#', '/', $current_request->getRequestUri()), + 'remote_addr' => ($webform && $webform->isConfidential()) ? '' : $current_request->getClientIp(), + ]; + + // Get default uid and langcode. + $values += [ + 'uid' => \Drupal::currentUser()->id(), + 'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(), + ]; + + // Hard code the token. + $values['token'] = Crypt::randomBytesBase64(); + + // Set is draft. + $values['in_draft'] = FALSE; + + $webform->invokeHandlers(__FUNCTION__, $values); + $webform->invokeElements(__FUNCTION__, $values); + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + // Set created. + if (!$this->created->value) { + $this->created->value = REQUEST_TIME; + } + + // Set changed. + $this->changed->value = REQUEST_TIME; + + // Set completed. + if ($this->isDraft()) { + $this->completed->value = NULL; + } + elseif (!$this->isCompleted()) { + $this->completed->value = REQUEST_TIME; + } + + parent::preSave($storage); + } + + /** + * {@inheritdoc} + */ + public function save() { + // Clear the remote_addr for confidential submissions. + if ($this->getWebform()->isConfidential()) { + $this->get('remote_addr')->value = ''; + } + + return parent::save(); + } + + /** + * {@inheritdoc} + */ + public function toArray($custom = FALSE, $check_access = FALSE) { + if ($custom === FALSE) { + return parent::toArray(); + } + else { + $values = parent::toArray(); + foreach ($values as $key => $item) { + // Issue #2567899 It seems it is impossible to save an empty string to an entity. + // @see https://www.drupal.org/node/2567899 + // Solution: Set empty (aka NULL) items to an empty string. + if (empty($item)) { + $values[$key] = ''; + } + else { + $value = reset($item); + $values[$key] = reset($value); + } + } + + $values['data'] = $this->getData(); + + // Check access. + if ($check_access) { + // Check field definition access. + $submission_storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + $field_definitions = $submission_storage->getFieldDefinitions(); + $field_definitions = $submission_storage->checkFieldDefinitionAccess($this->getWebform(), $field_definitions + ['data' => TRUE]); + $values = array_intersect_key($values, $field_definitions); + + // Check element data access. + $elements = $this->getWebform()->getElementsInitializedFlattenedAndHasValue('view'); + $values['data'] = array_intersect_key($values['data'], $elements); + } + + return $values; + } + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformAdminSettingsForm.php b/web/modules/contrib/webform/src/Form/WebformAdminSettingsForm.php new file mode 100644 index 000000000..b8fb56c3d --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformAdminSettingsForm.php @@ -0,0 +1,833 @@ +moduleHandler = $third_party_settings_manager; + $this->elementManager = $element_manager; + $this->submissionExporter = $submission_exporter; + $this->tokenManager = $token_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('module_handler'), + $container->get('plugin.manager.webform.element'), + $container->get('webform_submission.exporter'), + $container->get('webform.token_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->config('webform.settings'); + $settings = $config->get('settings'); + $element_plugins = $this->elementManager->getInstances(); + + // Page. + $form['page'] = [ + '#type' => 'details', + '#title' => $this->t('Page default settings'), + '#tree' => TRUE, + ]; + $form['page']['default_page_base_path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default base path for webform URLs'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_page_base_path'), + ]; + + // Form. + $form['form'] = [ + '#type' => 'details', + '#title' => $this->t('Form default settings'), + '#tree' => TRUE, + ]; + $form['form']['default_form_closed_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default closed message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_form_closed_message'), + ]; + $form['form']['default_form_exception_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default closed exception message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_form_exception_message'), + ]; + $form['form']['default_form_confidential_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default confidential message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_form_confidential_message'), + ]; + $form['form']['default_form_submit_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default submit button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_form_submit_label'], + ]; + $form['form']['default_form_submit_once'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Prevent duplicate submissions'), + '#description' => $this->t('If checked, the submit button will be disabled immediately after is is clicked.'), + '#default_value' => $settings['default_form_submit_once'], + ]; + $form['form']['default_form_disable_back'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable back button for all webforms'), + '#description' => $this->t('If checked, users will not be allowed to navigate back to the webform using the browsers back button.'), + '#return_value' => TRUE, + '#default_value' => $config->get('settings.default_form_disable_back'), + ]; + $form['form']['default_form_unsaved'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Warn users about unsaved changes'), + '#description' => $this->t('If checked, users will be displayed a warning message when they navigate away from a webform with unsaved changes.'), + '#return_value' => TRUE, + '#default_value' => $config->get('settings.default_form_unsaved'), + ]; + $form['form']['default_form_novalidate'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable client-side validation for all webforms'), + '#description' => $this->t('If checked, the novalidate attribute, which disables client-side validation, will be added to all webforms.', [':href' => 'http://www.w3schools.com/tags/att_form_novalidate.asp']), + '#return_value' => TRUE, + '#default_value' => $config->get('settings.default_form_novalidate'), + ]; + $form['form']['default_form_details_toggle'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Display collapse/expand all details link'), + '#description' => $this->t('If checked, an expand/collapse all (details) link will be added to all webforms with two or more details elements.'), + '#return_value' => TRUE, + '#default_value' => $config->get('settings.default_form_details_toggle'), + ]; + $form['form']['form_classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Webform CSS classes'), + '#description' => $this->t('A list of classes that will be provided in the "Webform 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.form_classes'), + ]; + $form['form']['button_classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Button CSS classes'), + '#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' => $config->get('settings.button_classes'), + ]; + + // Wizard. + $form['wizard'] = [ + '#type' => 'details', + '#title' => $this->t('Wizard default settings'), + '#tree' => TRUE, + ]; + $form['wizard']['default_wizard_prev_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default wizard previous page button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_wizard_prev_button_label'], + ]; + $form['wizard']['default_wizard_next_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default wizard next page button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_wizard_next_button_label'], + ]; + $form['wizard']['default_wizard_start_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default wizard start label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_wizard_start_label'], + ]; + $form['wizard']['default_wizard_complete_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default wizard end label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_wizard_complete_label'], + ]; + + // Preview. + $form['preview'] = [ + '#type' => 'details', + '#title' => $this->t('Preview default settings'), + '#tree' => TRUE, + ]; + $form['preview']['default_preview_next_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default preview button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_preview_next_button_label'], + ]; + $form['preview']['default_preview_prev_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default preview previous page button label'), + '#required' => TRUE, + '#size' => 20, + '#default_value' => $settings['default_preview_prev_button_label'], + ]; + $form['preview']['default_preview_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default preview message'), + '#required' => TRUE, + '#default_value' => $settings['default_preview_message'], + ]; + + // Draft. + $form['draft'] = [ + '#type' => 'details', + '#title' => $this->t('Draft default settings'), + '#tree' => TRUE, + ]; + $form['draft']['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']['default_draft_saved_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default draft save message'), + '#required' => TRUE, + '#default_value' => $settings['default_draft_saved_message'], + ]; + $form['draft']['default_draft_loaded_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default draft load message'), + '#required' => TRUE, + '#default_value' => $settings['default_draft_loaded_message'], + ]; + + $form['confirmation'] = [ + '#type' => 'details', + '#title' => $this->t('Confirmation default settings'), + '#tree' => TRUE, + ]; + $form['confirmation']['default_confirmation_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default confirmation message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_confirmation_message'), + ]; + $form['confirmation']['default_confirmation_back_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default confirmation back label'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_confirmation_back_label'), + ]; + $form['confirmation']['confirmation_classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Confirmation CSS classes'), + '#description' => $this->t('A list of classes that will be provided in the "Confirmation 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.confirmation_classes'), + ]; + $form['confirmation']['confirmation_back_classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Confirmation back link CSS classes'), + '#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' => $config->get('settings.confirmation_back_classes'), + ]; + + // Limit. + $form['limit'] = [ + '#type' => 'details', + '#title' => $this->t('Limit default settings'), + '#tree' => TRUE, + ]; + $form['limit']['default_limit_total_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default total submissions limit message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_limit_total_message'), + ]; + $form['limit']['default_limit_user_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Default per user submission limit message'), + '#required' => TRUE, + '#default_value' => $config->get('settings.default_limit_user_message'), + ]; + + // Assets. + $form['assets'] = [ + '#type' => 'details', + '#title' => $this->t('Global Assets (CSS/JavaScript)'), + '#tree' => TRUE, + ]; + $form['assets']['css'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'css', + '#title' => $this->t('CSS'), + '#description' => $this->t('Enter custom CSS to be attached to all webforms.'), + '#default_value' => $config->get('assets.css'), + ]; + $form['assets']['javascript'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'javascript', + '#title' => $this->t('JavaScript'), + '#description' => $this->t('Enter custom JavaScript to be attached to all webforms.'), + '#default_value' => $config->get('assets.javascript'), + ]; + + // Elements. + $form['elements'] = [ + '#type' => 'details', + '#title' => $this->t('Element default settings'), + '#tree' => TRUE, + ]; + $form['elements']['allowed_tags'] = [ + '#type' => 'webform_radios_other', + '#title' => $this->t('Allowed tags'), + '#options' => [ + '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'), + '#other__placeholder' => $this->t('Enter multiple tags delimited using spaces'), + '#other__default_value' => implode(' ', Xss::getAdminTagList()), + '#other__maxlength' => 1000, + '#required' => TRUE, + '#description' => $this->t('Allowed tags are applied to any element property that may contain HTML markup. This properties include #title, #description, #field_prefix, and #field_suffix'), + '#default_value' => $config->get('elements.allowed_tags'), + ]; + $form['elements']['wrapper_classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Wrapper CSS classes'), + '#description' => $this->t('A list of classes that will be provided in the "Wrapper CSS classes" dropdown. Enter one or more classes on each line. These styles should be available in your theme\'s CSS file.'), + '#required' => TRUE, + '#default_value' => $config->get('elements.wrapper_classes'), + ]; + $form['elements']['classes'] = [ + '#type' => 'webform_codemirror', + '#title' => $this->t('Element CSS classes'), + '#description' => $this->t('A list of classes that will be provided in the "Element CSS classes" dropdown. Enter one or more classes on each line. These styles should be available in your theme\'s CSS file.'), + '#required' => TRUE, + '#default_value' => $config->get('elements.classes'), + ]; + $form['elements']['default_description_display'] = [ + '#type' => 'select', + '#title' => $this->t('Default description display'), + '#options' => [ + '' => '', + 'before' => $this->t('Before'), + 'after' => $this->t('After'), + 'invisible' => $this->t('Invisible'), + 'tooltip' => $this->t('Tooltip'), + ], + '#description' => $this->t('Determines the default placement of the description for all webform elements.'), + '#default_value' => $config->get('elements.default_description_display'), + ]; + $form['elements']['default_google_maps_api_key'] = [ + '#type' => 'textfield', + '#title' => $this->t('Google Maps API key'), + '#description' => $this->t('Google requires users to use a valid API key. Using the Google API Manager, you can enable the Google Maps JavaScript API. That will create (or reuse) a Browser key which you can paste here.'), + '#default_value' => $config->get('elements.default_google_maps_api_key'), + ]; + + // (Excluded) Types. + $types_header = [ + 'title' => ['data' => $this->t('Title')], + 'type' => ['data' => $this->t('Type')], + ]; + $this->elementTypes = []; + $types_options = []; + foreach ($element_plugins as $element_id => $element_plugin) { + $this->elementTypes[$element_id] = $element_id; + $types_options[$element_id] = [ + 'title' => $element_plugin->getPluginLabel(), + 'type' => $element_plugin->getTypeName(), + ]; + } + $form['types'] = [ + '#type' => 'details', + '#title' => $this->t('Element types'), + '#description' => $this->t('Select available element types'), + ]; + $form['types']['excluded_types'] = [ + '#type' => 'tableselect', + '#header' => $types_header, + '#options' => $types_options, + '#required' => TRUE, + '#default_value' => array_diff($this->elementTypes, $config->get('elements.excluded_types')), + ]; + + // File. + $form['file'] = [ + '#type' => 'details', + '#title' => $this->t('File upload default settings'), + '#tree' => TRUE, + ]; + $form['file']['file_public'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow files to be uploaded to public file system.'), + '#description' => $this->t('Public files upload destination is dangerous for webforms that are available to anonymous and/or untrusted users.') . ' ' . + $this->t('For more information see: DRUPAL-PSA-2016-003', ['@href' => 'https://www.drupal.org/psa-2016-003']), + '#return_value' => TRUE, + '#default_value' => $config->get('file.file_public'), + ]; + $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 %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'), + ]; + $file_types = [ + 'managed_file' => 'file', + 'audio_file' => 'audio file', + 'document_file' => 'document file', + 'image_file' => 'image file', + 'video_file' => 'video file', + ]; + foreach ($file_types as $file_type_name => $file_type_title) { + $form['file']["default_{$file_type_name}_extensions"] = [ + '#type' => 'textfield', + '#title' => $this->t('Default allowed @title extensions', ['@title' => $file_type_title]), + '#description' => $this->t('Separate extensions with a space and do not include the leading dot.'), + '#element_validate' => [[get_class($this), 'validateExtensions']], + '#required' => TRUE, + '#maxlength' => 256, + '#default_value' => $config->get("file.default_{$file_type_name}_extensions"), + ]; + } + + // Format. + $form['format'] = [ + '#type' => 'details', + '#title' => $this->t('Format default settings'), + '#tree' => TRUE, + ]; + foreach ($element_plugins as $element_id => $element_plugin) { + // Element. + $element_plugin_definition = $element_plugin->getPluginDefinition(); + $element_plugin_label = $element_plugin_definition['label']; + $form['format'][$element_id] = [ + '#type' => 'details', + '#title' => new FormattableMarkup('@label (@id)', ['@label' => $element_plugin_label, '@id' => $element_plugin->getTypeName()]), + ]; + // Element item format. + $item_formats = $element_plugin->getItemFormats(); + foreach ($item_formats as $format_name => $format_label) { + $item_formats[$format_name] = new FormattableMarkup('@label (@name)', ['@label' => $format_label, '@name' => $format_name]); + } + $item_formats = ['' => '<' . $this->t('Default') . '>'] + $item_formats; + $item_default_format = $element_plugin->getItemDefaultFormat(); + $item_default_format_label = (isset($item_formats[$item_default_format])) ? $item_formats[$item_default_format] : $item_default_format; + $form['format'][$element_id]['item'] = [ + '#type' => 'select', + '#title' => $this->t('Item format'), + '#description' => $this->t("Select how a @label element's single value is displayed.", ['@label' => $element_plugin_label]) . '
          ' . + $this->t('Defaults to: %value', ['%value' => $item_default_format_label]), + '#options' => $item_formats, + '#default_value' => $config->get("format.$element_id"), + ]; + // Element items format. + if ($element_plugin->supportsMultipleValues()) { + $items_formats = $element_plugin->getItemsFormats(); + foreach ($items_formats as $format_name => $format_label) { + $items_formats[$format_name] = new FormattableMarkup('@label (@name)', ['@label' => $format_label, '@name' => $format_name]); + } + $items_formats = ['' => '<' . $this->t('Default') . '>'] + $items_formats; + $items_default_format = $element_plugin->getItemsDefaultFormat(); + $items_default_format_label = (isset($item_formats[$items_default_format])) ? $items_formats[$items_default_format] : $items_default_format; + $form['format'][$element_id]['items'] = [ + '#type' => 'select', + '#title' => $this->t('Items format'), + '#description' => $this->t("Select how a @label element's multiple values are displayed.", ['@label' => $element_plugin_label]) . '
          ' . + $this->t('Defaults to: %value', ['%value' => $items_default_format_label]), + '#options' => $items_formats, + '#default_value' => $config->get("format.$element_id"), + ]; + } + } + + // Mail. + $form['mail'] = [ + '#type' => 'details', + '#title' => $this->t('Email default settings'), + '#tree' => TRUE, + ]; + $form['mail']['default_from_mail'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default email from address'), + '#required' => TRUE, + '#default_value' => $config->get('mail.default_from_mail'), + ]; + $form['mail']['default_from_name'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default email from name'), + '#required' => TRUE, + '#default_value' => $config->get('mail.default_from_name'), + ]; + $form['mail']['default_subject'] = [ + '#type' => 'textfield', + '#title' => $this->t('Default email subject'), + '#required' => TRUE, + '#default_value' => $config->get('mail.default_subject'), + ]; + $form['mail']['default_body_text'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'text', + '#title' => $this->t('Default email body (Plain text)'), + '#required' => TRUE, + '#default_value' => $config->get('mail.default_body_text'), + ]; + $form['mail']['default_body_html'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'html', + '#title' => $this->t('Default email body (HTML)'), + '#required' => TRUE, + '#default_value' => $config->get('mail.default_body_html'), + ]; + $form['mail']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + + // Export. + $form['export'] = [ + '#type' => 'details', + '#title' => $this->t('Export default settings'), + ]; + $export_options = NestedArray::mergeDeep($config->get('export') ?: [], + $this->submissionExporter->getValuesFromInput($form_state->getUserInput()) + ); + $export_form_state = new FormState(); + $this->submissionExporter->buildExportOptionsForm($form, $export_form_state, $export_options); + + // Batch. + $form['batch'] = [ + '#type' => 'details', + '#title' => $this->t('Batch settings'), + '#tree' => TRUE, + ]; + $form['batch']['default_batch_export_size'] = [ + '#type' => 'number', + '#title' => $this->t('Batch export size'), + '#min' => 1, + '#required' => TRUE, + '#default_value' => $config->get('batch.default_batch_export_size'), + ]; + $form['batch']['default_batch_update_size'] = [ + '#type' => 'number', + '#title' => $this->t('Batch update size'), + '#min' => 1, + '#required' => TRUE, + '#default_value' => $config->get('batch.default_batch_update_size'), + ]; + $form['batch']['default_batch_delete_size'] = [ + '#type' => 'number', + '#title' => $this->t('Batch delete size'), + '#min' => 1, + '#required' => TRUE, + '#default_value' => $config->get('batch.default_batch_delete_size'), + ]; + + $form['purge_settings'] = [ + '#type' => 'details', + '#title' => $this->t('Automated purging settings'), + '#tree' => TRUE, + ]; + $form['purge_settings']['cron_size'] = [ + '#type' => 'number', + '#title' => $this->t('Amount of submissions to process'), + '#min' => 1, + '#default_value' => $config->get('purge_settings.cron_size'), + '#description' => $this->t('Amount of submissions to purge during single cron run. You may want to lower this number if you are facing memory or timeout issues when purging via cron.'), + ]; + + // Test. + $form['test'] = [ + '#type' => 'details', + '#title' => $this->t('Test settings'), + '#tree' => TRUE, + ]; + $form['test']['types'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Test data by element type'), + '#description' => $this->t("Above test data is keyed by FAPI element #type."), + '#default_value' => $config->get('test.types'), + ]; + $form['test']['names'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Test data by element name'), + '#description' => $this->t("Above test data is keyed by full or partial element names. For example, Using 'zip' will populate fields that are named 'zip' and 'zip_code' but not 'zipcode' or 'zipline'."), + '#default_value' => $config->get('test.names'), + ]; + + // UI. + $form['ui'] = [ + '#type' => 'details', + '#title' => $this->t('User interface settings'), + '#tree' => TRUE, + ]; + $form['ui']['video_display'] = [ + '#type' => 'select', + '#title' => $this->t('Video display'), + '#description' => $this->t('Controls how videos are displayed in inline help and within the global help section.'), + '#options' => [ + 'dialog' => $this->t('Dialog'), + 'link' => $this->t('External link'), + 'hidden' => $this->t('Hidden'), + ], + '#default_value' => $config->get('ui.video_display'), + ]; + $form['ui']['details_save'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Save details open/close state'), + '#description' => $this->t('If checked, all Details element\'s open/close state will be saved using Local Storage.', [ + ':details_href' => 'http://www.w3schools.com/tags/tag_details.asp', + ':local_storage_href' => 'http://www.w3schools.com/html/html5_webstorage.asp', + ]), + '#return_value' => TRUE, + '#default_value' => $config->get('ui.details_save'), + ]; + $form['ui']['dialog_disabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable dialogs'), + '#description' => $this->t('If checked, all modal dialogs (ie popups) will be disabled.'), + '#return_value' => TRUE, + '#default_value' => $config->get('ui.dialog_disabled'), + ]; + $form['ui']['offcanvas_disabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable off-canvas system tray'), + '#description' => $this->t('If checked, off-canvas system tray will be disabled.'), + '#return_value' => TRUE, + '#default_value' => $config->get('ui.offcanvas_disabled'), + '#access' => $this->moduleHandler->moduleExists('outside_in') && (floatval(\Drupal::VERSION) >= 8.3), + '#states' => [ + 'visible' => [ + ':input[name="ui[dialog_disabled]"]' => [ + 'checked' => FALSE, + ], + ], + ], + ]; + if (!$this->moduleHandler->moduleExists('outside_in') && (floatval(\Drupal::VERSION) >= 8.3)) { + $form['ui']['offcanvas_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'info', + '#message_message' => $this->t('Enable the experimental System tray module to improve the Webform module\'s user experience.', [':href' => 'https://www.drupal.org/blog/drupal-82-now-with-more-outside-in']), + '#states' => [ + 'visible' => [ + ':input[name="ui[dialog_disabled]"]' => [ + 'checked' => FALSE, + ], + ], + ], + ]; + } + $form['ui']['html_editor_disabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable HTML editor'), + '#description' => $this->t('If checked, all HTML editor will be disabled.'), + '#return_value' => TRUE, + '#default_value' => $config->get('ui.html_editor_disabled'), + ]; + + // Library. + $form['library'] = [ + '#type' => 'details', + '#title' => $this->t('Library settings'), + '#tree' => TRUE, + ]; + $form['library']['cdn'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Use CDN'), + '#description' => $this->t('If checked, all warnings about missing libraries will be disabled.'), + '#return_value' => TRUE, + '#default_value' => $config->get('library.cdn'), + ]; + $form['library']['cdn_message'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Note that it is in general not a good idea to load libraries from a CDN; avoid this if possible. It introduces more points of failure both performance- and security-wise, requires more TCP/IP connections to be set up and these external assets are usually not in the browser cache anyway.'), + '#states' => [ + 'visible' => [ + ':input[name="library[cdn]"]' => [ + 'checked' => TRUE, + ], + ], + ], + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /* Settings */ + + $settings = $form_state->getValue('page') + + $form_state->getValue('form') + + $form_state->getValue('wizard') + + $form_state->getValue('preview') + + $form_state->getValue('draft') + + $form_state->getValue('confirmation') + + $form_state->getValue('limit'); + + // 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; + + /* Excluded types */ + + // Convert list of included types to excluded types. + $excluded_types = array_diff($this->elementTypes, array_filter($form_state->getValue('excluded_types'))); + ksort($excluded_types); + + /* Format */ + + $format = $form_state->getValue('format'); + foreach ($format as $element_id => $element_format) { + $format[$element_id] = array_filter($element_format); + } + $format = array_filter($format); + + /* Config save */ + + $config = $this->config('webform.settings'); + $config->set('settings', $settings); + $config->set('assets', $form_state->getValue('assets')); + $config->set('elements', $form_state->getValue('elements') + ['excluded_types' => $excluded_types]); + $config->set('file', $form_state->getValue('file')); + $config->set('format', $format); + $config->set('mail', $form_state->getValue('mail')); + $config->set('export', $this->submissionExporter->getValuesFromInput($form_state->getValues())); + $config->set('batch', $form_state->getValue('batch')); + $config->set('purge_settings', $form_state->getValue('purge_settings')); + $config->set('test', $form_state->getValue('test')); + $config->set('ui', $form_state->getValue('ui')); + $config->set('library', $form_state->getValue('library')); + $config->save(); + + /* Update paths */ + + if ($update_paths) { + /** @var \Drupal\webform\WebformInterface[] $webforms */ + $webforms = Webform::loadMultiple(); + foreach ($webforms as $webform) { + $webform->updatePaths(); + } + } + + parent::submitForm($form, $form_state); + } + + /** + * Wrapper for FileItem::validateExtensions. + */ + public static function validateExtensions($element, FormStateInterface $form_state) { + if (class_exists('\Drupal\file\Plugin\Field\FieldType\FileItem')) { + FileItem::validateExtensions($element, $form_state); + } + } + + /** + * 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); + } + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformAdminThirdPartySettingsForm.php b/web/modules/contrib/webform/src/Form/WebformAdminThirdPartySettingsForm.php new file mode 100644 index 000000000..e73bac033 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformAdminThirdPartySettingsForm.php @@ -0,0 +1,79 @@ +thirdPartySettingsManager = $third_party_settings_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('webform.third_party_settings_manager') + ); + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return ['webform.settings']; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = $this->thirdPartySettingsManager->buildForm($form, $form_state); + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $config = $this->config('webform.settings'); + $third_party_settings = $form_state->getValue('third_party_settings') + ($config->get('third_party_settings') ?: []); + $config->set('third_party_settings', $third_party_settings); + $config->save(); + parent::submitForm($form, $form_state); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformEntityFilterForm.php b/web/modules/contrib/webform/src/Form/WebformEntityFilterForm.php new file mode 100644 index 000000000..200ed7779 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformEntityFilterForm.php @@ -0,0 +1,31 @@ +t('Filter webforms'); + $form['filter']['search']['#title'] = $this->t('Filter by title, description, or elements'); + $form['filter']['search']['#autocomplete_route_name'] = 'entity.webform.autocomplete'; + $form['filter']['search']['#placeholder'] = $this->t('Filter by title, description, or elements'); + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformFilterFormBase.php b/web/modules/contrib/webform/src/Form/WebformFilterFormBase.php new file mode 100644 index 000000000..1082026b1 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformFilterFormBase.php @@ -0,0 +1,81 @@ + ['webform-filter-form']]; + $form['filter'] = [ + '#type' => 'details', + '#title' => $this->t('Filter submissions'), + '#open' => TRUE, + '#attributes' => ['class' => ['container-inline']], + ]; + $form['filter']['search'] = [ + '#type' => 'search', + '#title' => $this->t('Filter by submitted data and/or notes'), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Filter by submitted data and/or notes'), + '#maxlength' => 128, + '#size' => 40, + '#default_value' => $search, + ]; + $form['filter']['state'] = [ + '#type' => 'select', + '#title' => $this->t('State'), + '#title_display' => 'invisible', + '#options' => $state_options, + '#default_value' => $state, + ]; + $form['filter']['submit'] = [ + '#type' => 'submit', + '#button_type' => 'primary', + '#value' => $this->t('Filter'), + ]; + if (!empty($search) || !empty($state)) { + $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')), + 'state' => trim($form_state->getValue('state')), + ]; + $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/contrib/webform/src/Form/WebformHandlerAddForm.php b/web/modules/contrib/webform/src/Form/WebformHandlerAddForm.php new file mode 100644 index 000000000..719837cf2 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformHandlerAddForm.php @@ -0,0 +1,62 @@ +webformHandlerManager = $webform_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.webform.handler') + ); + } + + /** + * {@inheritdoc} + */ + 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('Add @label handler', ['@label' => $this->webformHandler->label()]); + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareWebformHandler($webform_handler) { + $webform_handler = $this->webformHandlerManager->createInstance($webform_handler); + // Initialize the handler an pass in the webform. + $webform_handler->init($this->webform); + // Set the initial weight so this handler comes last. + $webform_handler->setWeight(count($this->webform->getHandlers())); + return $webform_handler; + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformHandlerDeleteForm.php b/web/modules/contrib/webform/src/Form/WebformHandlerDeleteForm.php new file mode 100644 index 000000000..984943b52 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformHandlerDeleteForm.php @@ -0,0 +1,80 @@ +t('Are you sure you want to delete the @handler handler from the %webform webform?', ['%webform' => $this->webform->label(), '@handler' => $this->webformHandler->label()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return $this->webform->toUrl('handlers-form'); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_handler_delete_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL, $webform_handler = NULL) { + $this->webform = $webform; + $this->webformHandler = $this->webform->getHandler($webform_handler); + + $form = parent::buildForm($form, $form_state); + $this->buildConfirmFormDialog($form, $form_state); + return $form; + } + + /** + * {@inheritdoc} + */ + 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()])); + $form_state->setRedirectUrl($this->webform->toUrl('handlers-form')); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformHandlerDuplicateForm.php b/web/modules/contrib/webform/src/Form/WebformHandlerDuplicateForm.php new file mode 100644 index 000000000..ec3558ee3 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformHandlerDuplicateForm.php @@ -0,0 +1,35 @@ +t('Duplicate @label handler', ['@label' => $this->webformHandler->label()]); + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareWebformHandler($webform_handler) { + $webform_handler = clone $this->webform->getHandler($webform_handler); + $webform_handler->setHandlerId(NULL); + // Initialize the handler an pass in the webform. + $webform_handler->init($this->webform); + // Set the initial weight so this handler comes last. + $webform_handler->setWeight(count($this->webform->getHandlers())); + return $webform_handler; + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformHandlerEditForm.php b/web/modules/contrib/webform/src/Form/WebformHandlerEditForm.php new file mode 100644 index 000000000..c0aeee4a2 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformHandlerEditForm.php @@ -0,0 +1,29 @@ +t('Edit @label handler', ['@label' => $this->webformHandler->label()]); + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareWebformHandler($webform_handler) { + return $this->webform->getHandler($webform_handler); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformHandlerFormBase.php b/web/modules/contrib/webform/src/Form/WebformHandlerFormBase.php new file mode 100644 index 000000000..3a6f6a6c6 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformHandlerFormBase.php @@ -0,0 +1,255 @@ +webform = $webform; + try { + $this->webformHandler = $this->prepareWebformHandler($webform_handler); + } + catch (PluginNotFoundException $e) { + throw new NotFoundHttpException("Invalid handler id: '$webform_handler'."); + } + + // Limit the number of plugin instanced allowed. + if (!$this->webformHandler->getHandlerId()) { + $plugin_id = $this->webformHandler->getPluginId(); + $cardinality = $this->webformHandler->cardinality(); + $number_of_instances = $webform->getHandlers($plugin_id)->count(); + if ($cardinality !== WebformHandlerInterface::CARDINALITY_UNLIMITED && $cardinality <= $number_of_instances) { + $t_args = ['@number' => $cardinality, '@instances' => $this->formatPlural($cardinality, $this->t('instance is'), $this->t('instances are'))]; + throw new NotFoundHttpException($this->t('Only @number @instance permitted', $t_args)); + } + } + + $request = $this->getRequest(); + + $form['description'] = [ + '#markup' => $this->webformHandler->description(), + '#prefix' => '

          ', + '#suffix' => '

          ', + ]; + + $form['id'] = [ + '#type' => 'value', + '#value' => $this->webformHandler->getPluginId(), + ]; + + $form['status'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable the %name handler.', ['%name' => $this->webformHandler->label()]), + '#default_value' => $this->webformHandler->isEnabled(), + // Disable broken plugins. + '#disabled' => ($this->webformHandler->getPluginId() == 'broken'), + ]; + + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Title'), + '#maxlength' => 255, + '#default_value' => $this->webformHandler->label(), + '#required' => TRUE, + '#attributes' => ['autofocus' => 'autofocus'], + ]; + + $form['handler_id'] = [ + '#type' => 'machine_name', + '#maxlength' => 64, + '#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, + '#disabled' => $this->webformHandler->getHandlerId() ? TRUE : FALSE, + '#machine_name' => [ + 'exists' => [$this, 'exists'], + ], + ]; + + $form['settings'] = $this->webformHandler->buildConfigurationForm([], $form_state); + // Get $form['settings']['#attributes']['novalidate'] and apply it to the + // $form. + // This allows handlers with hide/show logic to skip HTML5 validation. + // @see http://stackoverflow.com/questions/22148080/an-invalid-form-control-with-name-is-not-focusable + if (isset($form['settings']['#attributes']['novalidate'])) { + $form['#attributes']['novalidate'] = 'novalidate'; + } + $form['settings']['#tree'] = TRUE; + + // Check the URL for a weight, then the webform handler, + // otherwise use default. + $form['weight'] = [ + '#type' => 'hidden', + '#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', + ]; + + $form = $this->buildFormDialog($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // The webform handler configuration is stored in the 'settings' key in + // the webform, pass that through for validation. + $settings = $form_state->getValue('settings') ?: []; + $handler_state = (new FormState())->setValues($settings); + $this->webformHandler->validateConfigurationForm($form, $handler_state); + + // Process handler state webform errors. + $this->processHandlerFormErrors($handler_state, $form_state); + + // Update the original webform values. + $form_state->setValue('settings', $handler_state->getValues()); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + if ($response = $this->validateDialog($form, $form_state)) { + return $response; + } + + $form_state->cleanValues(); + + // The webform handler configuration is stored in the 'settings' key in + // the webform, pass that through for submission. + $handler_data = (new FormState())->setValues($form_state->getValue('settings')); + + $this->webformHandler->submitConfigurationForm($form, $handler_data); + // Update the original webform values. + $form_state->setValue('settings', $handler_data->getValues()); + + $is_new = ($this->webformHandler->getHandlerId()) ? FALSE : TRUE; + + $this->webformHandler->setHandlerId($form_state->getValue('handler_id')); + $this->webformHandler->setLabel($form_state->getValue('label')); + $this->webformHandler->setStatus($form_state->getValue('status')); + $this->webformHandler->setWeight($form_state->getValue('weight')); + if ($is_new) { + $this->webform->addWebformHandler($this->webformHandler->getConfiguration()); + } + $this->webform->save(); + + // Display status message. + drupal_set_message($this->t('The webform handler was successfully applied.')); + + // Redirect. + return $this->redirectForm($form, $form_state, $this->webform->toUrl('handlers-form')); + } + + /** + * Generates a unique machine name for a webform handler instance. + * + * @param \Drupal\webform\WebformHandlerInterface $handler + * The webform handler. + * + * @return string + * Returns the unique name. + */ + public function getUniqueMachineName(WebformHandlerInterface $handler) { + $suggestion = $handler->getPluginId(); + $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 : ''; + } + + /** + * Determines if the webform handler ID already exists. + * + * @param string $handler_id + * The webform handler ID. + * + * @return bool + * TRUE if the webform handler ID exists, FALSE otherwise. + */ + public function exists($handler_id) { + $instance_ids = $this->webform->getHandlers()->getInstanceIds(); + + return (isset($instance_ids[$handler_id])) ? TRUE : FALSE; + } + + /** + * Process handler webform errors in webform. + * + * @param \Drupal\Core\Form\FormStateInterface $handler_state + * The webform handler webform state. + * @param \Drupal\Core\Form\FormStateInterface &$form_state + * The webform state. + */ + protected function processHandlerFormErrors(FormStateInterface $handler_state, FormStateInterface &$form_state) { + foreach ($handler_state->getErrors() as $name => $message) { + $form_state->setErrorByName($name, $message); + } + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformResultsClearForm.php b/web/modules/contrib/webform/src/Form/WebformResultsClearForm.php new file mode 100644 index 000000000..895b3729f --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformResultsClearForm.php @@ -0,0 +1,54 @@ +sourceEntity) { + $t_args = ['%title' => $this->sourceEntity->label()]; + } + else { + $t_args = ['%title' => $this->webform->label()]; + } + return $this->t('Are you sure you want to delete all submissions to the %title webform?', $t_args); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + $route_name = $this->requestHandler->getRouteName($this->webform, $this->sourceEntity, 'webform.results_submissions'); + $route_parameters = $this->requestHandler->getRouteParameters($this->webform, $this->sourceEntity); + return new Url($route_name, $route_parameters); + } + + /** + * {@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); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformResultsCustomForm.php b/web/modules/contrib/webform/src/Form/WebformResultsCustomForm.php new file mode 100644 index 000000000..2ff60d53e --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformResultsCustomForm.php @@ -0,0 +1,334 @@ +submissionStorage = $webform_submission_storage; + $this->requestHandler = $request_handler; + list($this->webform, $this->sourceEntity) = $this->requestHandler->getWebformEntities(); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('webform_submission'), + $container->get('webform.request') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $available_columns = $this->submissionStorage->getColumns($this->webform, $this->sourceEntity, NULL, TRUE); + $custom_columns = $this->submissionStorage->getCustomColumns($this->webform, $this->sourceEntity, NULL, TRUE); + // 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'); + } + + // Columns. + $columns_options = []; + foreach ($available_columns as $column_name => $column) { + $title = (strpos($column_name, 'element__') === 0) ? ['data' => ['#markup' => '' . $column['title'] . '']] : $column['title']; + $key = (isset($column['key'])) ? str_replace('webform_', '', $column['key']) : $column['name']; + $columns_options[$column_name] = ['title' => $title, 'key' => $key]; + } + $columns_keys = array_keys($custom_columns); + $columns_default_value = array_combine($columns_keys, $columns_keys); + $form['columns'] = [ + '#type' => 'webform_tableselect_sort', + '#header' => [ + 'title' => $this->t('Title'), + 'key' => $this->t('Key'), + ], + '#options' => $columns_options, + '#default_value' => $columns_default_value, + ]; + + // Get available sort options. + $sort_options = []; + $sort_columns = $available_columns; + ksort($sort_columns); + foreach ($sort_columns as $column_name => $column) { + if (!isset($column['sort']) || $column['sort'] === TRUE) { + $sort_options[$column_name] = (string) $column['title']; + }; + } + asort($sort_options); + + // Sort and direction. + // Display available columns sorted alphabetically. + $sort = $this->webform->getState($this->getStateKey('sort'), 'serial'); + $direction = $this->webform->getState($this->getStateKey('direction'), 'desc'); + $form['sort'] = [ + '#prefix' => '
          ', + '#type' => 'select', + '#field_prefix' => $this->t('Sort by'), + '#options' => $sort_options, + '#default_value' => $sort, + ]; + $form['direction'] = [ + '#type' => 'select', + '#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' => [ + 'asc' => $this->t('Ascending (ASC)'), + 'desc' => $this->t('Descending (DESC)'), + ], + '#default_value' => $direction, + '#suffix' => '
          ', + ]; + + // Limit. + $limit = $this->webform->getState($this->getStateKey('limit'), NULL); + $form['limit'] = [ + '#type' => 'select', + '#field_prefix' => $this->t('Show', [], ['context' => 'Show {limit} results per page.']), + '#field_suffix' => $this->t('results per page') . '.', + '#options' => [ + '20' => '20', + '50' => '50', + '100' => '100', + '200' => '200', + '500' => '500', + '1000' => '1000', + '0' => $this->t('All'), + ], + '#default_value' => ($limit != NULL) ? $limit : 50, + ]; + + // Default configuration. + if (empty($this->sourceEntity)) { + $form['config'] = [ + '#type' => 'details', + '#title' => $this->t('Configuration settings'), + ]; + $form['config']['default'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Use as default configuration'), + '#description' => $this->t('If checked, the above settings will be used as the default configuration for all associated Webform nodes.'), + '#return_value' => TRUE, + '#default_value' => $this->webform->getState($this->getStateKey('default'), TRUE), + ]; + } + + // Format settings. + $format = $this->webform->getState($this->getStateKey('format'), [ + 'header_format' => 'label', + 'element_format' => 'value', + ]); + $form['format'] = [ + '#type' => 'details', + '#title' => $this->t('Format settings'), + '#tree' => TRUE, + ]; + $form['format']['header_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Column header format'), + '#description' => $this->t('Choose whether to show the element label or element key in each column header.'), + '#options' => [ + 'label' => $this->t('Element titles (label)'), + 'key' => $this->t('Element keys (key)'), + ], + '#default_value' => $format['header_format'], + ]; + $form['format']['element_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Element format'), + '#options' => [ + 'value' => $this->t('Labels/values, the human-readable value (value)'), + 'raw' => $this->t('Raw values, the raw value stored in the database (raw)'), + ], + '#default_value' => $format['element_format'], + ]; + + // Build actions. + $form['actions']['#type'] = 'actions'; + $form['actions']['save'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + ]; + $form['actions']['delete'] = [ + '#type' => 'submit', + '#value' => $this->t('Reset'), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + ], + '#access' => $this->webform->hasState($this->getStateKey('columns')), + '#submit' => ['::delete'], + ]; + return $form; + } + + /** + * Build table row for a results columns. + * + * @param string $column_name + * The column name. + * @param array $column + * The column. + * @param bool $default_value + * Whether the column should be checked. + * @param int $weight + * The columns weights. + * @param int $delta + * The max delta for the weight element. + * + * @return array + * A renderable containing a table row for a results column. + */ + protected function buildRow($column_name, array $column, $default_value, $weight, $delta) { + return [ + '#attributes' => ['class' => ['draggable']], + 'name' => [ + '#type' => 'checkbox', + '#default_value' => $default_value, + ], + 'title' => [ + '#markup' => $column['title'], + ], + 'key' => [ + '#markup' => (isset($column['key'])) ? $column['key'] : $column['name'], + ], + 'weight' => [ + '#type' => 'weight', + '#title' => $this->t('Weight for @label', ['@label' => $column['title']]), + '#title_display' => 'invisible', + '#attributes' => [ + 'class' => ['table-sort-weight'], + ], + '#delta' => $delta, + '#default_value' => $weight, + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $columns = $form_state->getValue('columns'); + if (empty($columns)) { + $form_state->setErrorByName('columns', $this->t('At least once column is required')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // Set columns. + $this->webform->setState($this->getStateKey('columns'), $form_state->getValue('columns')); + + // Set sort, direction, limit. + $this->webform->setState($this->getStateKey('sort'), $form_state->getValue('sort')); + $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')); + + // Set default. + if (empty($this->sourceEntity)) { + $this->webform->setState($this->getStateKey('default'), $form_state->getValue('default')); + } + + // Display message. + drupal_set_message($this->t('The customized table has been saved.')); + + // Set redirect. + $route_name = $this->requestHandler->getRouteName($this->webform, $this->sourceEntity, 'webform.results_table'); + $route_parameters = $this->requestHandler->getRouteParameters($this->webform, $this->sourceEntity); + $form_state->setRedirect($route_name, $route_parameters); + } + + /** + * Webform delete customized columns 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 delete(array &$form, FormStateInterface $form_state) { + $this->webform->deleteState($this->getStateKey('columns')); + $this->webform->deleteState($this->getStateKey('sort')); + $this->webform->deleteState($this->getStateKey('direction')); + $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.')); + } + + /** + * Get the state key for the custom data. + * + * @return string + * The state key for the custom data. + */ + protected function getStateKey($name) { + if ($source_entity = $this->sourceEntity) { + return "results.custom.$name." . $source_entity->getEntityTypeId() . '.' . $source_entity->id(); + } + else { + return "results.custom.$name"; + } + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformResultsExportForm.php b/web/modules/contrib/webform/src/Form/WebformResultsExportForm.php new file mode 100644 index 000000000..01849f389 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformResultsExportForm.php @@ -0,0 +1,138 @@ +submissionExporter = $webform_submission_exporter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform_submission.exporter') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Set the merged default (global setting), saved, and user export options + // into the webform's state. + $settings_options = $this->config('webform.settings')->get('export'); + $saved_options = $this->submissionExporter->getWebformOptions(); + $user_options = $this->submissionExporter->getValuesFromInput($form_state->getUserInput()); + $export_options = NestedArray::mergeDeep($settings_options, $saved_options, $user_options); + + // Build the webform. + $this->submissionExporter->buildExportOptionsForm($form, $form_state, $export_options); + + // Build actions. + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Download'), + '#button_type' => 'primary', + ]; + $form['actions']['save'] = [ + '#type' => 'submit', + '#value' => $this->t('Save settings'), + '#submit' => ['::save'], + ]; + $form['actions']['delete'] = [ + '#type' => 'submit', + '#value' => $this->t('Reset settings'), + '#attributes' => [ + 'class' => ['button', 'button--danger'], + ], + '#access' => ($saved_options) ? TRUE : FALSE, + '#submit' => ['::delete'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $export_options = $this->submissionExporter->getValuesFromInput($form_state->getValues()); + + // Implode exclude columns. + $export_options['excluded_columns'] = implode(',', $export_options['excluded_columns']); + + if ($source_entity = $this->submissionExporter->getSourceEntity()) { + $entity_type = $source_entity->getEntityTypeId(); + $entity_id = $source_entity->id(); + $route_parameters = [$entity_type => $entity_id]; + $route_options = ['query' => $export_options]; + $form_state->setRedirect('entity.' . $entity_type . '.webform.results_export', $route_parameters, $route_options); + } + elseif ($webform = $this->submissionExporter->getWebform()) { + $route_parameters = ['webform' => $webform->id()]; + $route_options = ['query' => $export_options]; + $form_state->setRedirect('entity.webform.results_export', $route_parameters, $route_options); + } + } + + /** + * Webform save configuration 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 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.')); + } + + /** + * Webform delete configuration 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 delete(array &$form, FormStateInterface $form_state) { + $this->submissionExporter->deleteWebformOptions(); + drupal_set_message($this->t('The download settings have been reset.')); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteForm.php b/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteForm.php new file mode 100644 index 000000000..ae95d161a --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteForm.php @@ -0,0 +1,116 @@ +requestHandler = $request_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('webform.request') + ); + } + + /** + * {@inheritdoc} + */ + 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); + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to delete @title?', ['@title' => $this->webformSubmission->label()]); + } + + /** + * {@inheritdoc} + */ + protected function getDeletionMessage() { + return $this->t('@title has been deleted.', ['@title' => $this->webformSubmission->label()]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + $route_name = $this->requestHandler->getRouteName($this->webform, $this->sourceEntity, 'webform.results_submissions'); + $route_parameters = $this->requestHandler->getRouteParameters($this->webform, $this->sourceEntity); + return new Url($route_name, $route_parameters); + } + + /** + * {@inheritdoc} + */ + protected function getRedirectUrl() { + return $this->getCancelUrl(); + } + + /** + * {@inheritdoc} + */ + protected function logDeletionMessage() { + // Deletion logging is handled via WebformSubmissionStorage. + // @see \Drupal\webform\WebformSubmissionStorage::delete + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteMultiple.php b/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteMultiple.php new file mode 100644 index 000000000..1fc8fad5a --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionDeleteMultiple.php @@ -0,0 +1,125 @@ +tempStoreFactory = $temp_store_factory; + $this->storage = $manager->getStorage('webform_submission'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('user.private_tempstore'), + $container->get('entity.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'webform_submission_multiple_delete_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->formatPlural(count($this->webformSubmissionInfo), 'Are you sure you want to delete this submission?', 'Are you sure you want to delete these submissions?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.webform_submission.collection'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformSubmissionInterface[] $webform_submissions */ + $webform_submissions = $this->tempStoreFactory->get('webform_submission_multiple_delete_confirm')->get(\Drupal::currentUser()->id()); + if (empty($webform_submissions)) { + return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString()); + } + + $form['webform_submissions'] = [ + '#theme' => 'item_list', + '#items' => array_map(function ($webform_submission) { + return $webform_submission->label(); + }, $webform_submissions), + ]; + $form = parent::buildForm($form, $form_state); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformSubmissionInterface[] $webform_submissions */ + $webform_submissions = $this->tempStoreFactory->get('webform_submission_multiple_delete_confirm')->get(\Drupal::currentUser()->id()); + if ($form_state->getValue('confirm') && !empty($webform_submissions)) { + $this->storage->delete($webform_submissions); + $this->logger('content')->notice('Deleted @count submission.', ['@count' => count($webform_submissions)]); + $this->tempStoreFactory->get('webform_submission_multiple_delete_confirm')->delete(\Drupal::currentUser()->id()); + } + + $form_state->setRedirect('entity.webform_submission.collection'); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionFilterForm.php b/web/modules/contrib/webform/src/Form/WebformSubmissionFilterForm.php new file mode 100644 index 000000000..4d8f90576 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionFilterForm.php @@ -0,0 +1,30 @@ +t('Filter submissions'); + $form['filter']['search']['#title'] = $this->t('Filter by submitted data and/or notes'); + $form['filter']['search']['#placeholder'] = $this->t('Filter by submitted data and/or notes'); + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionResendForm.php b/web/modules/contrib/webform/src/Form/WebformSubmissionResendForm.php new file mode 100644 index 000000000..53e6b3539 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionResendForm.php @@ -0,0 +1,231 @@ +requestHandler = $request_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('webform.request') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission = NULL) { + $this->webformSubmission = $webform_submission; + + $handlers = $webform_submission->getWebform()->getHandlers(); + + /** @var \Drupal\webform\WebformHandlerMessageInterface[] $message_handlers */ + $message_handlers = []; + foreach ($handlers as $handler_id => $handler) { + if ($handler instanceof EmailWebformHandler) { + $message_handlers[$handler_id] = $handler; + } + } + + // Get header. + $header = []; + $header['title'] = [ + 'data' => $this->t('Title / Description'), + ]; + $header['id'] = [ + 'data' => $this->t('ID'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + $header['summary'] = [ + 'data' => $this->t('summary'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + $header['status'] = [ + 'data' => $this->t('Status'), + 'class' => [RESPONSIVE_PRIORITY_LOW], + ]; + + // Get options. + $options = []; + foreach ($message_handlers as $index => $message_handler) { + $message = $message_handler->getMessage($this->webformSubmission); + + $options[$index]['title'] = [ + 'data' => [ + 'label' => [ + '#type' => 'label', + '#title' => $message_handler->label() . ': ' . $message_handler->description(), + '#title_display' => NULL, + '#for' => 'edit-message-handler-id-' . str_replace('_', '-', $message_handler->getHandlerId()), + ], + ], + ]; + $options[$index]['id'] = [ + 'data' => $message_handler->getHandlerId(), + ]; + $options[$index]['summary'] = [ + 'data' => $message_handler->getMessageSummary($message), + ]; + $options[$index]['status'] = ($message_handler->isEnabled()) ? $this->t('Enabled') : $this->t('Disabled'); + } + + // Get message handler id. + if (empty($form_state->getValue('message_handler_id'))) { + reset($options); + $message_handler_id = key($options); + $form_state->setValue('message_handler_id', $message_handler_id); + } + else { + $message_handler_id = $form_state->getValue('message_handler_id'); + } + + $message_handler = $this->getMessageHandler($form_state); + $form['message_handler_id'] = [ + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#js_select' => TRUE, + '#empty' => $this->t('No messages are available.'), + '#multiple' => FALSE, + '#default_value' => $message_handler_id, + '#ajax' => [ + 'callback' => '::updateMessage', + 'wrapper' => 'edit-webform-message-wrapper', + ], + ]; + + // Message. + $form['message'] = [ + '#type' => 'details', + '#title' => $this->t('Message'), + '#open' => TRUE, + '#tree' => TRUE, + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + $message = $message_handler->getMessage($webform_submission); + $form['message'] += $message_handler->resendMessageForm($message); + + // Add resend button. + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Resend message'), + ]; + + // Add submission navigation. + $source_entity = $this->requestHandler->getCurrentSourceEntity('webform_submission'); + $form['navigation'] = [ + '#theme' => 'webform_submission_navigation', + '#webform_submission' => $webform_submission, + '#weight' => -20, + ]; + $form['information'] = [ + '#theme' => 'webform_submission_information', + '#webform_submission' => $webform_submission, + '#source_entity' => $source_entity, + '#weight' => -19, + ]; + $form['#attached']['library'][] = 'webform/webform.admin'; + + return $form; + } + + /** + * Handles switching between messages. + * + * @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 an email message. + */ + public function updateMessage(array $form, FormStateInterface $form_state) { + $message_handler = $this->getMessageHandler($form_state); + $message = $message_handler->getMessage($this->webformSubmission); + foreach ($message as $key => $value) { + $form['message'][$key]['#value'] = $value; + } + return $form['message']; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $params = $form_state->getValue('message'); + $message_handler = $this->getMessageHandler($form_state); + $message_handler->sendMessage($params); + + $t_args = [ + '%label' => $message_handler->label(), + ]; + drupal_set_message($this->t('Successfully re-sent %label.', $t_args)); + } + + /** + * Get message handler from webform state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\webform\WebformHandlerMessageInterface + * The current message handler. + */ + protected function getMessageHandler(FormStateInterface $form_state) { + $message_handler_id = $form_state->getValue('message_handler_id'); + return $this->webformSubmission->getWebform()->getHandler($message_handler_id); + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionsDeleteFormBase.php b/web/modules/contrib/webform/src/Form/WebformSubmissionsDeleteFormBase.php new file mode 100644 index 000000000..03f436279 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionsDeleteFormBase.php @@ -0,0 +1,210 @@ +submissionStorage = $webform_submission_storage; + $this->requestHandler = $request_handler; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('webform_submission'), + $container->get('webform.request') + ); + } + + /** + * {@inheritdoc} + */ + 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} + */ + 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()); + } + else { + $this->batchSet($this->webform, $this->sourceEntity); + } + } + + /** + * Message to displayed after submissions are deleted. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * Message to be displayed after delete has finished. + */ + public function getFinishedMessage() { + return $this->t('Webform submissions cleared.'); + } + + /** + * Batch API; Initialize batch operations. + * + * @param \Drupal\webform\WebformInterface|null $webform + * The webform. + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * The webform's source entity. + */ + public function batchSet(WebformInterface $webform = NULL, EntityInterface $entity = NULL) { + $parameters = [ + $webform, + $entity, + $this->submissionStorage->getMaxSubmissionId($webform, $entity), + ]; + $batch = [ + 'title' => $this->t('Clear submissions'), + 'init_message' => $this->t('Clearing submission data'), + 'error_message' => $this->t('The submissions could not be cleared because an error occurred.'), + 'operations' => [ + [[$this, 'batchProcess'], $parameters], + ], + 'finished' => [$this, 'batchFinish'], + ]; + + batch_set($batch); + } + + /** + * Get the number of submissions to be deleted with each batch. + * + * @return int + * Number of submissions to be deleted with each batch. + */ + public function getBatchLimit() { + return $this->config('webform.settings')->get('batch.default_batch_delete_size') ?: $this->batchLimit; + } + + /** + * Batch API callback; Delete submissions. + * + * @param \Drupal\webform\WebformInterface|null $webform + * The webform. + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * The webform's source entity. + * @param int $max_sid + * The max submission ID to be delete. + * @param mixed|array $context + * The batch current context. + */ + public function batchProcess(WebformInterface $webform = NULL, EntityInterface $entity = NULL, $max_sid, &$context) { + // ISSUE: + // $this->submissionStorage is not being setup via + // WebformSubmissionsDeleteFormBase::__construct. + // + // WORKAROUND: + // Reset it for each batch process. + $this->submissionStorage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['max'] = $this->submissionStorage->getTotal($webform, $entity); + $context['results']['webform'] = $webform; + $context['results']['entity'] = $entity; + } + + // 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']]); + + // Track finished. + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } + } + + /** + * Batch API callback; Completed deletion. + * + * @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 function batchFinish($success = FALSE, array $results, array $operations) { + if (!$success) { + drupal_set_message($this->t('Finished with an error.')); + } + else { + drupal_set_message($this->getFinishedMessage()); + } + } + +} diff --git a/web/modules/contrib/webform/src/Form/WebformSubmissionsPurgeForm.php b/web/modules/contrib/webform/src/Form/WebformSubmissionsPurgeForm.php new file mode 100644 index 000000000..2969fe122 --- /dev/null +++ b/web/modules/contrib/webform/src/Form/WebformSubmissionsPurgeForm.php @@ -0,0 +1,73 @@ +t('Are you sure you want to delete all submissions?'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Purge'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.webform_submission.collection'); + } + + /** + * {@inheritdoc} + */ + public function getFinishedMessage() { + return $this->t('Webform submissions purged.'); + } + + /** + * {@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(); + $t_args = [ + '@submission_total' => $submission_total, + '@submissions' => $this->formatPlural($submission_total, $this->t('submission'), $this->t('submissions')), + '@form_total' => $form_total, + '@forms' => $this->formatPlural($form_total, $this->t('webform'), $this->t('webforms')), + ]; + + $form['confirm'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Are you sure you want to delete @submission_total @submissions in @form_total @forms?', $t_args), + '#required' => TRUE, + '#weight' => -10, + ]; + + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Normalizer/WebformEntityReferenceItemNormalizer.php b/web/modules/contrib/webform/src/Normalizer/WebformEntityReferenceItemNormalizer.php new file mode 100644 index 000000000..13ffe6433 --- /dev/null +++ b/web/modules/contrib/webform/src/Normalizer/WebformEntityReferenceItemNormalizer.php @@ -0,0 +1,32 @@ +currentUser = $current_user; + $this->tempStoreFactory = $temp_store_factory; + + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('user.private_tempstore'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + $this->tempStoreFactory->get('webform_submission_multiple_delete_confirm')->set($this->currentUser->id(), $entities); + } + + /** + * {@inheritdoc} + */ + public function execute($object = NULL) { + $this->executeMultiple([$object]); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\webform\WebformSubmissionInterface $object */ + return $object->access('delete', $account, $return_as_object); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Action/StickyWebformSubmission.php b/web/modules/contrib/webform/src/Plugin/Action/StickyWebformSubmission.php new file mode 100644 index 000000000..faf8e40ac --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Action/StickyWebformSubmission.php @@ -0,0 +1,38 @@ +setSticky(TRUE)->save(); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\webform\WebformSubmissionInterface $object */ + $result = $object->sticky->access('edit', $account, TRUE) + ->andIf($object->access('update', $account, TRUE)); + + return $return_as_object ? $result : $result->isAllowed(); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Action/UnstickyWebformSubmission.php b/web/modules/contrib/webform/src/Plugin/Action/UnstickyWebformSubmission.php new file mode 100644 index 000000000..1bf047e7b --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Action/UnstickyWebformSubmission.php @@ -0,0 +1,38 @@ +setSticky(FALSE)->save(); + } + + /** + * {@inheritdoc} + */ + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\webform\WebformSubmissionInterface $object */ + $result = $object->sticky->access('edit', $account, TRUE) + ->andIf($object->access('update', $account, TRUE)); + + return $return_as_object ? $result : $result->isAllowed(); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Block/WebformBlock.php b/web/modules/contrib/webform/src/Plugin/Block/WebformBlock.php new file mode 100644 index 000000000..2447d0c09 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Block/WebformBlock.php @@ -0,0 +1,150 @@ +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('webform.token_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'webform_id' => '', + 'default_data' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $form['webform_id'] = [ + '#title' => $this->t('Webform'), + '#type' => 'entity_autocomplete', + '#target_type' => 'webform', + '#required' => TRUE, + '#default_value' => $this->getWebform(), + ]; + $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'], + ]; + $form['token_tree_link'] = $this->tokenManager->buildTreeLink(); + return $form; + } + + /** + * {@inheritdoc} + */ + 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'); + } + + /** + * {@inheritdoc} + */ + public function build() { + return [ + '#type' => 'webform', + '#webform' => $this->getWebform(), + '#default_data' => $this->configuration['default_data'], + ]; + } + + /** + * {@inheritdoc} + */ + protected function blockAccess(AccountInterface $account) { + $webform = $this->getWebform(); + if (!$webform || !$webform->access('submission_create', $account)) { + return AccessResult::forbidden(); + } + else { + return parent::blockAccess($account); + } + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // Caching strategy is handled by the webform. + return 0; + } + + /** + * Get this block instance webform. + * + * @return \Drupal\webform\WebformInterface + * A webform or NULL. + */ + protected function getWebform() { + return Webform::load($this->configuration['webform_id']); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Condition/Webform.php b/web/modules/contrib/webform/src/Plugin/Condition/Webform.php new file mode 100644 index 000000000..b64f4f7b5 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Condition/Webform.php @@ -0,0 +1,189 @@ +entityStorage = $entity_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get('entity.manager')->getStorage('webform'), + $configuration, + $plugin_id, + $plugin_definition + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $options = []; + $webforms = $this->entityStorage->loadMultiple(); + foreach ($webforms as $webform) { + $options[$webform->id()] = $webform->label(); + } + $form['webforms'] = [ + '#title' => $this->t('Webform'), + '#description' => $this->t('Select which webforms this block should be displayed on.'), + '#type' => 'select', + '#options' => $options, + '#multiple' => $options, + '#default_value' => $this->configuration['webforms'], + '#attached' => ['library' => ['webform/webform.element.select2']], + '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']], + ]; + + if (empty($this->configuration['context_mapping'])) { + $form['message'] = [ + '#type' => 'webform_message', + '#message_message' => $this->t('Please make sure to select which entities should be used to determine the current webform.'), + '#message_type' => 'warning', + ]; + } + + $form = parent::buildConfigurationForm($form, $form_state); + + // Add helpful descriptions to context mapping. + $form['context_mapping']['webform']['#description'] = $this->t("Select 'Webform from URL' to display this block, when the current request's path contains the selected webform."); + $form['context_mapping']['webform_submission']['#title'] = $this->t('Select a @context value:', ['@context' => $this->t('webform submission')]); + $form['context_mapping']['webform_submission']['#description'] = $this->t("Select 'Webform submission from URL' to display this block, when the current request's path contains a webform submission that was created from the selected webform."); + $form['context_mapping']['node']['#description'] = $this->t("Select 'Node from URL' to display this block, when the current request's path contains a node that references the selected webform using a dedicated webform field or node."); + + // Hide 'Negate the condition', which does not make sense. + if (isset($form['negate'])) { + $form['negate']['#access'] = FALSE; + } + + // Attached library to summarize configuration settings. + $form['#attached']['library'][] = 'webform/webform.block'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + $values = $form_state->getValues(); + if (!empty($values['webforms']) && empty(array_filter($values['context_mapping']))) { + $form_state->setErrorByName('webforms', $this->t('Please select which entity should be used to determine the current webform.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + $this->configuration['webforms'] = array_filter($form_state->getValue('webforms')); + parent::submitConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function summary() { + if (count($this->configuration['webforms']) > 1) { + $webforms = $this->configuration['webforms']; + $last = array_pop($webforms); + $webforms = implode(', ', $webforms); + return $this->t('The webform is @webforms or @last', ['@webforms' => $webforms, '@last' => $last]); + } + $webform = reset($this->configuration['webforms']); + return $this->t('The webform is @webform', ['@webform' => $webform]); + } + + /** + * {@inheritdoc} + */ + public function evaluate() { + if (empty($this->configuration['webforms']) && !$this->isNegated()) { + return TRUE; + } + elseif ($webform = $this->getContextWebform()) { + return !empty($this->configuration['webforms'][$webform->id()]); + } + else { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return ['webforms' => []] + parent::defaultConfiguration(); + } + + /** + * Gets the webform for a defined context. + * + * @return null|\Drupal\webform\WebformInterface + * The current context's webform. + */ + protected function getContextWebform() { + if ($webform_submission = $this->getContextValue('webform_submission')) { + return $webform_submission->getWebform(); + } + if ($webform = $this->getContextValue('webform')) { + return $webform; + } + if ($node = $this->getContextValue('node')) { + $webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($node); + if ($webform_field_name && $node->$webform_field_name->entity) { + return $node->$webform_field_name->entity; + } + } + return NULL; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php b/web/modules/contrib/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php new file mode 100644 index 000000000..6d924c4de --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/DevelGenerate/WebformSubmissionDevelGenerate.php @@ -0,0 +1,403 @@ +entityTypeManager = $entity_type_manager; + $this->webformSubmissionGenerate = $webform_submission_generate; + + $this->webformStorage = $entity_type_manager->getStorage('webform'); + $this->webformSubmissionStorage = $entity_type_manager->getStorage('webform_submission'); + } + + /** + * {@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('webform_submission.generate') + ); + } + + /** + * {@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'); + $options = []; + foreach ($this->webformStorage->loadMultiple() as $webform) { + $options[$webform->id()] = $webform->label(); + } + $form['webform_ids'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Webform'), + '#description' => $this->t('Restrict submissions to these webforms.'), + '#required' => TRUE, + '#options' => $options, + ]; + $form['num'] = [ + '#type' => 'number', + '#title' => $this->t('Number of submissions?'), + '#min' => 1, + '#required' => TRUE, + '#default_value' => $this->getSetting('num'), + ]; + $form['kill'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Delete existing submissions in specified webform before generating new submissions.'), + '#default_value' => $this->getSetting('kill'), + ]; + $entity_types = \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE); + $form['submitted'] = [ + '#type' => 'item', + '#title' => $this->t('Submitted to'), + '#field_prefix' => '
          ', + '#field_suffix' => '
          ', + ]; + $form['submitted']['entity-type'] = [ + '#type' => 'select', + '#title' => $this->t('Entity type'), + '#title_display' => 'Invisible', + '#options' => ['' => ''] + $entity_types, + '#default_value' => $this->getSetting('entity-type'), + ]; + $form['submitted']['entity-id'] = [ + '#type' => 'number', + '#title' => $this->t('Entity id'), + '#title_display' => 'Invisible', + '#default_value' => $this->getSetting('entity-id'), + '#min' => 1, + '#size' => 10, + '#states' => [ + 'invisible' => [ + ':input[name="entity-type"]' => ['value' => ''], + ], + ], + ]; + + $form['#validate'] = [[$this, 'validateForm']]; + return $form; + } + + /** + * Custom validation handler. + */ + public function validateForm(array $form, FormStateInterface $form_state) { + $webform_ids = array_filter($form_state->getValue('webform_ids')); + + // Let default webform validation handle requiring webform ids. + if (empty($webform_ids)) { + return; + } + + $entity_type = $form_state->getValue('entity-type'); + $entity_id = $form_state->getValue('entity-id'); + if ($entity_type || $entity_id) { + if ($error = $this->validateEntity($webform_ids, $entity_type, $entity_id)) { + $form_state->setErrorByName('entity_type', $error); + } + } + } + + /** + * {@inheritdoc} + */ + public function generateElements(array $values) { + $this->generateSubmissions($values); + } + + /** + * Generates submissions for a list of given webforms. + * + * @param array $values + * The element values from the settings webform. + */ + protected function generateSubmissions(array $values) { + self::$generatingSubmissions = TRUE; + if (!empty($values['kill'])) { + $this->deleteWebformSubmissions($values['webform_ids'], $values['entity-type'], $values['entity-id']); + $this->setMessage($this->t('Deleted existing submissions.')); + } + if (!empty($values['webform_ids'])) { + $this->initializeGenerate($values); + $start = time(); + for ($i = 1; $i <= $values['num']; $i++) { + $this->generateSubmission($values); + if (function_exists('drush_log') && $i % drush_get_option('feedback', 1000) == 0) { + $now = time(); + drush_log(dt('Completed @feedback submissions (@rate submissions/min)', ['@feedback' => drush_get_option('feedback', 1000), '@rate' => (drush_get_option('feedback', 1000) * 60) / ($now - $start)]), 'ok'); + $start = $now; + } + } + } + $this->setMessage($this->formatPlural($values['num'], '1 submissions created.', 'Finished creating @count submissions')); + self::$generatingSubmissions = FALSE; + } + + /** + * Deletes all submissions of given webforms. + * + * @param array $webform_ids + * Array of webform ids. + * @param string|null $entity_type + * A webform source entity type. + * @param int|null $entity_id + * A webform source entity id. + */ + protected function deleteWebformSubmissions(array $webform_ids, $entity_type = NULL, $entity_id = NULL) { + $webforms = $this->webformStorage->loadMultiple($webform_ids); + $entity = ($entity_type && $entity_id) ? $this->entityTypeManager->getStorage($entity_type)->load($entity_id) : NULL; + foreach ($webforms as $webform) { + $this->webformSubmissionStorage->deleteAll($webform, $entity); + } + } + + /** + * Add 'users' that contains a list of uids. + * + * @param array $values + * The element values from the settings webform. + */ + protected function initializeGenerate(array &$values) { + // Set user id.$devel_generate_manager = \Drupal::service('plugin.manager.develgenerate') + $users = $this->getUsers(); + $users = array_merge($users, ['0']); + $values['users'] = $users; + + // Set created min and max. + $values['created_min'] = strtotime('-1 month'); + $values['created_max'] = time(); + + // Set entity type and id default value. + $values += [ + 'num' => 50, + 'entity-type' => '', + 'entity-id' => '', + ]; + } + + /** + * Create one node. Used by both batch and non-batch code branches. + */ + protected function generateSubmission(&$results) { + $webform_id = array_rand(array_filter($results['webform_ids'])); + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $this->webformStorage->load($webform_id); + + $users = $results['users']; + $uid = $users[array_rand($users)]; + $entity_type = $results['entity-type']; + $entity_id = $results['entity-id']; + + $timestamp = rand($results['created_min'], $results['created_max']); + $this->webformSubmissionStorage->create([ + 'webform_id' => $webform_id, + 'entity_type' => $entity_type, + 'entity_id' => $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() . '#', '/', $webform->toUrl()->toString()), + 'data' => Yaml::encode($this->webformSubmissionGenerate->getData($webform)), + 'created' => $timestamp, + 'changed' => $timestamp, + ])->save(); + } + + /** + * {@inheritdoc} + */ + public function validateDrushParams($args) { + $webform_id = array_shift($args); + $webform_ids = [$webform_id => $webform_id]; + $values = [ + 'webform_ids' => $webform_ids, + 'num' => array_shift($args) ?: 50, + 'kill' => drush_get_option('kill') ?: FALSE, + ]; + + if (empty($webform_id)) { + return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Webform id required')); + } + + if (!$this->webformStorage->load($webform_id)) { + return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid webform name: @name', ['@name' => $webform_id])); + } + + if ($this->isNumber($values['num']) == FALSE) { + return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of submissions: @num', ['@num' => $values['num']])); + } + + $entity_type = drush_get_option('entity-type'); + $entity_id = drush_get_option('entity-id'); + if ($entity_type || $entity_id) { + if ($error = $this->validateEntity($webform_ids, $entity_type, $entity_id)) { + return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', $error); + } + else { + $values['entity-type'] = $entity_type; + $values['entity-id'] = $entity_id; + } + } + + return $values; + } + + /** + * Retrieve 50 uids from the database. + * + * @return array + * An array of uids. + */ + protected function getUsers() { + $users = []; + $result = db_query_range("SELECT uid FROM {users}", 0, 50); + foreach ($result as $record) { + $users[] = $record->uid; + } + return $users; + } + + /** + * Track if webform submissions are being generated. + * + * Used to block emails from being sent while using devel generate. + * + * @return bool + * TRUE if webform submissions are being generated. + */ + public static function isGeneratingSubmissions() { + return self::$generatingSubmissions; + } + + /** + * Validate webform source entity type and id. + * + * @param array $webform_ids + * An array webform ids. + * @param string $entity_type + * An entity type. + * @param int $entity_id + * An entity id. + * + * @return string + * An error message or NULL if there are no validation errors. + */ + protected function validateEntity(array $webform_ids, $entity_type, $entity_id) { + $t = function_exists('dt') ? 'dt' : 't'; + + if (!$entity_type) { + return $t('Entity type is required'); + } + + if (!$entity_id) { + return $t('Entity id is required'); + } + + $dt_args = ['@entity_type' => $entity_type, '@entity_id' => $entity_id]; + + $source_entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id); + if (!$source_entity) { + return $t('Unable to load @entity_type:@entity_id', $dt_args); + } + + $dt_args['@title'] = $source_entity->label(); + $webform_field_name = WebformEntityReferenceItem::getEntityWebformFieldName($source_entity); + if (!$webform_field_name) { + return $t("'@title' (@entity_type:@entity_id) does not have a 'webform' field.", $dt_args); + } + + if (count($webform_ids) > 1) { + return $t("'@title' (@entity_type:@entity_id) can only be associated with a single webform.", $dt_args); + } + + $dt_args['@webform_ids'] = WebformArrayHelper::toString($webform_ids, $t('or')); + if (!in_array($source_entity->webform->target_id, $webform_ids)) { + return $t("'@title' (@entity_type:@entity_id) does not have a '@webform_ids' webform associated with it.", $dt_args); + } + + return NULL; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php b/web/modules/contrib/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php new file mode 100644 index 000000000..8b08e1043 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/EntityReferenceSelection/WebformSelection.php @@ -0,0 +1,43 @@ +condition('template', FALSE); + return $query; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php b/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php new file mode 100644 index 000000000..909fd7d40 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceEntityFormatter.php @@ -0,0 +1,108 @@ +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('webform.message_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $source_entity = $items->getEntity(); + $this->messageManager->setSourceEntity($source_entity); + + $elements = []; + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { + /** @var \Drupal\webform\WebformInterface $entity */ + + // Do not display the webform if the current user can't create submissions. + if ($entity->id() && !$entity->access('submission_create')) { + continue; + } + + if ($entity->id() && $items[$delta]->status) { + $values = []; + if (!empty($items[$delta]->default_data)) { + $values['data'] = Yaml::decode($items[$delta]->default_data); + } + $elements[$delta] = $entity->getSubmissionForm($values); + } + else { + $this->messageManager->setWebform($entity); + $elements[$delta] = $this->messageManager->build(WebformMessageManagerInterface::FORM_CLOSED_MESSAGE); + } + } + + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php b/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php new file mode 100644 index 000000000..95422472b --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Field/FieldFormatter/WebformEntityReferenceLinkFormatter.php @@ -0,0 +1,155 @@ +messageManager = $message_manager; + $this->token = $token; + } + + /** + * {@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('webform.message_manager'), + $container->get('token') + ); + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + 'label' => 'Go to [webform:title] webform', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + $summary[] = $this->t('Label: @label', ['@label' => $this->getSetting('label')]); + return $summary; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $element['label'] = [ + '#title' => $this->t('Label'), + '#type' => 'textfield', + '#default_value' => $this->getSetting('label'), + '#required' => TRUE, + ]; + return $element; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $source_entity = $items->getEntity(); + $this->messageManager->setSourceEntity($source_entity); + + $elements = []; + + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { + /** @var \Drupal\webform\WebformInterface $entity */ + if ($entity->id() && $items[$delta]->status) { + $link_options = [ + 'query' => [ + 'source_entity_type' => $source_entity->getEntityTypeId(), + 'source_entity_id' => $source_entity->id(), + ], + ]; + $elements[$delta] = [ + '#type' => 'link', + '#title' => $this->token->replace($this->getSetting('label'), [ + 'webform' => $entity, + ]), + '#url' => $entity->toUrl('canonical', $link_options), + ]; + } + else { + /** @var \Drupal\webform\WebformMessageManagerInterface $message_manager */ + $this->messageManager->setWebform($entity); + $elements[$delta] = $this->messageManager->build(WebformMessageManagerInterface::FORM_CLOSED_MESSAGE); + } + } + + return $elements; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Field/FieldType/WebformEntityReferenceFieldItemList.php b/web/modules/contrib/webform/src/Plugin/Field/FieldType/WebformEntityReferenceFieldItemList.php new file mode 100644 index 000000000..f0a35ddea --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Field/FieldType/WebformEntityReferenceFieldItemList.php @@ -0,0 +1,12 @@ + 'webform', + ] + parent::defaultStorageSettings(); + } + + /** + * {@inheritdoc} + */ + public static function defaultFieldSettings() { + return [ + 'default_data' => '', + 'status' => TRUE, + ] + parent::defaultFieldSettings(); + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return [ + 'columns' => [ + 'target_id' => [ + 'description' => 'The ID of the webform entity.', + 'type' => 'varchar_ascii', + 'length' => EntityTypeInterface::BUNDLE_MAX_LENGTH, + ], + 'default_data' => [ + 'description' => 'Default submission data.', + 'type' => 'text', + ], + 'status' => [ + 'description' => 'Flag to control whether this webform should be open or closed to new submissions.', + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => TRUE, + 'default' => 1, + ], + ], + 'indexes' => [ + 'target_id' => ['target_id'], + ], + ]; + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties = parent::propertyDefinitions($field_definition); + + $properties['default_data'] = DataDefinition::create('string') + ->setLabel(t('Default submission data')); + + $properties['status'] = DataDefinition::create('boolean') + ->setLabel(t('Status')) + ->setDescription(t('Flag to control whether this webform should be open or closed to new submissions.')); + + return $properties; + } + + /** + * Get an entity's webform field name. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * A fieldable content entity. + * + * @return string + * The name of the webform field or an empty string. + */ + public static function getEntityWebformFieldName(EntityInterface $entity = NULL) { + if ($entity === NULL || !method_exists($entity, 'hasField')) { + return ''; + } + + if ($entity instanceof ContentEntityInterface) { + $fields = $entity->getFieldDefinitions(); + foreach ($fields as $field_name => $field_definition) { + if ($field_definition->getType() == 'webform') { + return $field_name; + } + } + } + return ''; + } + + /** + * {@inheritdoc} + */ + public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) { + return []; + } + + /** + * {@inheritdoc} + */ + public static function getPreconfiguredOptions() { + return []; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php b/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php new file mode 100644 index 000000000..2864151c1 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceAutocompleteWidget.php @@ -0,0 +1,69 @@ +status)) { + $items[$delta]->status = 1; + } + + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + // Set element 'target_id' default properties. + $element['target_id'] += [ + '#weight' => 0, + ]; + + // Get weight. + $weight = $element['target_id']['#weight']; + + $element['default_data'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#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.'), + '#weight' => $weight++, + '#default_value' => $items[$delta]->default_data, + ]; + + /** @var \Drupal\webform\WebformTokenManagerInterface $token_manager */ + $token_manager = \Drupal::service('webform.token_manager'); + $element['token_tree_link'] = $token_manager->buildTreeLink(); + + $element['status'] = [ + '#type' => 'radios', + '#title' => $this->t('Webform status'), + '#description' => $this->t('Closing a webform prevents any further submissions by any users.'), + '#options' => [ + 1 => $this->t('Open'), + 0 => $this->t('Closed'), + ], + '#weight' => $weight++, + '#default_value' => ($items[$delta]->status == 1) ? 1 : 0, + ]; + + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php b/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php new file mode 100644 index 000000000..27ef536ad --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Field/FieldWidget/WebformEntityReferenceSelectWidget.php @@ -0,0 +1,89 @@ +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(); + } + + // 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']]; + + return $element; + } + + /** + * Webform element validation handler for entity_select elements. + */ + public static function validateWebformEntityReferenceSelectWidget(&$element, FormStateInterface $form_state, &$complete_form) { + // 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); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/Mail/WebformPhpMail.php b/web/modules/contrib/webform/src/Plugin/Mail/WebformPhpMail.php new file mode 100644 index 000000000..f1bbae46d --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/Mail/WebformPhpMail.php @@ -0,0 +1,31 @@ +getItemFormat($element); + + switch ($format) { + case 'value': + return ($value) ? $this->t('Yes') : $this->t('No'); + + default: + return ($value) ? 1 : 0; + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Captcha.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Captcha.php new file mode 100644 index 000000000..476f450d5 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Captcha.php @@ -0,0 +1,119 @@ + 'default', + 'captcha_admin_mode' => FALSE, + // Flexbox. + 'flex' => 1, + // Conditional logic. + ]; + } + + /** + * {@inheritdoc} + */ + public function isInput(array $element) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return []; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + // Enable admin mode for test or user with 'skip CAPTCHA' permission. + $is_test = (strpos(\Drupal::routeMatch()->getRouteName(), '.webform.test') !== FALSE) ? TRUE : FALSE; + $is_admin = \Drupal::currentUser()->hasPermission('skip CAPTCHA'); + if ($is_test || $is_admin) { + $element['#captcha_admin_mode'] = TRUE; + } + parent::prepare($element, $webform_submission); + } + + /** + * {@inheritdoc} + */ + public function preSave(array &$element, WebformSubmissionInterface $webform_submission) { + // Remove all captcha related keys from the webform submission's data. + $key = $element['#webform_key']; + $data = $webform_submission->getData(); + unset($data[$key]); + // @see \Drupal\captcha\Element\Captcha + $sub_keys = ['sid', 'token', 'response']; + foreach ($sub_keys as $sub_key) { + unset($data[$key . '_' . $sub_key]); + } + $webform_submission->setData($data); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + if (\Drupal::moduleHandler()->moduleExists('captcha')) { + module_load_include('inc', 'captcha', 'captcha.admin'); + $captcha_types = _captcha_available_challenge_types(); + } + else { + $captcha_types = ['default' => $this->t('Default challenge type')]; + } + $form['captcha'] = [ + '#type' => 'fieldset', + '#title' => $this->t('CAPTCHA settings'), + ]; + $form['captcha']['captcha_type'] = [ + '#type' => 'select', + '#title' => $this->t('Challenge type'), + '#required' => TRUE, + '#options' => $captcha_types, + ]; + $form['captcha']['captcha_admin_mode'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Admin mode'), + '#description' => $this->t('Presolve the CAPTCHA and always shows it. This is useful for debugging and preview CAPTCHA integration.'), + '#return_value' => TRUE, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Checkbox.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Checkbox.php new file mode 100644 index 000000000..17518ab3e --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Checkbox.php @@ -0,0 +1,27 @@ + 'after', + ] + parent::getDefaultProperties(); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Checkboxes.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Checkboxes.php new file mode 100644 index 000000000..8d372976a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Checkboxes.php @@ -0,0 +1,78 @@ + TRUE, + 'multiple_error' => '', + // Options settings. + 'options_display' => 'one_column', + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleValues(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + $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) { + $text .= ' [' . $this->t('Checkbox') . ']'; + } + return $selectors; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Checkboxes must require > 2 options. + $form['element']['multiple']['#min'] = 2; + + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Color.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Color.php new file mode 100644 index 000000000..318ea62f6 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Color.php @@ -0,0 +1,114 @@ + 'medium', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Set the color swatches size. + $color_size = (isset($element['#color_size'])) ? $element['#color_size'] : 'medium'; + $element['#attributes']['class'][] = 'form-color-' . $color_size; + + // Add helpful attributes to better support older browsers. + // @see http://www.wufoo.com/html5/types/6-color.html + $element['#attributes'] += [ + 'title' => $this->t('Hexadecimal color'), + 'pattern' => '#[a-f0-9]{6}', + 'placeholder' => '#000000', + ]; + + $element['#attached']['library'][] = 'webform/webform.element.color'; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'swatch': + return [ + '#theme' => 'webform_element_color_value_swatch', + '#element' => $element, + '#value' => $value, + '#options' => $options, + ]; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'swatch'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'swatch' => $this->t('Color swatch'), + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['color'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Color settings'), + ]; + $form['color']['color_size'] = [ + '#type' => 'select', + '#title' => $this->t('Color swatch size'), + '#options' => [ + 'small' => $this->t('Small (@size)', ['@size' => '16px']), + 'medium' => $this->t('Medium (@size)', ['@size' => '24px']), + 'large' => $this->t('Large (@size)', ['@size' => '32px']), + ], + '#required' => TRUE, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Container.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Container.php new file mode 100644 index 000000000..4fc1269f8 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Container.php @@ -0,0 +1,32 @@ + [], + // Flexbox. + 'flex' => 1, + // Conditional logic. + 'states' => [], + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/ContainerBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/ContainerBase.php new file mode 100644 index 000000000..1e68a2596 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/ContainerBase.php @@ -0,0 +1,90 @@ + '', + // General settings. + 'description' => '', + // Form display. + 'title_display' => '', + // Form validation. + 'required' => FALSE, + // Attributes. + 'attributes' => [], + ] + $this->getDefaultBaseProperties(); + } + + /** + * {@inheritdoc} + */ + public function isInput(array $element) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isContainer(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + protected function build($format, array &$element, $value, array $options = []) { + if (empty($value)) { + return []; + } + + return [ + '#theme' => 'webform_container_base_' . $format, + '#element' => $element, + '#value' => $value, + '#options' => $options, + ]; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Containers should never have values and therefore should never have + // a test value. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return []; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Date.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Date.php new file mode 100644 index 000000000..a82150a4e --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Date.php @@ -0,0 +1,86 @@ +getPattern(); + } + } + + return parent::getDefaultProperties() + [ + // Date settings. + 'date_date_format' => $date_format, + 'step' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + // Set the (input) type attribute to 'date' since #min and #max will + // override the default attributes. + // @see \Drupal\Core\Render\Element\Date::getInfo + $element['#attributes']['type'] = 'date'; + + // Issue #2817693 by danbohea: Min date option not working with jQuery UI + // datepicker. + $element['#attached']['library'][] = 'webform/webform.element.date'; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $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('Year-Month-Date (@date)', ['@date' => date($date_format)]), + ], + '#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__description' => $this->t('Enter date format using Date Input Format.'), + ]; + $form['date']['step'] = [ + '#type' => 'number', + '#title' => $this->t('Step'), + '#description' => $this->t('Specifies the legal number intervals.'), + '#min' => 1, + '#size' => 4, + '#weight' => 10, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/DateBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/DateBase.php new file mode 100644 index 000000000..7979789ff --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/DateBase.php @@ -0,0 +1,345 @@ + FALSE, + 'multiple__header_label' => '', + // Form validation. + 'min' => '', + 'max' => '', + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + // Don't used 'datetime_wrapper', instead use 'form_element' wrapper. + // Note: Below code must be executed before parent::prepare(). + // @see \Drupal\Core\Datetime\Element\Datelist + // @see \Drupal\webform\Plugin\WebformElement\DateTime + $element['#theme_wrappers'] = ['form_element']; + + // Must manually process #states. + // @see drupal_process_states(). + if (isset($element['#states'])) { + $element['#attached']['library'][] = 'core/drupal.states'; + $element['#wrapper_attributes']['data-drupal-states'] = Json::encode($element['#states']); + } + + parent::prepare($element, $webform_submission); + + // Parse #default_value date input format. + $this->parseInputFormat($element, '#default_value'); + + // Parse #min and #max date input format. + $this->parseInputFormat($element, '#min'); + $this->parseInputFormat($element, '#max'); + + // Override min/max attributes. + if (!empty($element['#min'])) { + $element['#attributes']['min'] = $element['#min']; + } + if (!empty($element['#max'])) { + $element['#attributes']['max'] = $element['#max']; + } + + $element['#element_validate'] = array_merge([[get_class($this), 'preValidateDate']], $element['#element_validate']); + $element['#element_validate'][] = [get_class($this), 'validateDate']; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (in_array($element['#type'], ['datelist', 'datetime'])) { + if (!empty($element['#default_value'])) { + if (is_array($element['#default_value'])) { + foreach ($element['#default_value'] as $key => $value) { + $element['#default_value'][$key] = ($value) ? DrupalDateTime::createFromTimestamp(strtotime($value)) : NULL; + } + } + elseif (is_string($element['#default_value'])) { + $element['#default_value'] = ($element['#default_value']) ? DrupalDateTime::createFromTimestamp(strtotime($element['#default_value'])) : NULL; + } + } + } + else { + parent::setDefaultValue($element); + } + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + $timestamp = strtotime($value); + if (empty($timestamp)) { + return $value; + } + + $format = $this->getItemFormat($element) ?: 'html_' . $this->getDateType($element); + if (DateFormat::load($format)) { + return \Drupal::service('date.formatter')->format($timestamp, $format); + } + else { + return date($format, $timestamp); + } + } + + /** + * {@inheritdoc} + */ + public function getItemFormat(array $element) { + if (isset($element['#format'])) { + return $element['#format']; + } + else { + return parent::getItemFormat($element); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'fallback'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + $formats = parent::getItemFormats(); + $date_formats = DateFormat::loadMultiple(); + foreach ($date_formats as $date_format) { + $formats[$date_format->id()] = $date_format->label(); + } + return $formats; + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $export_options) { + $element['#format'] = ($this->getDateType($element) === 'datetime') ? 'Y-m-d H:i:s' : 'Y-m-d'; + return [$this->formatText($element, $value, $export_options)]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Append supported date input format to #default_value description. + $form['element']['default_value']['#description'] .= '
          ' . $this->t('Accepts any date in any GNU Date Input Format. Strings such as today, +2 months, and Dec 9 2004 are all valid.'); + + // 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__description'] = $this->t('A user-defined date format. See the PHP manual for available options.'); + + $form['date'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Date settings'), + ]; + + $form['date']['min'] = [ + '#type' => 'textfield', + '#title' => $this->t('Min'), + '#description' => $this->t('Specifies the minimum date.') . '
          ' . $this->t('Accepts any date in any GNU Date Input Format. Strings such as today, +2 months, and Dec 9 2004 are all valid.'), + '#weight' => 10, + ]; + $form['date']['max'] = [ + '#type' => 'textfield', + '#title' => $this->t('Max'), + '#description' => $this->t('Specifies the maximum date.') . '
          ' . $this->t('Accepts any date in any GNU Date Input Format. Strings such as today, +2 months, and Dec 9 2004 are all valid.'), + '#weight' => 10, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + $properties = $this->getConfigurationFormProperties($form, $form_state); + + // Validate #default_value GNU Date Input Format. + if (isset($properties['#default_value']) && strtotime($properties['#default_value']) === FALSE) { + $this->setInputFormatError($form['properties']['element']['default_value'], $form_state); + } + + // Validate #min and #max GNU Date Input Format. + $input_formats = ['min', 'max']; + foreach ($input_formats as $input_format) { + if (!empty($properties["#$input_format"]) && strtotime($properties["#$input_format"]) === FALSE) { + $this->setInputFormatError($form['properties']['date'][$input_format], $form_state); + } + } + + parent::validateConfigurationForm($form, $form_state); + } + + /** + * Get the type of date/time element. + * + * @param array $element + * An element. + * + * @return string + * The type of date/time element which be either a 'date' or 'datetime'. + */ + protected function getDateType(array $element) { + switch ($element['#type']) { + case 'datelist': + return (isset($element['#date_part_order']) && !in_array('hour', $element['#date_part_order'])) ? 'date' : 'datetime'; + + case 'datetime': + return 'datetime'; + + case 'date': + default: + return 'date'; + } + } + + /** + * Parse GNU Date Input Format. + * + * @param array $element + * An element. + * @param string $property + * The element's date property. + */ + protected function parseInputFormat(array &$element, $property) { + if (!isset($element[$property])) { + return; + } + elseif (is_array($element[$property])) { + foreach ($element[$property] as $key => $value) { + $timestamp = strtotime($value); + $element[$property][$key] = ($timestamp) ? \Drupal::service('date.formatter')->format($timestamp, 'html_' . $this->getDateType($element)) : NULL; + } + } + else { + $timestamp = strtotime($element[$property]); + $element[$property] = ($timestamp) ? \Drupal::service('date.formatter')->format($timestamp, 'html_' . $this->getDateType($element)) : NULL; + } + } + + /** + * Set GNU input format error. + * + * @param array $element + * The property element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function setInputFormatError(array $element, FormStateInterface $form_state) { + $t_args = [ + '@title' => $element['#title'] ?: $element['#key'], + ]; + $form_state->setError($element, $this->t('The @title could not be interpreted in GNU Date Input Format.', $t_args)); + } + + /** + * Webform element pre validation handler for Date elements. + */ + public static function preValidateDate(&$element, FormStateInterface $form_state, &$complete_form) { + // ISSUE: + // When datelist is nested inside a webform_multiple element the $form_state + // value is not being properly set. + // + // WORKAROUND: + // Set the $form_state datelist value using $element['#value']. + // @todo: Possible move this validation logic to webform_multiple. + if (!empty($element['#multiple'])) { + $values = $form_state->getValues(); + NestedArray::setValue($values, $element['#parents'], $element['#value']); + $form_state->setValues($values); + } + } + + /** + * Webform element validation handler for date elements. + * + * Note that #required is validated by _form_validate() already. + * + * @see \Drupal\Core\Render\Element\Number::validateNumber + */ + public static function validateDate(&$element, FormStateInterface $form_state, &$complete_form) { + $value = $element['#value']; + + // Convert DrupalDateTime array and object to ISO datetime. + if (is_array($value)) { + /** @var \Drupal\Core\Datetime\DrupalDateTime $datetime */ + if ($datetime = $value['object']) { + $value = $datetime->format('c'); + } + else { + $value = ''; + } + $form_state->setValueForElement($element, $value); + } + + if ($value === '') { + return; + } + + $name = empty($element['#title']) ? $element['#parents'][0] : $element['#title']; + + // Ensure the input is valid date. + // @see http://stackoverflow.com/questions/10691949/check-if-variable-is-a-valid-date-with-php + $date = date_parse($value); + if ($date["error_count"] || !checkdate($date["month"], $date["day"], $date["year"])) { + $form_state->setError($element, t('%name must be a valid date.', ['%name' => $name])); + } + + $time = strtotime($value); + $date_date_format = (!empty($element['#date_date_format'])) ? $element['#date_date_format'] : DateFormat::load('html_date')->getPattern(); + + // Ensure that the input is greater than the #min property, if set. + if (isset($element['#min'])) { + $min = strtotime($element['#min']); + if ($time < $min) { + $form_state->setError($element, t('%name must be on or after %min.', [ + '%name' => $name, + '%min' => date($date_date_format, $min), + ])); + } + } + + // Ensure that the input is less than the #max property, if set. + if (isset($element['#max'])) { + $max = strtotime($element['#max']); + if ($time > $max) { + $form_state->setError($element, t('%name must be on or before %max.', [ + '%name' => $name, + '%max' => date($date_date_format, $max), + ])); + } + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/DateList.php b/web/modules/contrib/webform/src/Plugin/WebformElement/DateList.php new file mode 100644 index 000000000..a945c9c53 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/DateList.php @@ -0,0 +1,143 @@ + [ + 'year', + 'month', + 'day', + 'hour', + 'minute', + ], + 'date_text_parts' => [ + 'year', + ], + 'date_year_range' => '1900:2050', + 'date_increment' => 1, + ]; + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $date_parts = (isset($element['#date_part_order'])) ? $element['#date_part_order'] : ['year', 'month', 'day', 'hour', 'minute']; + + $t_args = ['@title' => $this->getAdminLabel($element)]; + $selectors = [ + 'day' => (string) $this->t('@title days', $t_args), + 'month' => (string) $this->t('@title months', $t_args), + 'year' => (string) $this->t('@title years', $t_args), + 'hour' => (string) $this->t('@title hours', $t_args), + 'minute' => (string) $this->t('@title minutes', $t_args), + 'second' => (string) $this->t('@title seconds', $t_args), + 'ampm' => (string) $this->t('@title am/pm', $t_args), + ]; + + $selectors = array_intersect_key($selectors, array_combine($date_parts, $date_parts)); + foreach ($selectors as &$selector) { + $selector .= ' [' . $this->t('Select') . ']'; + } + return $selectors; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['date'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Date list settings'), + ]; + $form['date']['date_part_order_label'] = [ + '#type' => 'item', + '#title' => $this->t('Date part and order'), + '#description' => $this->t("Select the date parts and order that should be used in the element."), + '#access' => TRUE, + ]; + $form['date']['date_part_order'] = [ + '#type' => 'webform_tableselect_sort', + '#header' => ['part' => 'Date part'], + '#options' => [ + 'day' => ['part' => $this->t('Days')], + 'month' => ['part' => $this->t('Months')], + 'year' => ['part' => $this->t('Years')], + 'hour' => ['part' => $this->t('Hours')], + 'minute' => ['part' => $this->t('Minutes')], + 'second' => ['part' => $this->t('Seconds')], + 'ampm' => ['part' => $this->t('AM/PM')], + ], + ]; + $form['date']['date_text_parts'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Date text parts'), + '#description' => $this->t("Select date parts that should be presented as text fields instead of drop-down selectors."), + '#options' => [ + 'day' => $this->t('Days'), + 'month' => $this->t('Months'), + 'year' => $this->t('Years'), + 'hour' => $this->t('Hours'), + 'minute' => $this->t('Minutes'), + 'second' => $this->t('Seconds'), + ], + ]; + $form['date']['date_year_range'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date year range'), + '#description' => $this->t("A description of the range of years to allow, like '1900:2050', '-3:+3' or '2000:+3', where the first value describes the earliest year and the second the latest year in the range.") . ' ' . + $this->t('Use min/max validation to define a more specific date range.'), + ]; + $form['date']['date_increment'] = [ + '#type' => 'number', + '#title' => $this->t('Date increment'), + '#description' => $this->t('The increment to use for minutes and seconds'), + '#min' => 1, + '#size' => 4, + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::validateConfigurationForm($form, $form_state); + $values = $form_state->getValues(); + $values['date_part_order'] = array_values($values['date_part_order']); + $values['date_text_parts'] = array_values(array_filter($values['date_text_parts'])); + $form_state->setValues($values); + } + + /** + * {@inheritdoc} + */ + protected function setConfigurationFormDefaultValue(array &$form, array &$element_properties, array &$property_element, $property_name) { + if (in_array($property_name, ['date_text_parts', 'date_part_order'])) { + $element_properties[$property_name] = array_combine($element_properties[$property_name], $element_properties[$property_name]); + } + parent::setConfigurationFormDefaultValue($form, $element_properties, $property_element, $property_name); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/DateTime.php b/web/modules/contrib/webform/src/Plugin/WebformElement/DateTime.php new file mode 100644 index 000000000..9b13cf128 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/DateTime.php @@ -0,0 +1,265 @@ +getPattern(); + } + /** @var \Drupal\Core\Datetime\DateFormatInterface $time_format_entity */ + if ($time_format_entity = DateFormat::load('html_time')) { + $time_format = $time_format_entity->getPattern(); + } + } + + return parent::getDefaultProperties() + [ + // Date settings. + 'date_date_format' => $date_format, + 'date_date_element' => 'date', + 'date_time_format' => $time_format, + 'date_time_element' => 'time', + 'date_year_range' => '1900:2050', + 'date_increment' => 1, + 'date_timezone' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Must define a '#default_value' for Datetime element to prevent the + // below error. + // Notice: Undefined index: #default_value in Drupal\Core\Datetime\Element\Datetime::valueCallback(). + if (!isset($element['#default_value'])) { + $element['#default_value'] = NULL; + } + + // Issue #1838234 Add jQuery Timepicker for the Time element of the + // datetime field. + $element['#attached']['library'][] = 'webform/webform.element.time'; + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $t_args = ['@title' => $this->getAdminLabel($element)]; + return [ + 'date' => (string) $this->t('@title [Date]', $t_args), + 'time' => (string) $this->t('@title [Time]', $t_args), + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + // Date. + $form['date'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Date/time settings'), + '#description' => $this->t('Datetime element is designed to have sane defaults so any or all can be omitted.') . ' ' . + $this->t('Both the date and time components are configurable so they can be output as HTML5 datetime elements or not, as desired.'), + ]; + $form['date']['date_date_element'] = [ + '#type' => 'select', + '#title' => t('Date element'), + '#options' => [ + 'datetime' => $this->t('HTML datetime - Use the HTML5 datetime element type.'), + 'datetime-local' => $this->t('HTML datetime input (localized) - Use the HTML5 datetime-local element type.'), + 'date' => $this->t('HTML date input - Use the HTML5 date element type.'), + 'text' => $this->t('Text input - No HTML5 element, use a normal text field.'), + 'none' => $this->t('None - Do not display a date element'), + ], + ]; + $form['date']['date_date_element_datetime_warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('HTML5 datetime elements do not gracefully degrade in older browsers and will be displayed as a plain text field without a date or time picker.'), + '#access' => TRUE, + '#states' => [ + 'visible' => [ + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime']], + 'or', + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime-local']], + ], + ], + ]; + $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.'), + '#access' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="properties[date_date_element]"]' => ['value' => 'none'], + ], + ], + ]; + $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('Year-Month-Date (@date)', ['@date' => date($date_format)]), + ], + '#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__description' => $this->t('Enter date format using Date Input Format.'), + '#states' => [ + 'invisible' => [ + ':input[name="properties[date_date_element]"]' => ['value' => 'none'], + ], + ], + ]; + $form['date']['date_year_range'] = [ + '#type' => 'textfield', + '#title' => $this->t('Date year range'), + '#description' => $this->t("A description of the range of years to allow, like '1900:2050', '-3:+3' or '2000:+3', where the first value describes the earliest year and the second the latest year in the range.") . ' ' . + $this->t('A year in either position means that specific year.') . ' ' . + $this->t('A +/- value describes a dynamic value that is that many years earlier or later than the current year at the time the webform is displayed.') . ' ' . + $this->t("Used in jQueryUI (fallback) datepicker year range and HTML5 min/max date settings. Defaults to '1900:2050'.") . ' ' . + $this->t('Use min/max validation to define a more specific date range.'), + '#states' => [ + 'invisible' => [ + ':input[name="properties[date_date_element]"]' => ['value' => 'none'], + ], + ], + ]; + + // Time. + $form['date']['date_time_element'] = [ + '#type' => 'select', + '#title' => t('Time element'), + '#options' => [ + 'time' => $this->t('HTML time input - Use a HTML5 time element type.'), + 'text' => $this->t('Text input - No HTML5 element, use a normal text field.'), + 'none' => $this->t('None - Do not display a time element.'), + ], + '#states' => [ + 'invisible' => [ + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime']], + 'or', + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime-local']], + ], + ], + ]; + $form['date']['date_time_format'] = [ + '#type' => 'webform_select_other', + '#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' => [ + 'g:i A' => $this->t('12 hour (@time)', ['@time' => date('g:i A')]), + 'g:i:s A' => $this->t('12 hour with seconds (@time)', ['@time' => date('g:i:s A')]), + 'H:i' => $this->t('24 hour (@time)', ['@time' => date('H:i')]), + 'H:i:s' => $this->t('24 hour with seconds (@time)', ['@time' => date('H:i:s')]), + ], + '#other__option_label' => $this->t('Custom...'), + '#other__placeholder' => $this->t('Custom time format...'), + '#other__description' => $this->t('Enter time format using Time Input Format.'), + '#states' => [ + 'invisible' => [ + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime']], + 'or', + [':input[name="properties[date_date_element]"]' => ['value' => 'datetime-local']], + 'or', + [':input[name="properties[date_time_element]"]' => ['value' => 'none']], + ], + ], + ]; + $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, + '#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']], + ], + ], + ]; + $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().'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getConfigurationFormProperties(array &$form, FormStateInterface $form_state) { + $properties = parent::getConfigurationFormProperties($form, $form_state); + // Remove hidden date properties. + if (isset($properties['#date_date_element'])) { + switch ($properties['#date_date_element']) { + case 'datetime': + case 'datetime-local': + unset( + $properties['#date_time_element'], + $properties['#date_time_format'], + $properties['#date_increment'] + ); + break; + + case 'none': + unset( + $properties['#date_date_format'], + $properties['#date_year_range'] + ); + break; + } + } + + // Remove hidden date properties. + if (isset($properties['#date_time_element']) && $properties['#date_time_element'] == 'none') { + unset( + $properties['#date_time_format'], + $properties['date_increment'] + ); + } + + return $properties; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Details.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Details.php new file mode 100644 index 000000000..82cc9bd2e --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Details.php @@ -0,0 +1,50 @@ + FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + if (isset($element['#webform_key'])) { + $element['#attributes']['data-webform-key'] = $element['#webform_key']; + } + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + $title = $this->getAdminLabel($element); + $name = $element['#webform_key']; + return ["details[data-webform-key=\"$name\"]" => $title . ' [' . $this->getPluginLabel() . ']']; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Email.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Email.php new file mode 100644 index 000000000..0a0464610 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Email.php @@ -0,0 +1,73 @@ + FALSE, + 'multiple__header_label' => '', + 'size' => '', + 'minlength' => '', + 'maxlength' => '', + 'placeholder' => '', + 'autocomplete' => 'on', + ]; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'link': + return [ + '#type' => 'link', + '#title' => $value, + '#url' => \Drupal::pathValidator()->getUrlIfValid('mailto:' . $value), + ]; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'link'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'link' => $this->t('Link'), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/EntityAutocomplete.php b/web/modules/contrib/webform/src/Plugin/WebformElement/EntityAutocomplete.php new file mode 100644 index 000000000..f9d5102c3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/EntityAutocomplete.php @@ -0,0 +1,136 @@ + FALSE, + 'multiple__header_label' => '', + // Entity reference settings. + 'target_type' => '', + 'selection_handler' => 'default', + 'selection_settings' => [], + 'tags' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (isset($element['#default_value']) && (!empty($element['#default_value']) || $element['#default_value'] === 0)) { + if ($this->hasMultipleValues($element)) { + $element['#default_value'] = $this->getTargetEntities($element, $element['#default_value']); + } + else { + $element['#default_value'] = $this->getTargetEntity($element, $element['#default_value']); + } + } + else { + $element['#default_value'] = NULL; + } + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleWrapper() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleValues(array $element) { + if ($this->hasProperty('tags') && isset($element['#tags'])) { + return $element['#tags']; + } + else { + return parent::hasMultipleValues($element); + } + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + // If #tags (aka multiple entities) use #after_builder to set #element_value + // which must be executed after + // \Drupal\Core\Entity\Element\EntityAutocomplete::validateEntityAutocomplete(). + if ($this->hasMultipleValues($element)) { + $element['#after_build'][] = [get_class($this), 'afterBuildEntityAutocomplete']; + } + + // If selection handler include auto_create when need to also set it for + // the $element. + // @see \Drupal\Core\Entity\Element\EntityAutocomplete::validateEntityAutocomplete + if (!empty($element['#selection_settings']['auto_create_bundle'])) { + $element['#autocreate']['bundle'] = $element['#selection_settings']['auto_create_bundle']; + } + } + + /** + * Form API callback. After build set the #element_validate handler. + */ + public static function afterBuildEntityAutocomplete(array $element, FormStateInterface $form_state) { + $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); + if (is_array($value) && !empty($value)) { + $entity_ids = []; + foreach ($value as $item) { + if (isset($item['target_id'])) { + $entity_ids[] = $item['target_id']; + } + elseif (isset($item['entity'])) { + // If #auto_create is set then we need to save the entity and get + // the new entity's id. + // @todo Decide what level of access controls are needed to allow + // users to create entities. + $entity = $item['entity']; + $entity->save(); + $entity_ids[] = $entity->id(); + } + } + $form_state->setValueForElement($element, $entity_ids); + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Fieldset.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Fieldset.php new file mode 100644 index 000000000..89095eab7 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Fieldset.php @@ -0,0 +1,28 @@ + FALSE, + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Hidden.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Hidden.php new file mode 100644 index 000000000..0162d642a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Hidden.php @@ -0,0 +1,28 @@ + '', + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Item.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Item.php new file mode 100644 index 000000000..3a034c14b --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Item.php @@ -0,0 +1,55 @@ + '', + // General settings. + 'description' => '', + // Form display. + 'title_display' => '', + 'description_display' => '', + 'field_prefix' => '', + 'field_suffix' => '', + // Form validation. + 'required' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + $element['#element_validate'][] = [get_class($this), 'validateItem']; + } + + /** + * Form API callback. Removes ignored element for $form_state values. + */ + public static function validateItem(array &$element, FormStateInterface $form_state, array &$completed_form) { + $name = $element['#name']; + $form_state->unsetValue($name); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Label.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Label.php new file mode 100644 index 000000000..dfa67fdd1 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Label.php @@ -0,0 +1,19 @@ +getLanguage($value); + $format = $this->getItemFormat($element); + switch ($format) { + case 'langcode': + return $language->getId(); + + case 'language': + return $language->getName(); + + case 'text': + default: + // Use `sprintf` instead of FormattableMarkup because we really just + // want a basic string. + return sprintf('%s (%s)', $language->getName(), $language->getId()); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'text'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'text' => $this->t('Text'), + 'langcode' => $this->t('Langcode'), + 'language' => $this->t('Language'), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/MachineName.php b/web/modules/contrib/webform/src/Plugin/WebformElement/MachineName.php new file mode 100644 index 000000000..cef760ff3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/MachineName.php @@ -0,0 +1,51 @@ + FALSE, + 'multiple__header_label' => '', + // Number settings. + 'min' => '', + 'max' => '', + 'step' => '', + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/NumericBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/NumericBase.php new file mode 100644 index 000000000..469fbf62f --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/NumericBase.php @@ -0,0 +1,85 @@ + '', + 'minlength' => '', + 'maxlength' => '', + 'placeholder' => '', + 'autocomplete' => 'on', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + if ($this->hasProperty('step') && !isset($element['#step'])) { + $element['#step'] = 'any'; + } + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + $element += ['#min' => 1, '#max' => 10]; + return [ + $element['#min'], + floor((($element['#max'] - $element['#min']) / 2) + $element['#min']), + $element['#max'], + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['number'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Number settings'), + ]; + $form['number']['min'] = [ + '#type' => 'number', + '#title' => $this->t('Min'), + '#description' => $this->t('Specifies the minimum value.'), + '#step' => 'any', + '#size' => 4, + ]; + $form['number']['max'] = [ + '#type' => 'number', + '#title' => $this->t('Max'), + '#description' => $this->t('Specifies the maximum value.'), + '#step' => 'any', + '#size' => 4, + ]; + $form['number']['step'] = [ + '#type' => 'number', + '#title' => $this->t('Steps'), + '#description' => $this->t('Specifies the legal number intervals. Leave blank to support any number interval.'), + '#step' => 'any', + '#size' => 4, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/OptionsBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/OptionsBase.php new file mode 100644 index 000000000..e90a0c178 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/OptionsBase.php @@ -0,0 +1,518 @@ +getPluginId())) { + unset($default_properties['wrapper_attributes']); + } + + return $default_properties + [ + // Options settings. + 'options' => [], + 'options_randomize' => FALSE, + ]; + } + + /** + * Get option (option) properties. + * + * @return array + * An associative array containing other (option) properties. + */ + public function getOtherProperties() { + return [ + 'other__option_label' => $this->t('Other...'), + 'other__type' => 'textfield', + 'other__title' => '', + 'other__placeholder' => $this->t('Enter other...'), + 'other__description' => '', + // Text field or textarea. + 'other__size' => '', + 'other__maxlength' => '', + 'other__field_prefix' => '', + 'other__field_suffix' => '', + // Textarea. + 'other__rows' => '', + // Number. + 'other__min' => '', + 'other__max' => '', + 'other__step' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['options', 'empty_option', 'option_label']); + } + + /** + * {@inheritdoc} + */ + public function getRelatedTypes(array $element) { + $related_types = parent::getRelatedTypes($element); + // Remove entity reference elements. + $elements = $this->elementManager->getInstances(); + foreach ($related_types as $type => $related_type) { + $element_instance = $elements[$type]; + if ($element_instance instanceof WebformEntityReferenceInterface) { + unset($related_types[$type]); + } + } + return $related_types; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Randomize options. + if (isset($element['#options']) && !empty($element['#options_randomize'])) { + $element['#options'] = WebformArrayHelper::shuffle($element['#options']); + } + + $is_wrapper_fieldset = in_array($element['#type'], ['checkboxes', 'radios']); + if ($is_wrapper_fieldset) { + // Issue #2396145: Option #description_display for webform element fieldset + // is not changing anything. + // @see core/modules/system/templates/fieldset.html.twig + $is_description_display = (isset($element['#description_display'])) ? TRUE : FALSE; + $has_description = (!empty($element['#description'])) ? TRUE : FALSE; + if ($is_description_display && $has_description) { + switch ($element['#description_display']) { + case 'before': + $element += ['#field_prefix' => '']; + $element['#field_prefix'] = '
          ' . $element['#description'] . '
          ' . $element['#field_prefix']; + unset($element['#description']); + break; + + case 'invisible': + $element += ['#field_suffix' => '']; + $element['#field_suffix'] .= '
          ' . $element['#description'] . '
          '; + unset($element['#description']); + break; + } + } + } + } + + /** + * {@inheritdoc} + */ + public function hasMultipleWrapper() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (!isset($element['#default_value'])) { + return; + } + + // Compensate for #default_value not being an array, for elements that + // allow for multiple #options to be selected/checked. + if ($this->hasMultipleValues($element) && !is_array($element['#default_value'])) { + $element['#default_value'] = [$element['#default_value']]; + } + } + + /** + * {@inheritdoc} + */ + protected function formatTextItem(array &$element, $value, array $options = []) { + $format = $this->getItemFormat($element); + if ($format == 'value' && isset($element['#options'])) { + $flattened_options = OptGroup::flattenOptions($element['#options']); + $value = WebformOptionsHelper::getOptionText($value, $flattened_options); + } + return parent::formatTextItem($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function getItemsDefaultFormat() { + return 'comma'; + } + + /** + * {@inheritdoc} + */ + public function getTableColumn(array $element) { + $key = $element['#webform_key']; + $columns = parent::getTableColumn($element); + $columns['element__' . $key]['sort'] = !$this->hasMultipleValues($element); + return $columns; + } + + /** + * {@inheritdoc} + */ + public function getExportDefaultOptions() { + return [ + 'options_format' => 'compact', + 'options_item_format' => 'label', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportOptionsForm(array &$form, FormStateInterface $form_state, array $export_options) { + parent::buildExportOptionsForm($form, $form_state, $export_options); + if (isset($form['options'])) { + return; + } + + $form['options'] = [ + '#type' => 'details', + '#title' => $this->t('Select menu, radio buttons, and checkboxes options'), + '#open' => TRUE, + '#weight' => -10, + ]; + $form['options']['options_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Options format'), + '#options' => [ + 'compact' => $this->t('Compact; with the option values delimited by commas in one column.') . '
          ' . $this->t('Compact options are more suitable for importing data into other systems.') . '
          ', + 'separate' => $this->t('Separate; with each possible option value in its own column.') . '
          ' . $this->t('Separate options are more suitable for building reports, graphs, and statistics in a spreadsheet application. Ranking will be included for sortable option elements.') . '
          ', + ], + '#default_value' => $export_options['options_format'], + ]; + $form['options']['options_item_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Options item format'), + '#options' => [ + 'label' => $this->t('Option labels, the human-readable value (label)'), + 'key' => $this->t('Option values, the raw value stored in the database (key)'), + ], + '#default_value' => $export_options['options_item_format'], + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportHeader(array $element, array $options) { + if ($options['options_format'] == 'separate' && isset($element['#options'])) { + $header = []; + foreach ($element['#options'] as $option_value => $option_text) { + // Note: If $option_text is an array (typically a tableselect row) + // always use $option_value. + $title = ($options['options_item_format'] == 'key' || is_array($option_text)) ? $option_value : $option_text; + $header[] = $title; + } + return $this->prefixExportHeader($header, $element, $options); + } + else { + return parent::buildExportHeader($element, $options); + } + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $export_options) { + $element_options = $element['#options']; + + if ($export_options['options_format'] == 'separate') { + $record = []; + // Combine the values so that isset can be used instead of in_array(). + // http://stackoverflow.com/questions/13483219/what-is-faster-in-array-or-isset + $deltas = FALSE; + if (is_array($value)) { + $value = array_combine($value, $value); + $deltas = ($this->exportDelta) ? array_flip(array_values($value)) : FALSE; + } + // Separate multiple values (ie options). + foreach ($element_options as $option_value => $option_text) { + if ((is_array($value) && isset($value[$option_value])) || ($value == $option_value)) { + $record[] = ($deltas) ? ($deltas[$option_value] + 1) : 'X'; + } + else { + $record[] = ''; + } + } + return $record; + } + else { + if ($export_options['options_item_format'] == 'key') { + $element['#format'] = 'raw'; + } + return parent::buildExportRecord($element, $value, $export_options); + } + } + + /** + * 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); + // 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); + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $plugin_id = $this->getPluginId(); + if (preg_match('/webform_(select|radios|checkboxes|buttons)_other$/', $plugin_id, $match)) { + list($type) = explode(' ', $this->getPluginLabel()); + $title = $this->getAdminLabel($element); + $name = $match[1]; + + $inputs = []; + $inputs[$name] = $title . ' [' . $type . ']'; + $inputs['other'] = $title . ' [' . $this->t('Text field') . ']'; + return $inputs; + } + else { + return []; + } + } + + /** + * {@inheritdoc} + * + * @see \Drupal\webform\Entity\Webform::getElementsSelectorOptions + */ + public function getElementSelectorOptions(array $element) { + if ($this->hasMultipleValues($element) && $this->hasMultipleWrapper()) { + return []; + } + + $plugin_id = $this->getPluginId(); + $title = $this->getAdminLabel($element) . ' [' . $this->getPluginLabel() . ']'; + $name = $element['#webform_key']; + + if ($inputs = $this->getElementSelectorInputsOptions($element)) { + $selectors = []; + foreach ($inputs as $input_name => $input_title) { + $multiple = ($this->hasMultipleValues($element) && $input_name === 'select') ? '[]' : ''; + $selectors[":input[name=\"{$name}[{$input_name}]$multiple\"]"] = $input_title; + } + return [$title => $selectors]; + } + else { + $multiple = ($this->hasMultipleValues($element) && strpos($plugin_id, 'select') !== FALSE) ? '[]' : ''; + return [":input[name=\"$name$multiple\"]" => $title]; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['element']['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()), + ':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. (Issue #2836374)', $t_args); + } + // Options. + $form['options'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Element options'), + '#open' => TRUE, + ]; + $form['options']['options'] = [ + '#type' => 'webform_element_options', + '#title' => $this->t('Options'), + '#required' => TRUE, + ]; + $form['options']['options_display'] = [ + '#title' => $this->t('Options display'), + '#type' => 'select', + '#options' => [ + 'one_column' => $this->t('One column'), + 'two_columns' => $this->t('Two columns'), + 'three_columns' => $this->t('Three columns'), + 'side_by_side' => $this->t('Side by side'), + ], + ]; + $form['options']['empty_option'] = [ + '#type' => 'textfield', + '#title' => $this->t('Empty option label'), + '#description' => $this->t('The label to show for the initial option denoting no selection in a select element.'), + ]; + $form['options']['empty_value'] = [ + '#type' => 'textfield', + '#title' => $this->t('Empty option value'), + '#description' => $this->t('The value for the initial option denoting no selection in a select element, which is used to determine whether the user submitted a value or not.'), + ]; + $form['options']['options_randomize'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Randomize options'), + '#description' => $this->t('Randomizes the order of the options when they are displayed in the webform.'), + '#return_value' => TRUE, + ]; + + // Other. + $states_textfield_or_number = [ + 'visible' => [ + [':input[name="properties[other__type]"]' => ['value' => 'textfield']], + 'or', + [':input[name="properties[other__type]"]' => ['value' => 'number']], + ], + ]; + $states_textarea = [ + 'visible' => [ + ':input[name="properties[other__type]"]' => ['value' => 'textarea'], + ], + ]; + $states_number = [ + 'visible' => [ + ':input[name="properties[other__type]"]' => ['value' => 'number'], + ], + ]; + $form['options_other'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Other option settings'), + ]; + $form['options_other']['other__type'] = [ + '#type' => 'select', + '#title' => $this->t('Other type'), + '#options' => [ + 'textfield' => $this->t('Text field'), + 'textarea' => $this->t('Textarea'), + 'number' => $this->t('Number'), + ], + ]; + $form['options_other']['other__option_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other option label'), + ]; + $form['options_other']['other__title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other title'), + ]; + $form['options_other']['other__placeholder'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other placeholder'), + ]; + $form['options_other']['other__description'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other description'), + ]; + $form['options_other']['other__size'] = [ + '#type' => 'number', + '#title' => $this->t('Other size'), + '#description' => $this->t('Leaving blank will use the default size.'), + '#min' => 1, + '#size' => 4, + '#states' => $states_textfield_or_number, + ]; + $form['options_other']['other__maxlength'] = [ + '#type' => 'number', + '#title' => $this->t('Other maxlength'), + '#description' => $this->t('Leaving blank will use the default maxlength.'), + '#min' => 1, + '#size' => 4, + '#states' => $states_textfield_or_number, + ]; + $form['options_other']['other__field_prefix'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other field prefix'), + '#description' => $this->t('Text or code that is placed directly in front of the input. This can be used to prefix an input with a constant string. Examples: $, #, -.'), + '#size' => 10, + '#states' => $states_textfield_or_number, + ]; + $form['options_other']['other__field_suffix'] = [ + '#type' => 'textfield', + '#title' => $this->t('Other field suffix'), + '#description' => $this->t('Text or code that is placed directly after the input. This can be used to add a unit to an input. Examples: lb, kg, %.'), + '#size' => 10, + '#states' => $states_textfield_or_number, + ]; + $form['options_other']['other__rows'] = [ + '#type' => 'number', + '#title' => $this->t('Other rows'), + '#description' => $this->t('Leaving blank will use the default rows.'), + '#min' => 1, + '#size' => 4, + '#states' => $states_textarea, + ]; + $form['options_other']['other__min'] = [ + '#type' => 'number', + '#title' => $this->t('Other min'), + '#description' => $this->t('Specifies the minimum value.'), + '#step' => 'any', + '#size' => 4, + '#states' => $states_number, + ]; + $form['options_other']['other__max'] = [ + '#type' => 'number', + '#title' => $this->t('Other max'), + '#description' => $this->t('Specifies the maximum value.'), + '#step' => 'any', + '#size' => 4, + '#states' => $states_number, + ]; + $form['options_other']['other__step'] = [ + '#type' => 'number', + '#title' => $this->t('Other steps'), + '#description' => $this->t('Specifies the legal number intervals. Leave blank to support any number interval.'), + '#step' => 'any', + '#size' => 4, + '#states' => $states_number, + ]; + + // Add hide/show #format_items based on #multiple. + if ($this->supportsMultipleValues() && $this->hasProperty('multiple')) { + $form['display']['format_items']['#states'] = [ + 'visible' => [ + [':input[name="properties[multiple]"]' => ['checked' => TRUE]], + ], + ]; + } + + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Password.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Password.php new file mode 100644 index 000000000..70fb3731c --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Password.php @@ -0,0 +1,55 @@ +getItemFormat($element); + switch ($format) { + case 'obscured': + return '********'; + + default: + return parent::formatTextItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'obscured'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'obscured' => $this->t('Obscured'), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/PasswordConfirm.php b/web/modules/contrib/webform/src/Plugin/WebformElement/PasswordConfirm.php new file mode 100644 index 000000000..501d295b0 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/PasswordConfirm.php @@ -0,0 +1,68 @@ + $this->getAdminLabel($element) . ' 1 [' . $this->t('Password') . ']', + 'pass2' => $this->getAdminLabel($element) . ' 2 [' . $this->t('Password') . ']', + ]; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (isset($element['#default_value'])) { + $element['#default_value'] = [ + 'pass1' => $element['#default_value'], + 'pass2' => $element['#default_value'], + ]; + } + } + + /** + * 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']); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/ProcessedText.php b/web/modules/contrib/webform/src/Plugin/WebformElement/ProcessedText.php new file mode 100644 index 000000000..267ee5fec --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/ProcessedText.php @@ -0,0 +1,106 @@ + '', + 'format' => (function_exists('filter_default_format')) ? filter_default_format(\Drupal::currentUser()) : '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['text']); + } + + /** + * {@inheritdoc} + */ + public function buildText(array &$element, $value, array $options = []) { + // Copy to element so that we can render it without altering the actual + // $element. + $render_element = $element; + $html = (string) \Drupal::service('renderer')->renderPlain($render_element); + $element['#markup'] = MailFormatHelper::htmlToText($html); + + // Must remove #type, #text, and #format. + unset($element['#type'], $element['#text'], $element['#format']); + + return parent::buildText($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + // Issue #2741877 Nested modals don't work: when using CKEditor in a + // modal, then clicking the image button opens another modal, + // 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 Issue #2741877: Nested modals don\'t work.'), 'warning'); + } + $form = parent::form($form, $form_state); + + // Remove 'Submission display' since the 'format' property is handled by + // the text format element. + unset($form['display']); + + $form['markup']['#title'] = $this->t('Processed text settings'); + $form['markup']['text'] = [ + '#type' => 'text_format', + '#format' => '', + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function setConfigurationFormDefaultValue(array &$form, array &$element_properties, array &$property_element, $property_name) { + // Apply element.format to the text (text_format) element and unset it. + if ($property_name == 'text') { + $property_element['#format'] = $element_properties['format']; + unset($element_properties['format']); + } + + parent::setConfigurationFormDefaultValue($form, $element_properties, $property_element, $property_name); + } + + /** + * {@inheritdoc} + */ + protected function getConfigurationFormProperty(array &$properties, $property_name, $property_value, array $element) { + if ($property_name == 'text') { + $properties['text'] = $property_value['value']; + $properties['format'] = $property_value['format']; + } + else { + parent::getConfigurationFormProperty($properties, $property_name, $property_value, $element); + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Radios.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Radios.php new file mode 100644 index 000000000..07c7194fa --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Radios.php @@ -0,0 +1,43 @@ + 'one_column', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Issue #2856795: If radio buttons are required but not filled form is + // nevertheless submitted. + // Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. + $element['#attached']['library'][] = 'webform/webform.element.radios'; + } + +} + diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Range.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Range.php new file mode 100644 index 000000000..66635e73f --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Range.php @@ -0,0 +1,99 @@ + '', + 'max' => '', + 'step' => '', + // Range settings. + 'range__output' => FALSE, + 'range__output_prefix' => '', + 'range__output_suffix' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + if (!empty($element['#range__output'])) { + $element['#attributes']['data-range-output'] = 'true'; + $element['#attributes']['class'][] = 'js-form-range-output'; + $element['#attributes']['class'][] = 'form-range-output'; + $element['#wrapper_attributes']['class'][] = 'js-form-type-range-output'; + $element['#wrapper_attributes']['class'][] = 'form-type-range-output'; + if (!empty($element['#range__output_prefix'])) { + $element['#attributes']['data-range-output-prefix'] = $element['#range__output_prefix']; + } + if (!empty($element['#range__output_suffix'])) { + $element['#attributes']['data-range-output-suffix'] = $element['#range__output_suffix']; + } + + $element['#attached']['library'][] = 'webform/webform.element.range'; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['number']['#title'] = $this->t('Range settings'); + + $form['number']['range__output'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Output the range's value."), + '#return_type' => TRUE, + ]; + $form['number']['range__output_prefix'] = [ + '#type' => 'textfield', + '#title' => $this->t('Range output prefix'), + '#description' => $this->t('Text or code that is placed directly in front of the output. This can be used to prefix an output with a constant string. Examples: $, #, -.'), + '#size' => 10, + '#states' => [ + 'visible' => [ + ':input[name="properties[range__output]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['number']['range__output_suffix'] = [ + '#type' => 'textfield', + '#title' => $this->t('Range output suffix'), + '#description' => $this->t('Text or code that is placed directly after the output. This can be used to add a unit to an output. Examples: lb, kg, %.'), + '#size' => 10, + '#states' => [ + 'visible' => [ + ':input[name="properties[range__output]"]' => ['checked' => TRUE], + ], + ], + ]; + + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Search.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Search.php new file mode 100644 index 000000000..39c75b3c3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Search.php @@ -0,0 +1,18 @@ + FALSE, + 'multiple_error' => '', + 'empty_option' => '', + 'empty_value' => '', + 'select2' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + if (empty($element['#multiple'])) { + if (!isset($element['#empty_option'])) { + $element['#empty_option'] = empty($element['#required']) ? $this->t('- Select -') : $this->t('- None -'); + } + } + else { + if (!isset($element['#empty_option'])) { + $element['#empty_option'] = empty($element['#required']) ? $this->t('- None -') : NULL; + } + $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; + } + + parent::prepare($element, $webform_submission); + + // Add select2 library and classes. + if (!empty($element['#select2'])) { + $element['#attached']['library'][] = 'webform/webform.element.select2'; + $element['#attributes']['class'][] = 'js-webform-select2'; + $element['#attributes']['class'][] = 'webform-select2'; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['options']['select2'] = [ + '#title' => $this->t('Select2'), + '#type' => 'checkbox', + '#return_value' => TRUE, + '#description' => $this->t('Replace select element with jQuery Select2 box.', [':href' => 'https://select2.github.io/']), + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Table.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Table.php new file mode 100644 index 000000000..d71404f12 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Table.php @@ -0,0 +1,183 @@ + [], + 'empty' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['header']); + } + + /** + * {@inheritdoc} + */ + public function isInput(array $element) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isContainer(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Add .js-form.wrapper to fix #states handling. + $element['#attributes']['class'][] = 'js-form-wrapper'; + + // Disable #tree for table element. Webforms do not support the #tree + // property. + $element['#tree'] = FALSE; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'table'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return ['table']; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + // Containers should never have values and therefore should never have + // a test value. + return NULL; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + // Undo webform submission elements and convert rows back into a simple + // render array. + $rows = []; + foreach ($value as $row_key => $row_element) { + $element[$row_key] = []; + foreach ($row_element['#value'] as $column_key => $column_element) { + if (isset($column_element['#value'])) { + if (is_string($column_element['#value']) || $column_element['#value'] instanceof TranslatableMarkup) { + $value = ['#markup' => $column_element['#value']]; + } + else { + $value = $column_element['#value']; + } + } + elseif (isset($column_element['#markup'])) { + $value = ['#markup' => $column_element['#markup']]; + } + else { + $value = ''; + } + $rows[$row_key][$column_key] = ['data' => $value]; + } + } + return $rows + $element; + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + // Render the HTML table. + $build = $this->formatHtml($element, $value, $options); + $html = \Drupal::service('renderer')->renderPlain($build); + + // Convert table in pipe delimited plain text. + $html = preg_replace('#\s*\s*]*>\s*#', ' | ', $html); + $html = preg_replace('#\s*\s*]*>\s*#', ' | ', $html); + $html = preg_replace('#^\s+#m', '', $html); + $html = preg_replace('#\s+$#m', '', $html); + $html = preg_replace('#\n+#s', PHP_EOL, $html); + $html = strip_tags($html); + + // Remove blank links from text. + // From: http://stackoverflow.com/questions/709669/how-do-i-remove-blank-lines-from-text-in-php + $html = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", PHP_EOL, $html); + + // 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])); + $html = implode(PHP_EOL, $lines); + } + + return $html; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['table'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Table settings'), + ]; + $form['table']['header'] = [ + '#title' => $this->t('Header (YAML)'), + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + ]; + $form['table']['empty'] = [ + '#type' => 'textfield', + '#title' => $this->t('Empty text'), + '#description' => $this->t('Text to display when no rows are present.'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return []; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/TableSelect.php b/web/modules/contrib/webform/src/Plugin/WebformElement/TableSelect.php new file mode 100644 index 000000000..880b23529 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/TableSelect.php @@ -0,0 +1,48 @@ + TRUE, + 'multiple_error' => '', + // Table settings. + 'js_select' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return $this->getTableSelectElementSelectorOptions($element); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Telephone.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Telephone.php new file mode 100644 index 000000000..8e5d95557 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Telephone.php @@ -0,0 +1,120 @@ + FALSE, + 'international' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Add international library and classes. + if (!empty($element['#international'])) { + $element['#attached']['library'][] = 'webform/webform.element.telephone'; + $element['#attributes']['class'][] = 'js-webform-telephone-international'; + $element['#attributes']['class'][] = 'webform-webform-telephone-international'; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['telephone'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Telephone settings'), + ]; + $form['telephone']['international'] = [ + '#title' => $this->t('Enhance support for international phone numbers'), + '#type' => 'checkbox', + '#return_value' => TRUE, + '#description' => $this->t('Enhance the telephone element\'s international support using the jQuery International Telephone Input plugin.', [':href' => 'http://intl-tel-input.com/']), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $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('@tel', $t_args); + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'link'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'link' => $this->t('Link'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + if (empty($element['#international'])) { + return FALSE; + } + return [ + '+1 212-333-4444', + '+1 718-555-6666', + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/TextBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/TextBase.php new file mode 100644 index 000000000..7a1a46b3b --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/TextBase.php @@ -0,0 +1,197 @@ + '', + 'minlength' => '', + 'maxlength' => '', + 'placeholder' => '', + 'autocomplete' => 'on', + 'pattern' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Counter. + if (!empty($element['#counter_type']) && !empty($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']; + } + + $element['#attributes']['class'][] = 'js-webform-counter'; + $element['#attributes']['class'][] = 'webform-counter'; + $element['#attached']['library'][] = 'webform/webform.element.counter'; + + $element['#element_validate'][] = [get_class($this), 'validateCounter']; + } + + // Input mask. + if (!empty($element['#input_mask'])) { + // See if the element mask is JSON by looking for 'name':, else assume it + // is a mask pattern. + $input_mask = $element['#input_mask']; + if (preg_match("/^'[^']+'\s*:/", $input_mask)) { + $element['#attributes']['data-inputmask'] = $input_mask; + } + else { + $element['#attributes']['data-inputmask-mask'] = $input_mask; + } + + $element['#attributes']['class'][] = 'js-webform-element-mask'; + $element['#attached']['library'][] = 'webform/webform.element.inputmask'; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Input mask. + $form['form']['input_mask'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('Input masks'), + '#description' => $this->t('An inputmask 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__description' => $this->t('(9 = numeric; a = alphabetical; * = alphanumeric)'), + '#options' => [ + '' => '', + 'Basic' => [ + "'alias': 'currency'" => $this->t('Currency - @format', ['@format' => '$ 9.99']), + "'alias': 'mm/dd/yyyy'" => $this->t('Date - @format', ['@format' => 'mm/dd/yyyy']), + "'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'" => 'IP address - 255.255.255.255', + '[9-]AAA-999' => 'License plate - [9-]AAA-999', + "'alias': 'mac'" => 'MAC addresses - 99-99-99-99-99-99', + '999-99-9999' => 'SSN - 999-99-9999', + "'alias': 'vin'" => 'VIN (Vehicle identification number)', + ], + ], + ]; + + // Pattern. + $form['validation']['pattern'] = [ + '#type' => 'textfield', + '#title' => $this->t('Pattern'), + '#description' => $this->t('A regular expression that the element\'s value is checked against.', [':href' => 'http://www.w3schools.com/js/js_regexp.asp']), + ]; + + // Counter. + $form['validation']['counter_type'] = [ + '#type' => 'select', + '#title' => $this->t('Count'), + '#description' => $this->t('Limit entered value to a maximum number of characters or words.'), + '#options' => [ + '' => '', + 'character' => $this->t('Characters'), + 'word' => $this->t('Words'), + ], + ]; + $form['validation']['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'] = [ + '#type' => 'textfield', + '#title' => $this->t('Count message'), + '#description' => $this->t('Defaults to: %value', ['%value' => $this->t('X characters/word(s) left')]), + '#states' => [ + 'invisible' => [ + ':input[name="properties[counter_type]"]' => ['value' => ''], + ], + ], + ]; + + return $form; + } + + /** + * Form API callback. Validate (word/charcter) counter. + */ + public static function validateCounter(array &$element, FormStateInterface $form_state) { + $name = $element['#name']; + $value = $form_state->getValue($name); + $type = $element['#counter_type']; + $limit = $element['#counter_maximum']; + + // Validate character count. + if ($type == 'character' && Unicode::strlen($value) <= $limit) { + return; + } + // Validate word count. + elseif ($type == 'word' && str_word_count($value) <= $limit) { + return; + } + + // Display error. + $t_args = [ + '%name' => $name, + '@limit' => $limit, + '@type' => ($type == 'character') ? t('characters') : t('words'), + ]; + $form_state->setError($element, t('%name must be less than @limit @type.', $t_args)); + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::validateConfigurationForm($form, $form_state); + $properties = $this->getConfigurationFormProperties($form, $form_state); + + // Validate #pattern's regular expression. + // @see \Drupal\Core\Render\Element\FormElement::validatePattern + // @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) { + $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.')); + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/TextField.php b/web/modules/contrib/webform/src/Plugin/WebformElement/TextField.php new file mode 100644 index 000000000..45e81847a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/TextField.php @@ -0,0 +1,44 @@ + FALSE, + 'multiple__header_label' => '', + // Form display. + 'input_mask' => '', + // Form validation. + 'counter_type' => '', + 'counter_maximum' => '', + 'counter_message' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + $element['#maxlength'] = (!isset($element['#maxlength'])) ? 255 : $element['#maxlength']; + parent::prepare($element, $webform_submission); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/TextFormat.php b/web/modules/contrib/webform/src/Plugin/WebformElement/TextFormat.php new file mode 100644 index 000000000..5e04dec4a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/TextFormat.php @@ -0,0 +1,203 @@ + [], + 'hide_help' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function isInput(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + $element['#after_build'] = [[get_class($this), 'afterBuild']]; + $element['#attached']['library'][] = 'webform/webform.element.text_format'; + } + + /** + * Alter the 'text_format' element after it has been built. + * + * @param array $element + * An element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The element. + */ + public static function afterBuild(array $element, FormStateInterface $form_state) { + if (empty($element['format'])) { + return $element; + } + + // Hide tips. + if (!empty($element['#hide_help']) && isset($element['format']['help'])) { + $element['format']['help']['#attributes']['style'] = 'display: none'; + } + + // Hide filter format if the select menu and help is hidden. + if (!empty($element['#hide_help']) && + isset($element['format']['format']['#access']) && $element['format']['format']['#access'] === FALSE) { + // Can't hide the format via #access but we can use CSS. + $element['format']['#attributes']['style'] = 'display: none'; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (isset($element['#default_value']) && is_array($element['#default_value'])) { + if (isset($element['#default_value']['format'])) { + $element['#format'] = $element['#default_value']['format']; + } + if (isset($element['#default_value']['value'])) { + $element['#default_value'] = $element['#default_value']['value']; + } + } + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + $value = (isset($value['value'])) ? $value['value'] : $value; + $format = (isset($value['format'])) ? $value['format'] : $this->getItemFormat($element); + switch ($format) { + case 'raw': + return $value; + + case 'value': + default: + return check_markup($value, $format); + } + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + $format = (isset($value['format'])) ? $value['format'] : $this->getItemFormat($element); + switch ($format) { + case 'raw': + return $value; + + case 'value': + default: + $html = $this->formatHtml($element, $value); + // Convert any HTML to plain-text. + $html = MailFormatHelper::htmlToText($html); + // Wrap the mail body for sending. + $html = MailFormatHelper::wrapMail($html); + return $html; + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return (function_exists('filter_default_format')) ? filter_default_format() : parent::getItemDefaultFormat(); + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + $formats = parent::getItemFormats(); + $filters = (class_exists('\Drupal\filter\Entity\FilterFormat')) ? FilterFormat::loadMultiple() : []; + foreach ($filters as $filter) { + $formats[$filter->id()] = $filter->label(); + } + return $formats; + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $title = $this->getAdminLabel($element); + return [ + 'value' => $title . ' [' . t('Textarea') . ']', + 'format' => $title . ' [' . t('Select') . ']', + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $filters = FilterFormat::loadMultiple(); + $options = []; + foreach ($filters as $filter) { + $options[$filter->id()] = $filter->label(); + } + $form['text_format'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Text format settings'), + ]; + $form['text_format']['allowed_formats'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Allowed formats'), + '#description' => $this->t('Please check the formats that are available for this element. Leave blank to allow all available formats.'), + '#options' => $options, + ]; + $form['text_format']['hide_help'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Hide help'), + '#description' => $this->t("If checked, the 'About text formats' link will be hidden."), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + $allowed_formats = $form_state->getValue('allowed_formats'); + $allowed_formats = array_filter($allowed_formats); + $form_state->setValue('allowed_formats', $allowed_formats); + parent::validateConfigurationForm($form, $form_state); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Textarea.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Textarea.php new file mode 100644 index 000000000..e6741e71c --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Textarea.php @@ -0,0 +1,76 @@ + '', + // General settings. + 'description' => '', + 'default_value' => '', + // Form display. + 'title_display' => '', + 'description_display' => '', + 'field_prefix' => '', + 'field_suffix' => '', + 'placeholder' => '', + 'rows' => '', + // Form validation. + 'required' => FALSE, + 'required_error' => '', + 'unique' => FALSE, + 'unique_error' => '', + 'counter_type' => '', + 'counter_maximum' => '', + 'counter_message' => '', + // Submission display. + 'format' => $this->getItemDefaultFormat(), + ] + $this->getDefaultBaseProperties(); + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['counter_message']); + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + return [ + '#markup' => nl2br(new HtmlEscapedText($value)), + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['element']['default_value']['#type'] = 'textarea'; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Url.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Url.php new file mode 100644 index 000000000..fa9d3aac9 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Url.php @@ -0,0 +1,66 @@ + FALSE, + 'multiple__header_label' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'link': + return [ + '#type' => 'link', + '#title' => $value, + '#url' => \Drupal::pathValidator()->getUrlIfValid($value), + ]; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'link'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'link' => $this->t('Link'), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/Value.php b/web/modules/contrib/webform/src/Plugin/WebformElement/Value.php new file mode 100644 index 000000000..f235be7d5 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/Value.php @@ -0,0 +1,35 @@ + '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return []; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAddress.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAddress.php new file mode 100644 index 000000000..721c53b51 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAddress.php @@ -0,0 +1,78 @@ +formatTextItemValue($element, $value); + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, array $value) { + $lines = []; + if (!empty($value['address'])) { + $lines['address'] = $value['address']; + } + if (!empty($value['address_2'])) { + $lines['address_2'] = $value['address_2']; + } + $location = ''; + if (!empty($value['city'])) { + $location .= $value['city']; + } + if (!empty($value['state_province'])) { + $location .= ($location) ? ', ' : ''; + $location .= $value['state_province']; + } + if (!empty($value['postal_code'])) { + $location .= ($location) ? '. ' : ''; + $location .= $value['postal_code']; + } + if ($location) { + $lines['location'] = $location; + } + if (!empty($value['country'])) { + $lines['country'] = $value['country']; + } + return $lines; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAudioFile.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAudioFile.php new file mode 100644 index 000000000..098dedc12 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAudioFile.php @@ -0,0 +1,27 @@ +t('HTML5 Audio player (MP3 only)'); + return $formats; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAutocomplete.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAutocomplete.php new file mode 100644 index 000000000..2569e82a2 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformAutocomplete.php @@ -0,0 +1,111 @@ + FALSE, + 'multiple__header_label' => '', + // Autocomplete settings. + 'autocomplete_existing' => FALSE, + 'autocomplete_items' => [], + 'autocomplete_limit' => 10, + 'autocomplete_match' => 3, + 'autocomplete_match_operator' => 'CONTAINS', + ]; + // Remove autocomplete property which is not applicable to this autocomplete + // element. + unset($default_properties['autocomplete']); + return $default_properties; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + $has_items = !empty($element['#autocomplete_items']); + // Query webform submission for existing items. + if (!$has_items && !empty($element['#autocomplete_existing'])) { + $has_items = \Drupal::database()->select('webform_submission_data') + ->fields('webform_submission_data', ['value']) + ->condition('webform_id', $webform_submission->getWebform()->id()) + ->condition('name', $element['#webform_key']) + ->condition('value', '', '!=') + ->execute() + ->fetchField(); + } + + if ($has_items && isset($element['#webform_key'])) { + $element['#autocomplete_route_name'] = 'webform.element.autocomplete'; + $element['#autocomplete_route_parameters'] = [ + 'webform' => $webform_submission->getWebform()->id(), + 'key' => $element['#webform_key'], + ]; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['autocomplete'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Autocomplete settings'), + ]; + $form['autocomplete']['autocomplete_items'] = [ + '#type' => 'webform_element_options', + '#custom__type' => 'webform_multiple', + '#title' => $this->t('Autocomplete values'), + ]; + $form['autocomplete']['autocomplete_existing'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Include existing submission values.'), + '#description' => $this->t("If checked, all existing submission values will be visible to the webform's users."), + '#return_value' => TRUE, + ]; + $form['autocomplete']['autocomplete_limit'] = [ + '#type' => 'number', + '#title' => $this->t('Autocomplete limit'), + '#description' => $this->t("The maximum number of matches to be displayed."), + '#min' => 1, + ]; + $form['autocomplete']['autocomplete_match'] = [ + '#type' => 'number', + '#title' => $this->t('Autocomplete minimum number of characters'), + '#description' => $this->t('The minimum number of characters a user must type before a search is performed.'), + '#min' => 1, + ]; + $form['autocomplete']['autocomplete_match_operator'] = [ + '#type' => 'radios', + '#title' => $this->t('Autocomplete matching operator'), + '#description' => $this->t('Select the method used to collect autocomplete suggestions.'), + '#options' => [ + 'STARTS_WITH' => $this->t('Starts with'), + 'CONTAINS' => $this->t('Contains'), + ], + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformButtons.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformButtons.php new file mode 100644 index 000000000..f78bb7f49 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformButtons.php @@ -0,0 +1,17 @@ +getAdminLabel($element); + $name = $element['#webform_key']; + + $selectors = []; + foreach ($element['#options'] as $input_name => $input_title) { + $selectors[":input[name=\"{$name}[checkboxes][{$input_name}]\"]"] = $input_title . ' [' . $this->t('Checkboxes') . ']'; + } + $selectors[":input[name=\"{$name}[other]\"]"] = $title . ' [' . $this->t('Textfield') . ']'; + return [$title => $selectors]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCodeMirror.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCodeMirror.php new file mode 100644 index 000000000..439e89a4d --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCodeMirror.php @@ -0,0 +1,114 @@ + 'text', + ]; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'code': + return [ + '#theme' => 'webform_codemirror', + '#code' => $value, + '#type' => $element['#mode'], + ]; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'code'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'code' => $this->t('Code'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + $element += ['#mode' => 'text']; + switch ($element['#mode']) { + case 'html': + return ['

          Hello World!!!

          ']; + + case 'yaml': + return ["message: 'Hello World'"]; + + case 'text': + return ["Hello World"]; + + default: + return []; + + } + + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['codemirror'] = [ + '#type' => 'fieldset', + '#title' => $this->t('CodeMirror settings'), + ]; + $form['codemirror']['mode'] = [ + '#title' => $this->t('Mode'), + '#type' => 'select', + '#options' => [ + 'yaml' => $this->t('YAML'), + 'html' => $this->t('HTML'), + 'text' => $this->t('Plain text'), + ], + '#required' => TRUE, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCompositeBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCompositeBase.php new file mode 100644 index 000000000..dfb6b4dff --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformCompositeBase.php @@ -0,0 +1,744 @@ + '', + 'multiple' => FALSE, + 'multiple__header' => FALSE, + 'multiple__header_label' => '', + // General settings. + 'description' => '', + 'default_value' => [], + // Form display. + 'title_display' => 'invisible', + 'description_display' => '', + // Form validation. + 'required' => FALSE, + // Flex box. + 'flexbox' => '', + ] + $this->getDefaultBaseProperties(); + + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + // Get #type, #title, and #option from composite elements. + foreach ($composite_element as $composite_property_key => $composite_property_value) { + if (in_array($composite_property_key, ['#type', '#title', '#options'])) { + $property_key = str_replace('#', $composite_key . '__', $composite_property_key); + if ($composite_property_value instanceof TranslatableMarkup) { + $properties[$property_key] = (string) $composite_property_value; + } + else { + $properties[$property_key] = $composite_property_value; + } + } + } + if (isset($properties[$composite_key . '__type'])) { + $properties['default_value'][$composite_key] = ''; + $properties[$composite_key . '__description'] = FALSE; + $properties[$composite_key . '__required'] = FALSE; + $properties[$composite_key . '__placeholder'] = ''; + } + $properties[$composite_key . '__access'] = TRUE; + } + return $properties; + } + + /** + * {@inheritdoc} + */ + public function getRelatedTypes(array $element) { + return []; + } + + /** + * Get composite elements. + * + * @return array + * An array of composite elements. + */ + abstract protected function getCompositeElements(); + + /** + * Get initialized composite element. + * + * @param array &$element + * A composite element. + * + * @return array + * The initialized composite test element. + */ + abstract protected function getInitializedCompositeElement(array &$element); + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // If #flexbox is not set or an empty string, determine if the + // webform is using a flexbox layout. + if (!isset($element['#flexbox']) || $element['#flexbox'] === '') { + $webform = $webform_submission->getWebform(); + $element['#flexbox'] = $webform->hasFlexboxLayout(); + } + } + + /** + * Set multiple element wrapper. + * + * @param array $element + * An element. + */ + protected function prepareMultipleWrapper(array &$element) { + if (empty($element['#multiple']) || !$this->supportsMultipleValues()) { + return; + } + + parent::prepareMultipleWrapper($element); + + if (!empty($element['#multiple__header'])) { + $element['#header'] = TRUE; + $element = $this->getInitializedCompositeElement($element); + foreach (Element::children($element) as $key) { + $element['#element'][$key] = $element[$key]; + $element['#element'][$key]['#title_display'] = 'invisible'; + unset($element[$key]); + } + } + } + + /** + * {@inheritdoc} + */ + public function getTableColumn(array $element) { + $key = $element['#webform_key']; + $title = $element['#title'] ?: $key; + $is_title_displayed = WebformElementHelper::isTitleDisplayed($element); + + // Get the main composite element, which can't be sorted. + $columns = parent::getTableColumn($element); + $columns['element__' . $key]['sort'] = FALSE; + + // Get individual composite elements. + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + $composite_element = $composite_elements[$composite_key]; + // Make sure the composite element is visible. + $access_key = '#' . $composite_key . '__access'; + if (isset($element[$access_key]) && $element[$access_key] === FALSE) { + continue; + } + + // Add reference to initialized composite element so that it can be + // used by ::formatTableColumn(). + $columns['element__' . $key . '__' . $composite_key] = [ + 'title' => ($is_title_displayed ? $title . ': ' : '') . (!empty($composite_element['#title']) ? $composite_element['#title'] : $composite_key), + 'sort' => TRUE, + 'default' => FALSE, + 'key' => $key, + 'element' => $element, + 'property_name' => $composite_key, + 'composite_key' => $composite_key, + 'composite_element' => $composite_element, + 'plugin' => $this, + ]; + } + return $columns; + } + + /** + * {@inheritdoc} + */ + public function formatTableColumn(array $element, $value, array $options = []) { + if (isset($options['composite_key']) && isset($options['composite_element'])) { + $composite_key = $options['composite_key']; + $composite_element = $options['composite_element']; + $composite_value = $value[$composite_key]; + $composite_options = []; + + return $this->elementManager->invokeMethod('formatHtml', $composite_element, $composite_value, $composite_options); + } + else { + return $this->formatHtml($element, $value); + } + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + if ($this->hasMultipleValues($element)) { + return []; + } + + $title = $this->getAdminLabel($element) . ' [' . $this->getPluginLabel() . ']'; + $name = $element['#webform_key']; + + $selectors = []; + $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_handler = $this->elementManager->getElementInstance($composite_element); + $composite_title = (isset($composite_element['#title'])) ? $composite_element['#title'] : $composite_key; + + switch ($composite_element['#type']) { + case 'label': + case 'webform_message': + break; + + 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; + + default: + $selectors[":input[name=\"{$name}[{$composite_key}]\"]"] = $composite_title . ' [' . $element_handler->getPluginLabel() . ']'; + break; + } + } + } + return [$title => $selectors]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Update #default_value description. + $form['element']['default_value']['#description'] = $this->t("The default value of the composite webform element as YAML."); + + // Update #required label. + $form['validation']['required']['#description'] .= '
          ' . $this->t("Checking this option only displays the required indicator next to this element's label. Please chose which elements should be required below."); + + // Update '#multiple__header_label'. + $form['element']['multiple__header_label']['#states']['visible'][':input[name="properties[multiple__header]"]'] = ['checked' => FALSE]; + + $form['composite'] = [ + '#type' => 'fieldset', + '#title' => $this->t('@title settings', ['@title' => $this->getPluginLabel()]), + ]; + $form['composite']['elements'] = $this->buildCompositeElementsTable(); + $form['composite']['flexbox'] = [ + '#type' => 'select', + '#title' => $this->t('Use Flexbox'), + '#description' => $this->t("If 'Automatic' is selected Flexbox layout will only be used if a Flexbox element is included in the webform."), + '#options' => [ + '' => $this->t('Automatic'), + 0 => $this->t('No'), + 1 => $this->t('Yes'), + ], + ]; + return $form; + } + + /** + * Build the composite elements settings table. + * + * @return array + * A renderable array container the composite elements settings table. + */ + protected function buildCompositeElementsTable() { + $header = [ + $this->t('Key'), + $this->t('Title/Description/Placeholder'), + $this->t('Type/Options'), + $this->t('Required'), + $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']; + $state_disabled = [ + 'disabled' => [ + ':input[name="properties[' . $composite_key . '__access]"]' => [ + 'checked' => FALSE, + ], + ], + ]; + + $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, + '#states' => $state_disabled, + ], + $composite_key . '__placeholder' => [ + '#type' => 'textfield', + '#title' => $this->t('@title placeholder', $t_args), + '#title_display' => 'invisible', + '#placeholder' => $this->t('Enter placeholder...'), + '#attributes' => $attributes, + '#states' => $state_disabled, + ], + $composite_key . '__description' => [ + '#type' => 'textarea', + '#title' => $this->t('@title description', $t_args), + '#title_display' => 'invisible', + '#rows' => 2, + '#placeholder' => $this->t('Enter description...'), + '#attributes' => $attributes, + '#states' => $state_disabled, + ], + ], + ]; + } + else { + $row['title_and_description'] = ['data' => ['']]; + } + + // Type and options. + // Using if/else instead of switch/case because of complex conditions. + $row['type_and_options'] = []; + if ($type == 'tel') { + $row['type_and_options']['data'][$composite_key . '__type'] = [ + '#type' => 'select', + '#required' => TRUE, + '#options' => [ + 'tel' => $this->t('Telephone'), + 'textfield' => $this->t('Text field'), + ], + '#attributes' => ['style' => 'width: 100%; margin-bottom: 5px'], + '#states' => $state_disabled, + ]; + } + elseif ($type == 'select' && ($composite_options = $this->getCompositeElementOptions($composite_key))) { + $row['type_and_options']['data'][$composite_key . '__type'] = [ + '#type' => 'select', + '#required' => TRUE, + '#options' => [ + 'select' => $this->t('Select'), + 'webform_select_other' => $this->t('Select other'), + 'textfield' => $this->t('Text field'), + ], + '#attributes' => ['style' => 'width: 100%; margin-bottom: 5px'], + '#states' => $state_disabled, + ]; + $row['type_and_options']['data'][$composite_key . '__options'] = [ + '#type' => 'select', + '#options' => $composite_options, + '#required' => TRUE, + '#attributes' => ['style' => 'width: 100%;'], + '#states' => $state_disabled + [ + 'invisible' => [ + ':input[name="properties[' . $composite_key . '__type]"]' => [ + 'value' => 'textfield', + ], + ], + ], + ]; + } + else { + $row['type_and_options']['data'][$composite_key . '__type'] = [ + '#type' => 'textfield', + '#access' => FALSE, + ]; + $row['type_and_options']['data']['markup'] = [ + '#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; + } + + return [ + '#type' => 'table', + '#header' => $header, + ] + $rows; + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + // Return empty value. + if (empty($value) || empty(array_filter($value))) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'list': + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + $composite_element = $composite_elements[$composite_key]; + $composite_title = (isset($composite_element['#title'])) ? $composite_element['#title'] : $composite_key; + $composite_value = (isset($value[$composite_key])) ? $value[$composite_key] : ''; + if ($composite_value !== '') { + $items[$composite_key] = ['#markup' => "$composite_title: $composite_value"]; + } + } + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + + case 'raw': + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + $composite_value = (isset($value[$composite_key])) ? $value[$composite_key] : ''; + if ($composite_value !== '') { + $items[$composite_key] = ['#markup' => "$composite_key: $composite_value"]; + } + } + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + + default: + $lines = $this->formatHtmlItemValue($element, $value); + foreach ($lines as $key => $line) { + if (is_string($line)) { + if ($key == 'email') { + $lines[$key] = [ + '#type' => 'link', + '#title' => $line, + '#url' => \Drupal::pathValidator()->getUrlIfValid('mailto:' . $line), + ]; + } + else { + $lines[$key] = ['#markup' => $line]; + } + } + $lines[$key]['#suffix'] = '
          '; + } + return $lines; + } + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'list' => $this->t('List'), + ]; + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + // Return empty value. + if (empty($value) || (is_array($value) && empty(array_filter($value)))) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'list': + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + if (isset($value[$composite_key]) && $value[$composite_key] !== '') { + $composite_value = $value[$composite_key]; + if (!empty($composite_elements[$composite_key]['#title'])) { + $composite_title = $composite_elements[$composite_key]['#title']; + $items[$composite_key] = "$composite_title: $composite_value"; + } + else { + $items[$composite_key] = $composite_value; + + } + } + } + return implode(PHP_EOL, $items); + + case 'raw': + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + if (isset($value[$composite_key]) && $value[$composite_key] !== '') { + $composite_value = $value[$composite_key]; + $items[$composite_key] = "$composite_key: $composite_value"; + } + } + return implode(PHP_EOL, $items); + + default: + $lines = $this->formatTextItemValue($element, $value); + return implode(PHP_EOL, $lines); + } + } + + /** + * Format composite element value into lines of text. + * + * @param array $element + * A composite element. + * @param array $value + * Composite element values. + * + * @return array + * Composite element values converted into lines of html. + */ + protected function formatHtmlItemValue(array $element, array $value) { + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + if (isset($value[$composite_key]) && $value[$composite_key] != '') { + $composite_element = $composite_elements[$composite_key]; + $composite_title = $composite_element['#title']; + $composite_value = $value[$composite_key]; + $items[$composite_key] = "$composite_title: $composite_value"; + } + } + return $items; + } + + /** + * Format composite element value into lines of text. + * + * @param array $element + * A composite element. + * @param array $value + * Composite element values. + * + * @return array + * Composite element values converted into lines of text. + */ + protected function formatTextItemValue(array $element, array $value) { + $items = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + if (isset($value[$composite_key]) && $value[$composite_key] != '') { + $composite_element = $composite_elements[$composite_key]; + $composite_title = $composite_element['#title']; + $composite_value = $value[$composite_key]; + $items[$composite_key] = "$composite_title: $composite_value"; + } + } + return $items; + } + + /** + * {@inheritdoc} + */ + public function getItemsDefaultFormat() { + return 'ul'; + } + + /** + * {@inheritdoc} + */ + public function getItemsFormats() { + return [ + 'ol' => $this->t('Ordered list'), + 'ul' => $this->t('Unordered list'), + 'hr' => $this->t('Horizontal rule'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getExportDefaultOptions() { + return [ + 'composite_element_item_format' => 'label', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportOptionsForm(array &$form, FormStateInterface $form_state, array $export_options) { + parent::buildExportOptionsForm($form, $form_state, $export_options); + if (isset($form['composite'])) { + return; + } + + $form['composite'] = [ + '#type' => 'details', + '#title' => $this->t('Composite element options'), + '#open' => TRUE, + '#weight' => -10, + ]; + $form['composite']['composite_element_item_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Composite element item format'), + '#options' => [ + 'label' => $this->t('Option labels, the human-readable value (label)'), + 'key' => $this->t('Option values, the raw value stored in the database (key)'), + ], + '#default_value' => $export_options['composite_element_item_format'], + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportHeader(array $element, array $options) { + if (!empty($element['#multiple'])) { + return parent::buildExportHeader($element, $options); + } + + $composite_elements = $this->getInitializedCompositeElement($element); + $header = []; + foreach (RenderElement::children($composite_elements) as $composite_key) { + $composite_element = $composite_elements[$composite_key]; + if (isset($composite_element['#access']) && $composite_element['#access'] === FALSE) { + continue; + } + + if ($options['header_format'] == 'label' && !empty($composite_element['#title'])) { + $header[] = $composite_element['#title']; + } + else { + $header[] = $composite_key; + } + } + + return $this->prefixExportHeader($header, $element, $options); + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $export_options) { + if (!empty($element['#multiple'])) { + $element['#format'] = ($export_options['header_format'] == 'label') ? 'list' : 'raw'; + $export_options['multiple_delimiter'] = PHP_EOL . '---' . PHP_EOL; + return parent::buildExportRecord($element, $value, $export_options); + } + + $record = []; + $composite_elements = $this->getInitializedCompositeElement($element); + foreach (RenderElement::children($composite_elements) as $composite_key) { + $composite_element = $composite_elements[$composite_key]; + if (isset($composite_element['#access']) && $composite_element['#access'] === FALSE) { + continue; + } + + if ($export_options['composite_element_item_format'] == 'label' && $composite_element['#type'] != 'textfield' && !empty($composite_element['#options'])) { + $record[] = WebformOptionsHelper::getOptionText($value[$composite_key], $composite_element['#options']); + } + else { + $record[] = (isset($value[$composite_key])) ? $value[$composite_key] : NULL; + } + } + return $record; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + /** @var \Drupal\webform\WebformSubmissionGenerateInterface $generate */ + $generate = \Drupal::service('webform_submission.generate'); + + $values = []; + $composite_elements = $this->getInitializedCompositeElement($element); + for ($i = 1; $i <= 3; $i++) { + $value = []; + foreach (RenderElement::children($composite_elements) as $composite_key) { + $value[$composite_key] = $generate->getTestValue($webform, $composite_key, $composite_elements[$composite_key], $options); + } + $values[] = $value; + } + return $values; + } + + /** + * {@inheritdoc} + */ + public function getConfigurationFormProperties(array &$form, FormStateInterface $form_state) { + $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')) { + $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; + } + + /** + * Get webform option keys for composite element based on the composite element's key. + * + * @param string $composite_key + * A composite element's key. + * + * @return array + * An array webform options. + */ + protected function getCompositeElementOptions($composite_key) { + /** @var \Drupal\webform\WebformOptionsInterface[] $webform_options */ + $webform_options = WebformOptions::loadMultiple(); + $options = []; + foreach ($webform_options as $key => $webform_option) { + if (strpos($key, $composite_key) === 0) { + $options[$key] = $webform_option->label(); + } + } + return $options; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformContact.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformContact.php new file mode 100644 index 000000000..be48f7fda --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformContact.php @@ -0,0 +1,60 @@ + 'details', + '#title' => $this->t('General settings'), + '#open' => TRUE, + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $form['custom']['#title'] = $this->t('Element settings'); + $form['custom']['custom']['#title'] = $this->t('Properties'); + + // 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 API documentation.', $t_args); + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return []; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailConfirm.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailConfirm.php new file mode 100644 index 000000000..9f7e0cb11 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailConfirm.php @@ -0,0 +1,69 @@ + '', + 'confirm__description' => '', + 'confirm__placeholder' => '', + ]; + unset($default_properties['multiple']); + return $default_properties; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['email_confirm'] = [ + '#type' => 'details', + '#title' => $this->t('Email confirm settings'), + '#open' => TRUE, + ]; + $form['email_confirm']['confirm__title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Email confirm title'), + ]; + $form['email_confirm']['confirm__description'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Email confirm description'), + ]; + $form['email_confirm']['confirm__placeholder'] = [ + '#type' => 'textfield', + '#title' => $this->t('Email confirm placeholder'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + return [ + 'mail_1' => $this->getAdminLabel($element) . '1 [' . $this->t('Email') . ']', + 'mail_2' => $this->getAdminLabel($element) . ' 2 [' . $this->t('Email') . ']', + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailMultiple.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailMultiple.php new file mode 100644 index 000000000..6a69c6dd0 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEmailMultiple.php @@ -0,0 +1,56 @@ +getItemFormat($element); + switch ($format) { + case 'link': + $emails = preg_split('/\s*,\s*/', $value); + $total = count($emails); + $i = 0; + $links = []; + foreach ($emails as $email) { + $links[] = [ + '#type' => 'link', + '#title' => $email, + '#url' => \Drupal::pathValidator()->getUrlIfValid('mailto:' . $email), + '#suffix' => (++$i !== $total) ? ', ' : '', + ]; + } + return $links; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityCheckboxes.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityCheckboxes.php new file mode 100644 index 000000000..ef27712ed --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityCheckboxes.php @@ -0,0 +1,20 @@ + '', + 'selection_handler' => '', + 'selection_settings' => [], + ]; + unset($properties['options']); + return $properties; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + $this->setOptions($element); + parent::prepare($element, $webform_submission); + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $this->setOptions($element); + return parent::getElementSelectorInputsOptions($element); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityRadios.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityRadios.php new file mode 100644 index 000000000..99703c024 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntityRadios.php @@ -0,0 +1,20 @@ +getPluginId(); + $elements = $this->elementManager->getInstances(); + foreach ($elements as $element_name => $element_instance) { + // Skip self. + if ($plugin_id == $element_instance->getPluginId()) { + continue; + } + if ($element_instance instanceof WebformEntityReferenceInterface) { + $types[$element_name] = $element_instance->getPluginLabel(); + } + } + asort($types); + return $types; + } + + /** + * {@inheritdoc} + */ + public function format($type, array &$element, $value, array $options = []) { + if ($this->hasMultipleValues($element)) { + $value = $this->getTargetEntities($element, $value, $options); + } + else { + $value = $this->getTargetEntity($element, $value, $options); + } + return parent::format($type, $element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + $entity = $this->getTargetEntity($element, $value, $options); + $format = $this->getItemFormat($element); + switch ($format) { + case 'raw': + case 'value': + case 'id': + case 'label': + case 'text': + case 'breadcrumb': + return $this->formatTextItem($element, $value, $options); + + case 'link': + return [ + '#type' => 'link', + '#title' => $entity->label(), + '#url' => $entity->toUrl()->setAbsolute(TRUE), + ]; + + default: + return \Drupal::entityTypeManager()->getViewBuilder($entity->getEntityTypeId())->view($entity, $format); + } + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + $entity = $this->getTargetEntity($element, $value, $options); + $format = $this->getItemFormat($element); + switch ($format) { + case 'id': + return $entity->id(); + + case 'breadcrumb': + if ($entity->getEntityTypeId() == 'taxonomy_term') { + /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */ + $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $parents = $taxonomy_storage->loadAllParents($entity->id()); + $breadcrumb = []; + foreach ($parents as $parent) { + $breadcrumb[] = $parent->label(); + } + $element += ['#delimiter' => ' › ']; + return implode($element['#delimiter'], array_reverse($breadcrumb)); + } + return $entity->label(); + + case 'label': + return $entity->label(); + + case 'raw': + $entity_id = $entity->id(); + $entity_type = $element['#target_type']; + return "$entity_type:$entity_id"; + + case 'text': + default: + return sprintf('%s (%s)', $entity->label(), $entity->id()); + } + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + $this->setOptions($element); + $target_type = $this->getTargetType($element); + // Exclude 'anonymous' user. + if ($target_type == 'user') { + unset($element['#options'][0]); + } + return array_keys($element['#options']); + } + + /** + * {@inheritdoc} + */ + public function isMultiline(array $element) { + if ($this->hasMultipleValues($element)) { + return TRUE; + } + else { + return parent::isMultiline($element); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'link'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + $formats = parent::getItemFormats() + [ + 'link' => $this->t('Link'), + 'id' => $this->t('Entity ID'), + 'label' => $this->t('Label'), + 'text' => $this->t('Label (ID)'), + 'teaser' => $this->t('Teaser'), + 'default' => $this->t('Default'), + ]; + if ($this->hasProperty('breadcrumb')) { + $formats['breadcrumb'] = $this->t('Breadcrumb'); + } + return $formats; + } + + /** + * {@inheritdoc} + */ + public function getExportDefaultOptions() { + return [ + 'entity_reference_format' => 'link', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportOptionsForm(array &$form, FormStateInterface $form_state, array $export_options) { + parent::buildExportOptionsForm($form, $form_state, $export_options); + if (isset($form['entity_reference'])) { + return; + } + + $form['entity_reference'] = [ + '#type' => 'details', + '#title' => $this->t('Entity reference options'), + '#open' => TRUE, + ]; + $form['entity_reference']['entity_reference_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Entity reference format'), + '#options' => [ + 'link' => $this->t('Entity link; with entity id, title and url in their own column.') . '
          ' . $this->t("Entity links are suitable as long as there are not too many submissions (ie 1000's) pointing to just a few unique entities (ie 100's).") . '
          ', + 'id' => $this->t('Entity id; just the entity id column') . '
          ' . $this->t('Entity links are suitable as long as there is mechanism for the referenced entity to be looked up external (ie REST API).') . '
          ', + ], + '#default_value' => $export_options['entity_reference_format'], + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportHeader(array $element, array $options) { + if (!$this->hasMultipleValues($element) && $options['entity_reference_format'] == 'link') { + if ($options['header_format'] == 'label') { + $header = [ + (string) $this->t('ID'), + (string) $this->t('Title'), + (string) $this->t('URL'), + ]; + } + else { + $header = ['id', 'title', 'url']; + } + return $this->prefixExportHeader($header, $element, $options); + } + else { + return parent::buildExportHeader($element, $options); + } + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $options) { + if (!$this->hasMultipleValues($element) && $options['entity_reference_format'] == 'link') { + $entity_type = $this->getTargetType($element); + $entity_storage = $this->entityTypeManager->getStorage($entity_type); + $entity_id = $value; + + $record = []; + if ($entity_id && ($entity = $entity_storage->load($entity_id))) { + $record[] = $entity->id(); + $record[] = $entity->label(); + $record[] = $entity->toUrl('canonical', ['absolute' => TRUE])->toString(); + } + else { + $record[] = "$entity_type:$entity_id"; + $record[] = ''; + $record[] = ''; + } + return $record; + } + else { + if ($options['entity_reference_format'] == 'id') { + $element['#format'] = 'raw'; + } + return parent::buildExportRecord($element, $value, $options); + } + } + + /** + * Get element options. + * + * @param array $element + * An element. + */ + protected function setOptions(array &$element) { + WebformEntityTrait::setOptions($element); + } + + /** + * Get referenced entity type.. + * + * @param array $element + * An element. + * + * @return string + * A entity type. + */ + protected function getTargetType(array $element) { + return $element['#target_type']; + } + + /** + * Get referenced entity. + * + * @param array $element + * An element. + * @param array|mixed $value + * A value. + * @param array $options + * An array of options. + * + * @return \Drupal\Core\Entity\EntityInterface + * The referenced entity. + */ + protected function getTargetEntity(array $element, $value, array $options = []) { + if (empty($value)) { + return NULL; + } + if ($value instanceof EntityInterface) { + return $value; + } + + $target_type = $this->getTargetType($element); + $langcode = (!empty($options['langcode'])) ? $options['langcode'] : \Drupal::languageManager()->getCurrentLanguage()->getId(); + $entity = $this->entityTypeManager->getStorage($target_type)->load($value); + if ($entity && method_exists($entity, 'hasTranslation') && $entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + return $entity; + } + + /** + * Get referenced entities. + * + * @param array $element + * An element. + * @param array|mixed $value + * A value. + * @param array $options + * An array of options. + * + * @return array + * An associative array containing entities keyed by entity_id. + */ + protected function getTargetEntities(array $element, $value, array $options = []) { + if (empty($value)) { + return []; + } + + $target_type = $this->getTargetType($element); + $langcode = (!empty($options['langcode'])) ? $options['langcode'] : \Drupal::languageManager()->getCurrentLanguage()->getId(); + $entities = $this->entityTypeManager->getStorage($target_type)->loadMultiple($value); + foreach ($entities as $entity_id => $entity) { + if ($entity->hasTranslation($langcode)) { + $entities[$entity_id] = $entity->getTranslation($langcode); + } + } + return $entities; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Get element properties. + $element_properties = $form_state->get('element_properties'); + + // Alter element properties. + if ($properties = $form_state->getValue('properties')) { + $target_type = $properties['target_type']; + $selection_handler = $properties['selection_handler']; + // If the default selection handler has changed when need to update its + // value. + if (strpos($selection_handler, 'default:') === 0 && $selection_handler != "default:$target_type") { + $selection_handler = "default:$target_type"; + $selection_settings = []; + } + else { + $selection_settings = $properties['selection_settings'] ?: []; + } + } + else { + // Set default #target_type and #selection_handler. + if (empty($element_properties['target_type'])) { + $element_properties['target_type'] = 'node'; + } + if (empty($element_properties['selection_handler'])) { + $element_properties['selection_handler'] = 'default:' . $element_properties['target_type']; + } + $target_type = $element_properties['target_type']; + $selection_handler = $element_properties['selection_handler']; + $selection_settings = $element_properties['selection_settings']; + } + + // Reset element properties. + $element_properties['target_type'] = $target_type; + $element_properties['selection_handler'] = $selection_handler; + $element_properties['selection_settings'] = $selection_settings; + $form_state->set('element_properties', $element_properties); + + /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $entity_reference_selection_manager */ + $entity_reference_selection_manager = \Drupal::service('plugin.manager.entity_reference_selection'); + + // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem + $selection_plugins = $entity_reference_selection_manager->getSelectionGroups($target_type); + $handlers_options = []; + foreach (array_keys($selection_plugins) as $selection_group_id) { + if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) { + $handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']); + } + elseif (array_key_exists($selection_group_id . ':' . $target_type, $selection_plugins[$selection_group_id])) { + $selection_group_plugin = $selection_group_id . ':' . $target_type; + $handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']); + } + } + + // ISSUE: + // The AJAX handling for @EntityReferenceSelection plugins is just broken. + // + // WORKAROUND: + // Implement custom #ajax that refresh the entire details element and + // remove #ajax from selection settings to just get an MVP UI + // for entity reference elements. + // + // @see https://www.drupal.org/project/issues/drupal?text=EntityReferenceSelection&version=8.x + // @todo Figure out how to properly implement @EntityReferenceSelection plugins. + $ajax_settings = [ + 'callback' => [get_class($this), 'ajaxEntityReference'], + 'wrapper' => 'webform-entity-reference-selection-wrapper', + ]; + $form['entity_reference'] = [ + '#type' => 'fieldset', + '#title' => t('Entity reference settings'), + '#prefix' => '
          ', + '#suffix' => '
          ', + '#weight' => -40, + ]; + + // Target type. + $form['entity_reference']['target_type'] = [ + '#type' => 'select', + '#title' => $this->t('Type of item to reference'), + '#options' => \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE), + '#required' => TRUE, + '#empty_option' => t('- Select a target type -'), + '#ajax' => $ajax_settings, + '#default_value' => $target_type, + ]; + // Selection handler. + $form['entity_reference']['selection_handler'] = [ + '#type' => 'select', + '#title' => $this->t('Reference method'), + '#options' => $handlers_options, + '#required' => TRUE, + '#ajax' => $ajax_settings, + '#default_value' => $selection_handler, + ]; + // Selection settings. + // Note: The below options are used to populate the #default_value for + // selection settings. + $entity_reference_selection_handler = $entity_reference_selection_manager->getInstance([ + 'target_type' => $target_type, + 'handler' => $selection_handler, + 'handler_settings' => $selection_settings, + ]); + $form['entity_reference']['selection_settings'] = $entity_reference_selection_handler->buildConfigurationForm([], $form_state); + $form['entity_reference']['selection_settings']['#tree'] = TRUE; + + $this->updateAjaxCallbackRecursive($form['entity_reference']['selection_settings'], $ajax_settings); + + // Remove the no-ajax submit button. + unset( + $form['entity_reference']['selection_settings']['target_bundles_update'] + ); + + // Remove auto create, except for entity_autocomplete. + if ($this->getPluginId() != 'entity_autocomplete' || $target_type != 'taxonomy_term') { + unset( + $form['entity_reference']['selection_settings']['auto_create'], + $form['entity_reference']['selection_settings']['auto_create_bundle'] + ); + } + + // Disable AJAX callback that we don't need. + unset($form['entity_reference']['selection_settings']['target_bundles']['#ajax']); + unset($form['entity_reference']['selection_settings']['sort']['field']['#ajax']); + + // Remove user role filter, which is not working correctly. + // @see \Drupal\user\Plugin\EntityReferenceSelection\UserSelection + unset($form['entity_reference']['selection_settings']['filter']); + + // Add hide/show #format_items based on #tags. + if ($this->supportsMultipleValues() && $this->hasProperty('tags')) { + $form['display']['format_items']['#states'] = [ + 'visible' => [ + [':input[name="properties[tags]"]' => ['checked' => TRUE]], + ], + ]; + } + + // Tags (only applies to 'entity_autocomplete' element). + $form['element']['tags'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Tags'), + '#description' => $this->t('Check this option if the user should be allowed to enter multiple entity references using tags.'), + '#return_value' => TRUE, + ]; + if ($this->hasProperty('tags') && $this->hasProperty('multiple')) { + $form['element']['tags']['#states']['visible'][] = [':input[name="properties[multiple]"]' => ['checked' => FALSE]]; + $form['element']['multiple']['#states']['visible'][] = [':input[name="properties[tags]"]' => ['checked' => FALSE]]; + $form['element']['multiple__header_label']['#states']['visible'][] = [':input[name="properties[tags]"]' => ['checked' => FALSE]]; + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::validateConfigurationForm($form, $form_state); + $values = $form_state->getValues(); + if (isset($values['selection_settings']['target_bundles']) && empty($values['selection_settings']['target_bundles'])) { + unset($values['selection_settings']['target_bundles']); + } + if (isset($values['selection_settings']['sort']['field']) && $values['selection_settings']['sort']['field'] == '_none') { + unset($values['selection_settings']['sort']); + } + // Convert include_anonymous into boolean. + if (isset($values['selection_settings']['include_anonymous'])) { + $values['selection_settings']['include_anonymous'] = (bool) $values['selection_settings']['include_anonymous']; + } + $form_state->setValues($values); + } + + /** + * Replace #ajax = TRUE with a work #ajax callback. + * + * @param array $element + * A element. + * @param array $ajax_settings + * A #ajax callback. + */ + protected function updateAjaxCallbackRecursive(array &$element, array $ajax_settings) { + foreach (Element::children($element) as $key) { + $element[$key]['#access'] = TRUE; + if (isset($element[$key]['#ajax']) && $element[$key]['#ajax'] === TRUE) { + $element[$key]['#ajax'] = $ajax_settings; + } + $this->updateAjaxCallbackRecursive($element[$key], $ajax_settings); + } + } + + /** + * AJAX callback for entity reference details element. + * + * @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 entity reference details element. + */ + public function ajaxEntityReference(array $form, FormStateInterface $form_state) { + $element = $form['properties']['entity_reference']; + return $element; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntitySelect.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntitySelect.php new file mode 100644 index 000000000..b7718a4d3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformEntitySelect.php @@ -0,0 +1,20 @@ + 'flex-start', + ]; + } + + /** + * {@inheritdoc} + */ + protected function build($format, array &$element, $value, array $options = []) { + return $value; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['flexbox'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Flexbox settings'), + ]; + $form['flexbox']['align_items'] = [ + '#type' => 'select', + '#title' => $this->t('Align items'), + '#options' => [ + 'flex-start' => $this->t('Top (flex-start)'), + 'flex-end' => $this->t('Bottom (flex-end)'), + 'center' => $this->t('Center (center)'), + ], + '#required' => TRUE, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformImageFile.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformImageFile.php new file mode 100644 index 000000000..1420b4c45 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformImageFile.php @@ -0,0 +1,27 @@ +t('Image'); + return $formats; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLikert.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLikert.php new file mode 100644 index 000000000..729c94a9f --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLikert.php @@ -0,0 +1,396 @@ +Likert scale."), + * category = @Translation("Options elements"), + * multiline = TRUE, + * composite = TRUE, + * ) + */ +class WebformLikert extends WebformElementBase { + + /** + * {@inheritdoc} + */ + public function getDefaultProperties() { + return [ + 'title' => '', + // General settings. + 'description' => '', + 'default_value' => [], + // Form display. + 'title_display' => '', + 'description_display' => '', + // Form validation. + 'required' => FALSE, + // Submission display. + 'format' => $this->getItemDefaultFormat(), + // Likert settings. + 'questions' => [], + 'questions_randomize' => FALSE, + 'answers' => [], + 'na_answer' => FALSE, + 'na_answer_value' => '', + 'na_answer_text' => $this->t('N/A'), + ] + $this->getDefaultBaseProperties(); + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['questions', 'answers', 'na_answer_text']); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$element) { + parent::initialize($element); + + // Set element answers. + if (isset($element['#answers'])) { + $element['#answers'] = WebformOptions::getElementOptions($element, '#answers'); + } + + // Process answers and set N/A. + WebformLikertElement::processWebformLikertAnswers($element); + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $answers = []) { + $format = $this->getItemFormat($element); + switch ($format) { + case 'raw': + $items = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $answer_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + $items[$question_key] = ['#markup' => "$question_key: $answer_value"]; + } + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + + case 'table': + // NOTE: Including inline align attributes to help style the table for + // HTML emails. + $header = []; + $header['likert_question'] = [ + 'data' => '', + 'align' => 'left', + 'width' => '40%', + ]; + foreach ($element['#answers'] as $answer_value => $answer_text) { + $header[$answer_value] = [ + 'data' => $answer_text, + 'align' => 'center', + ]; + } + + // Calculate answers width. + $width = number_format((60 / count($element['#answers'])), 2, '.', '') . '%'; + + $rows = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $question_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + $row = []; + $row['likert_question'] = [ + 'data' => $question_label, + 'align' => 'left', + 'width' => '40%', + ]; + foreach ($element['#answers'] as $answer_value => $answer_text) { + $row[$answer_value] = [ + 'data' => ($question_value == $answer_value) ? ['#markup' => '✗'] : '', + 'align' => 'center', + 'width' => $width, + ]; + } + $rows[$question_key] = $row; + } + return [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + '#attributes' => [ + 'class' => ['webform-likert-table'], + ], + '#attached' => ['library' => ['webform/webform.element.likert']], + ]; + + default: + case 'value': + case 'list': + $items = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $answer_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + $answer_text = ($answer_value) ? WebformOptionsHelper::getOptionText($answer_value, $element['#answers']) : $this->t('[blank]'); + $items[$question_key] = ['#markup' => "$question_label: $answer_text"]; + } + return [ + '#theme' => 'item_list', + '#items' => $items, + ]; + + } + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $answers = []) { + // Return empty value. + if ($value === '' || $value === NULL || (is_array($value) && empty($value))) { + return ''; + } + + $format = $this->getItemFormat($element); + switch ($format) { + case 'raw': + $list = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $answer_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + $list[] = "$question_key: $answer_value"; + } + return implode(PHP_EOL, $list); + + default: + case 'value': + case 'table': + case 'list': + $list = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $answer_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + $answer_text = WebformOptionsHelper::getOptionText($answer_value, $element['#answers']); + $list[] = "$question_label: $answer_text"; + } + return implode(PHP_EOL, $list); + + } + } + + /** + * {@inheritdoc} + */ + public function getExportDefaultOptions() { + return [ + 'likert_answers_format' => 'label', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportOptionsForm(array &$form, FormStateInterface $form_state, array $export_options) { + parent::buildExportOptionsForm($form, $form_state, $export_options); + if (isset($form['likert'])) { + return; + } + + $form['likert'] = [ + '#type' => 'details', + '#title' => $this->t('Likert questions and answers options'), + '#open' => TRUE, + '#weight' => -10, + ]; + $form['likert']['likert_answers_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Answers format'), + '#options' => [ + 'label' => $this->t('Answer labels, the human-readable value (label)'), + 'key' => $this->t('Answer keys, the raw value stored in the database (key)'), + ], + '#default_value' => $export_options['likert_answers_format'], + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportHeader(array $element, array $options) { + $header = []; + foreach ($element['#questions'] as $key => $label) { + $header[] = ($options['header_format'] == 'key') ? $key : $label; + } + return $this->prefixExportHeader($header, $element, $options); + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $export_options) { + $record = []; + foreach ($element['#questions'] as $question_key => $question_label) { + $answer_value = (isset($value[$question_key])) ? $value[$question_key] : NULL; + if ($export_options['likert_answers_format'] == 'key') { + $record[] = $answer_value; + } + else { + $record[] = WebformOptionsHelper::getOptionText($answer_value, $element['#answers']); + } + } + return $record; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'list'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'list' => $this->t('List'), + 'table' => $this->t('Table'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTableColumn(array $element) { + $key = $element['#webform_key']; + $title = $element['#title'] ?: $key; + + $is_title_displayed = WebformElementHelper::isTitleDisplayed($element); + + // Get the main composite element, which can't be sorted. + $columns = parent::getTableColumn($element); + $columns['element__' . $key]['sort'] = FALSE; + + // Get individual questions. + foreach ($element['#questions'] as $question_key => $question_label) { + $columns['element__' . $key . '__' . $question_key] = [ + 'title' => ($is_title_displayed ? $title . ': ' : '') . $question_label, + 'sort' => TRUE, + 'default' => FALSE, + 'key' => $key, + 'element' => $element, + 'delta' => $question_key, + 'question_key' => $question_key, + 'plugin' => $this, + ]; + } + return $columns; + } + + /** + * {@inheritdoc} + */ + public function formatTableColumn(array $element, $value, array $options = []) { + if (isset($options['question_key'])) { + $question_key = $options['question_key']; + $question_value = (isset($value[$question_key])) ? $value[$question_key] : ''; + return WebformOptionsHelper::getOptionText($question_value, $element['#answers']); + } + else { + return $this->formatHtml($element, $value); + } + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + $value = []; + foreach ($element['#questions'] as $key => $question) { + $keys = array_keys($element['#answers']); + $value[$key] = ($options['random']) ? $keys[array_rand($keys)] : reset($keys); + } + return [$value]; + } + + /** + * {@inheritdoc} + */ + protected function getElementSelectorInputsOptions(array $element) { + $selectors = $element['#questions']; + foreach ($selectors as &$text) { + $text .= ' [' . $this->t('Radios') . ']'; + } + return $selectors; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['likert'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Likert settings'), + ]; + $form['likert']['questions'] = [ + '#type' => 'webform_options', + '#title' => $this->t('Questions'), + '#label' => $this->t('question'), + '#labels' => $this->t('questions'), + '#required' => TRUE, + ]; + $form['likert']['questions_randomize'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Randomize questions'), + '#description' => $this->t('Randomizes the order of the questions when they are displayed in the webform.'), + '#return_value' => TRUE, + ]; + $form['likert']['answers'] = [ + '#type' => 'webform_element_options', + '#title' => $this->t('Answers'), + '#likert' => TRUE, + '#required' => TRUE, + ]; + $form['likert']['na_answer'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow N/A answer'), + '#description' => $this->t('Allowing N/A is ideal for situations where you wish to make a likert element required, but still want to allow users to opt out of certain questions.'), + '#return_value' => TRUE, + ]; + $form['likert']['na_answer_value'] = [ + '#type' => 'textfield', + '#title' => $this->t('N/A answer value'), + '#description' => $this->t('Value stored in the database. Leave blank to store an empty string in the database.'), + '#states' => [ + 'visible' => [ + ':input[name="properties[na_answer]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['likert']['na_answer_text'] = [ + '#type' => 'textfield', + '#title' => $this->t('N/A answer text'), + '#description' => $this->t('Text display display on webform.'), + '#states' => [ + 'visible' => [ + ':input[name="properties[na_answer]"]' => ['checked' => TRUE], + ], + 'required' => [ + ':input[name="properties[na_answer]"]' => ['checked' => TRUE], + ], + ], + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLink.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLink.php new file mode 100644 index 000000000..d5ceb2d7d --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLink.php @@ -0,0 +1,60 @@ + [ + '#type' => 'link', + '#title' => $value['title'], + '#url' => \Drupal::pathValidator()->getUrlIfValid($value['url']), + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, array $value) { + return [ + 'link' => new FormattableMarkup('@title (@url)', ['@title' => $value['title'], '@url' => $value['url']]), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLocation.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLocation.php new file mode 100644 index 000000000..07f35af67 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformLocation.php @@ -0,0 +1,202 @@ + FALSE, + 'title' => '', + // General settings. + 'description' => '', + 'default_value' => [], + // For display. + 'title_display' => '', + 'description_display' => '', + // Form validation. + 'required' => FALSE, + // Location settings. + 'geolocation' => FALSE, + 'hidden' => FALSE, + 'api_key' => '', + ] + $this->getDefaultBaseProperties(); + + $composite_elements = $this->getCompositeElements(); + foreach ($composite_elements as $composite_key => $composite_element) { + $properties[$composite_key . '__title'] = (string) $composite_element['#title']; + if ($composite_key != 'value') { + $properties[$composite_key . '__access'] = FALSE; + } + } + return $properties; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // 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; + } + } + } + + /** + * {@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.'); + + $form['composite']['geolocation'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Use the browser's Geolocation as the default value."), + '#description' => $this->t('The HTML Geolocation API is used to get the geographical position of a user. Since this can compromise privacy, the position is not available unless the user approves it.'), + ]; + $form['composite']['hidden'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Hide the location element and collect the browser's Geolocation in the background."), + '#states' => [ + 'visible' => [ + ':input[name="properties[geolocation]"]' => [ + 'checked' => TRUE, + ], + ], + ], + ]; + $form['composite']['api_key'] = [ + '#type' => 'textfield', + '#title' => $this->t('Google Maps API key'), + '#description' => $this->t('Google requires users to use a valid API key. Using the Google API Manager, you can enable the Google Maps JavaScript API. That will create (or reuse) a Browser key which you can paste here.'), + ]; + $default_api_key = \Drupal::config('webform.settings')->get('elements.default_google_maps_api_key'); + if ($default_api_key) { + $form['composite']['api_key']['#description'] .= '
          ' . $this->t('Defaults to: %value', ['%value' => $default_api_key]); + } + else { + $form['composite']['api_key']['#required'] = TRUE; + if (\Drupal::currentUser()->hasPermission('administer webform')) { + $t_args = [':href' => UrlGenerator::fromRoute('webform.settings')->toString()]; + $form['composite']['api_key']['#description'] .= '
          ' . $this->t('You can either enter an element specific API key here or set the default site-wide API key.', $t_args); + } + } + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function buildCompositeElementsTable() { + $header = [ + $this->t('Key'), + $this->t('Title'), + $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, + ], + ], + ]; + } + else { + $row['title_and_description'] = ['data' => ['']]; + } + + // Access. + $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; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformManagedFileBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformManagedFileBase.php new file mode 100644 index 000000000..fd76ebc9a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformManagedFileBase.php @@ -0,0 +1,697 @@ +get('file.default_max_filesize') ?: file_upload_max_size(); + $max_filesize = Bytes::toInt($max_filesize); + $max_filesize = ($max_filesize / 1024 / 1024); + $file_extensions = $this->getFileExtensions(); + return parent::getDefaultProperties() + [ + 'multiple' => FALSE, + 'multiple__header_label' => '', + 'max_filesize' => $max_filesize, + 'file_extensions' => $file_extensions, + 'uri_scheme' => 'private', + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleWrapper() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isMultiline(array $element) { + if ($this->hasMultipleValues($element)) { + return TRUE; + } + else { + return parent::isMultiline($element); + } + } + + /** + * {@inheritdoc} + */ + public function isEnabled() { + if (!parent::isEnabled()) { + return FALSE; + } + + // Disable File element is there are no visible stream wrappers. + $scheme_options = self::getVisibleStreamWrappers(); + return (empty($scheme_options)) ? FALSE : TRUE; + } + + /** + * {@inheritdoc} + */ + public function displayDisabledWarning(array $element) { + // Display standard disabled element warning. + if (!parent::isEnabled()) { + parent::displayDisabledWarning($element); + } + else { + // Display 'managed_file' stream wrappers warning. + $scheme_options = self::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 private files directory has not been configured and public file uploads have not been enabled. For more information see: DRUPAL-PSA-2016-003'), 'warning'); + $context = [ + 'link' => Link::fromTextAndUrl($this->t('Edit'), \Drupal\Core\Url::fromRoute(''))->toString(), + ]; + $this->logger->notice("The 'File' element is unavailable because no stream wrappers are available", $context); + } + } + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + // Track if this element has been processed because the work-around below + // for 'Issue #2705471: Webform states File fields' which nests the + // 'managed_file' element in a basic container, which triggers this element + // to processed a second time. + if (!empty($element['#webform_managed_file_processed'])) { + return; + } + $element['#webform_managed_file_processed'] = TRUE; + + // Must come after #element_validate hook is defined. + parent::prepare($element, $webform_submission); + + // Check if the URI scheme exists and can be used the upload location. + $scheme_options = self::getVisibleStreamWrappers(); + $uri_scheme = $this->getUriScheme($element); + if (!isset($scheme_options[$uri_scheme])) { + $element['#access'] = FALSE; + $this->displayDisabledWarning($element); + } + else { + $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). + // 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']]); + + // Add file upload help to the element. + $element['help'] = [ + '#theme' => 'file_upload_help', + '#description' => '', + '#upload_validators' => $element['#upload_validators'], + '#cardinality' => (empty($element['#multiple'])) ? 1 : -1, + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + + // Issue #2705471: Webform states File fields. + // Workaround: Wrap the 'managed_file' element in a basic container. + if (!empty($element['#prefix'])) { + $container = [ + '#prefix' => $element['#prefix'], + '#suffix' => $element['#suffix'], + ]; + unset($element['#prefix'], $element['#suffix']); + $container[$element['#webform_key']] = $element + ['#webform_managed_file_processed' => TRUE]; + $element = $container; + } + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (!empty($element['#default_value']) && !is_array($element['#default_value'])) { + $element['#default_value'] = [$element['#default_value']]; + } + } + + /** + * {@inheritdoc} + */ + public function format($type, array &$element, $value, array $options = []) { + if ($this->hasMultipleValues($element)) { + $value = $this->getFiles($element, $value, $options); + } + else { + $value = $this->getFile($element, $value, $options); + } + return parent::format($type, $element, $value, $options); + } + + /** + * {@inheritdoc} + */ + protected function formatHtmlItem(array &$element, $value, array $options = []) { + $file = $this->getFile($element, $value, $options); + $format = $this->getItemFormat($element); + switch ($format) { + case 'id': + case 'url': + case 'value': + case 'raw': + return $this->formatTextItem($element, $value, $options); + + case 'link': + return [ + '#theme' => 'file_link', + '#file' => $file, + ]; + + default: + $theme = str_replace('webform_', 'webform_element_', $this->getPluginId()); + if (strpos($theme, 'webform_') !== 0) { + $theme = 'webform_element_' . $theme; + } + return [ + '#theme' => $theme, + '#element' => $element, + '#value' => $value, + '#options' => $options, + '#file' => $file, + ]; + } + } + + /** + * {@inheritdoc} + */ + protected function formatTextItem(array &$element, $value, array $options = []) { + $file = $this->getFile($element, $value, $options); + $format = $this->getItemFormat($element); + switch ($format) { + case 'id': + return $file->id(); + + case 'url': + case 'value': + case 'raw': + default: + return file_create_url($file->getFileUri()); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'file'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'file' => $this->t('File'), + 'link' => $this->t('Link'), + 'id' => $this->t('File ID'), + 'url' => $this->t('URL'), + ]; + } + + /** + * Get file. + * + * @param array $element + * An element. + * @param array|mixed $value + * A value. + * @param array $options + * An array of options. + * + * @return \Drupal\file\FileInterface + * A file. + */ + protected function getFile(array $element, $value, array $options) { + if (empty($value)) { + return NULL; + } + if ($value instanceof FileInterface) { + return $value; + } + + return $this->entityTypeManager->getStorage('file')->load($value); + } + + /** + * Get files. + * + * @param array $element + * An element. + * @param array|mixed $value + * A value. + * @param array $options + * An array of options. + * + * @return array + * An associative array containing files. + */ + protected function getFiles(array $element, $value, array $options) { + if (empty($value)) { + return []; + } + return $this->entityTypeManager->getStorage('file')->loadMultiple($value); + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + $title = $this->getAdminLabel($element); + $name = $element['#webform_key']; + $input = ($this->hasMultipleValues($element)) ? ":input[name=\"files[{$name}][]\"]" : ":input[name=\"files[{$name}]\"]"; + return [$input => $title . ' [' . $this->getPluginLabel() . ']']; + } + + /** + * {@inheritdoc} + */ + public function postSave(array &$element, WebformSubmissionInterface $webform_submission, $update = TRUE) { + // Get current value and original value for this element. + $key = $element['#webform_key']; + + $original_data = $webform_submission->getOriginalData(); + $data = $webform_submission->getData(); + + $value = isset($data[$key]) ? $data[$key] : []; + $fids = (is_array($value)) ? $value : [$value]; + + $original_value = isset($original_data[$key]) ? $original_data[$key] : []; + $original_fids = (is_array($original_value)) ? $original_value : [$original_value]; + + // Check the original submission fids and delete the old file upload. + foreach ($original_fids as $original_fid) { + if (!in_array($original_fid, $fids)) { + file_delete($original_fid); + } + } + + // Exit if there is no fids. + if (empty($fids)) { + return; + } + + $files = File::loadMultiple($fids); + foreach ($files as $file) { + $source_uri = $file->getFileUri(); + + // Replace /_sid_/ token with the submission id. + if (strpos($source_uri, '/_sid_/')) { + $destination_uri = str_replace('/_sid_/', '/' . $webform_submission->id() . '/', $source_uri); + $destination_directory = \Drupal::service('file_system')->dirname($destination_uri); + file_prepare_directory($destination_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + $destination_uri = file_unmanaged_move($source_uri, $destination_uri); + // Update the file's uri and save. + $file->setFileUri($destination_uri); + $file->save(); + } + + // Update file usage table. + // Set file usage which will also make the file's status permanent. + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + $file_usage->delete($file, 'webform', 'webform_submission', $webform_submission->id(), 0); + $file_usage->add($file, 'webform', 'webform_submission', $webform_submission->id()); + } + } + + /** + * {@inheritdoc} + */ + public function postDelete(array &$element, WebformSubmissionInterface $webform_submission) { + $webform = $webform_submission->getWebform(); + + $data = $webform_submission->getData(); + $key = $element['#webform_key']; + + $value = isset($data[$key]) ? $data[$key] : []; + $fids = (is_array($value)) ? $value : [$value]; + + // Delete File record. + foreach ($fids as $fid) { + file_delete($fid); + } + + // Remove the empty directory for all stream wrappers. + $stream_wrappers = array_keys(\Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE)); + foreach ($stream_wrappers as $stream_wrapper) { + file_unmanaged_delete_recursive($stream_wrapper . '://webform/' . $webform->id() . '/' . $webform_submission->id()); + } + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + if ($this->isDisabled() || !isset($element['#webform_key'])) { + return NULL; + } + + $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; + + // Look for an existing temp files that have not been uploaded. + $fids = \Drupal::entityQuery('file') + ->condition('status', FALSE) + ->condition('uid', \Drupal::currentUser()->id()) + ->condition('uri', $upload_location . '/' . $element['#webform_key'] . '.%', 'LIKE') + ->execute(); + if ($fids) { + return reset($fids); + } + + // Copy sample file or generate a new temp file that can be uploaded. + $sample_file = drupal_get_path('module', 'webform') . '/tests/files/sample.' . $file_extension; + if (file_exists($sample_file)) { + $file_uri = file_unmanaged_copy($sample_file, $file_destination); + } + else { + $file_uri = file_unmanaged_save_data('{empty}', $file_destination); + } + + $file = File::create([ + 'uri' => $file_uri , + 'uid' => \Drupal::currentUser()->id(), + ]); + $file->save(); + + $fid = $file->id(); + return [$fid]; + } + + /** + * Get max file size for an element. + * + * @param array $element + * An element. + * + * @return int + * Max file size. + */ + protected function getMaxFileSize(array $element) { + // Set max file size. + $max_filesize = \Drupal::config('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); + } + return $max_filesize; + } + + /** + * Get allowed file extensions for an element. + * + * @param array $element + * An element. + * + * @return int + * File extension. + */ + protected function getFileExtensions(array $element = NULL) { + $file_type = str_replace('webform_', '', $this->getPluginId()); + + // Set valid file extensions. + $file_extensions = \Drupal::config('webform.settings')->get("file.default_{$file_type}_extensions"); + if (!empty($element['#file_extensions'])) { + $file_extensions = $element['#file_extensions']; + } + return $file_extensions; + } + + /** + * Get file upload location. + * + * @param array $element + * An element. + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * + * @return string + * Upload location. + */ + protected function getUploadLocation(array $element, WebformInterface $webform) { + if (empty($element['#upload_location'])) { + $upload_location = $this->getUriScheme($element) . '://webform/' . $webform->id() . '/_sid_'; + } + else { + $upload_location = $element['#upload_location']; + } + + // Make sure the upload location exists and is writable. + file_prepare_directory($upload_location, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + + return $upload_location; + } + + /** + * Get file upload URI scheme. + * + * Defaults to private file uploads. + * + * Drupal file upload by anonymous or untrusted users into public file systems + * -- PSA-2016-003. + * + * @param array $element + * An element. + * + * @return string + * File upload URI scheme. + * + * @see https://www.drupal.org/psa-2016-003 + */ + protected function getUriScheme(array $element) { + if (isset($element['#uri_scheme'])) { + return $element['#uri_scheme']; + } + $scheme_options = self::getVisibleStreamWrappers(); + if (isset($scheme_options['private'])) { + return 'private'; + } + elseif (isset($scheme_options['public'])) { + return 'public'; + } + else { + return 'private'; + } + } + + /** + * Form API callback. Consolidate the array of fids for this field into a single fids. + */ + public static function validateManagedFile(array &$element, FormStateInterface $form_state, &$complete_form) { + if (!empty($element['#files'])) { + $fids = array_keys($element['#files']); + if (empty($element['#multiple'])) { + $form_state->setValueForElement($element, reset($fids)); + } + else { + $form_state->setValueForElement($element, $fids); + } + } + else { + $form_state->setValueForElement($element, NULL); + } + } + + /** + * {@inheritdoc} + */ + public static function validateMultiple(array &$element, FormStateInterface $form_state) { + // Don't validate #multiple when a file is being removed. + $trigger_element = $form_state->getTriggeringElement(); + if (end($trigger_element['#parents']) == 'remove_button') { + return; + } + + parent::validateMultiple($element, $form_state); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['file'] = [ + '#type' => 'fieldset', + '#title' => $this->t('File settings'), + ]; + $scheme_options = self::getVisibleStreamWrappers(); + + $form['file']['uri_scheme'] = [ + '#type' => 'radios', + '#title' => t('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, + ]; + // Public files security warning. + if (isset($scheme_options['public'])) { + $form['file']['uri_public_warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Public files upload destination is dangerous for webforms that are available to anonymous and/or untrusted users.') . ' ' . + $this->t('For more information see: DRUPAL-PSA-2016-003'), + '#access' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="properties[uri_scheme]"]' => ['value' => 'public'], + ], + ], + ]; + } + // Private files not set warning. + if (!isset($scheme_options['private'])) { + $form['file']['uri_private_warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Private file system is not set. This must be changed in settings.php. For more information see: DRUPAL-PSA-2016-003'), + '#access' => TRUE, + ]; + } + + $form['file']['max_filesize'] = [ + '#type' => 'number', + '#title' => $this->t('Maximum file size'), + '#field_suffix' => $this->t('MB'), + '#description' => $this->t('Enter the max file size a user may upload.'), + '#min' => 1, + ]; + $form['file']['file_extensions'] = [ + '#type' => 'textfield', + '#title' => $this->t('File extensions'), + '#description' => $this->t('A list of additional file extensions for this upload field, separated by spaces.'), + '#maxlength' => 255, + ]; + $form['file']['multiple'] = [ + '#title' => $this->t('Multiple'), + '#type' => 'checkbox', + '#return_value' => TRUE, + '#description' => $this->t('Check this option if the user should be allowed to upload multiple files.'), + ]; + return $form; + } + + /** + * Control access to webform submission private file downloads. + * + * @param string $uri + * The URI of the file. + * + * @return mixed + * Returns NULL is the file is not attached to a webform submission. + * Returns -1 if the user does not have permission to access a webform. + * Returns an associative array of headers. + * + * @see hook_file_download() + * @see webform_file_download() + */ + public static function accessFileDownload($uri) { + $files = \Drupal::entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uri' => $uri]); + if (empty($files)) { + return NULL; + } + + /** @var \Drupal\file\FileInterface $file */ + $file = reset($files); + if (empty($file)) { + return NULL; + } + + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + $usage = $file_usage->listUsage($file); + foreach ($usage as $module => $entity_types) { + // Check for Webform module. + if ($module != 'webform') { + continue; + } + + foreach ($entity_types as $entity_type => $counts) { + $entity_ids = array_keys($counts); + + // Check for webform submission entity type. + if ($entity_type != 'webform_submission' || empty($entity_ids)) { + continue; + } + + // Get webform submission. + $webform_submission = WebformSubmission::load(reset($entity_ids)); + if (!$webform_submission) { + continue; + } + + // Check webform submission view access. + if (!$webform_submission->access('view')) { + return -1; + } + + // Return file content headers. + return file_get_content_headers($file); + } + } + return NULL; + } + + /** + * Get visible stream wrappers. + * + * @return array + * An associative array of visible stream wrappers keyed by type. + */ + public static function getVisibleStreamWrappers() { + $stream_wrappers = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE); + if (!\Drupal::config('webform.settings')->get('file.file_public')) { + unset($stream_wrappers['public']); + } + return $stream_wrappers; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkup.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkup.php new file mode 100644 index 000000000..a5f71bd53 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkup.php @@ -0,0 +1,52 @@ + '', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildText(array &$element, $value, array $options = []) { + $element['#markup'] = MailFormatHelper::htmlToText($element['#markup']); + return parent::buildText($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['markup']['markup'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('HTML markup'), + '#description' => $this->t('Enter custom HTML into your webform.'), + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkupBase.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkupBase.php new file mode 100644 index 000000000..f7d1557ee --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMarkupBase.php @@ -0,0 +1,126 @@ + 'form', + ] + $this->getDefaultBaseProperties(); + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + // Hide markup element is it should be only displayed on 'view'. + if (isset($element['#display_on']) && $element['#display_on'] == 'view') { + $element['#access'] = FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function buildHtml(array &$element, $value, array $options = []) { + // Hide markup element if it should be only displayed on a 'form'. + if (empty($element['#display_on']) || $element['#display_on'] == 'form') { + return []; + } + + // Since we are not passing this element to the + // webform_container_base_html template we need to replace the default + // sub elements with the value (ie renderable sub elements). + if (is_array($value)) { + $element = $value + $element; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function buildText(array &$element, $value, array $options = []) { + // Hide markup element if it should be only displayed on a 'form'. + if (empty($element['#display_on']) || $element['#display_on'] == 'form') { + return []; + } + + // Must remove #prefix and #suffix. + unset($element['#prefix'], $element['#suffix']); + + // Since we are not passing this element to the + // webform_container_base_text template we need to replace the default + // sub elements with the value (ie renderable sub elements). + if (is_array($value)) { + $element = $value + $element; + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function getRelatedTypes(array $element) { + return []; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return []; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['markup'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Markup settings'), + ]; + $form['markup']['display_on'] = [ + '#type' => 'select', + '#title' => $this->t('Display on'), + '#options' => [ + 'form' => t('form only'), + 'display' => t('viewed submission only'), + 'both' => t('both form and viewed submission'), + ], + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMessage.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMessage.php new file mode 100644 index 000000000..12f697f94 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformMessage.php @@ -0,0 +1,131 @@ + [], + // Message settings. + 'message_type' => 'status', + 'message_message' => '', + 'message_close' => FALSE, + 'message_close_effect' => 'slide', + 'message_id' => '', + 'message_storage' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['message_message']); + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + parent::prepare($element, $webform_submission); + + if (!empty($element['#message_storage']) && empty($element['#message_id'])) { + // Use + // [webform:id]--[source_entity:type]-[source_entity:id]--[element:key] + // as the message id. + $id = []; + if ($webform = $webform_submission->getWebform()) { + $id[] = $webform->id(); + } + if ($source_entity = $webform_submission->getSourceEntity()) { + $id[] = $source_entity->getEntityTypeId() . '-' . $source_entity->id(); + } + $id[] = $element['#webform_key']; + $element['#message_id'] = implode('--', $id); + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['markup']['#title'] = $this->t('Message settings'); + $form['markup']['message_type'] = [ + '#type' => 'select', + '#title' => $this->t('Message type'), + '#options' => [ + 'status' => t('Status'), + 'error' => t('Error'), + 'warning' => t('Warning'), + 'info' => t('Info'), + ], + ]; + $form['markup']['message_message'] = [ + '#type' => 'webform_html_editor', + '#title' => $this->t('Message content'), + ]; + $form['markup']['message_close'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Allow users to close the message.'), + '#return_value' => TRUE, + ]; + $form['markup']['message_close_effect'] = [ + '#type' => 'select', + '#title' => $this->t('Message close effect'), + '#options' => [ + 'hide' => $this->t('Hide'), + 'slide' => $this->t('Slide'), + 'fade' => $this->t('Fade'), + ], + '#states' => [ + 'visible' => [':input[name="properties[message_close]"]' => ['checked' => TRUE]], + ], + ]; + $form['markup']['message_storage'] = [ + '#type' => 'radios', + '#title' => $this->t('Message storage'), + '#options' => [ + WebformMessageElement::STORAGE_NONE => $this->t('None: Message state is never stored.'), + WebformMessageElement::STORAGE_SESSION => $this->t('Session storage: Message state is reset after the browser is closed.'), + WebformMessageElement::STORAGE_LOCAL => $this->t('Local storage: Message state persists after the browser is closed.'), + WebformMessageElement::STORAGE_USER => $this->t("User data: Message state is saved to the current user's data. (Applies to authenticated users only)"), + WebformMessageElement::STORAGE_STATE => $this->t("State API: Message state is saved to the site's system state. (Applies to authenticated users only)"), + ], + '#states' => [ + 'visible' => [':input[name="properties[message_close]"]' => ['checked' => TRUE]], + ], + ]; + $form['markup']['message_id'] = [ + '#type' => 'textfield', + '#title' => $this->t('Message ID'), + '#description' => $this->t("Unique ID used to store the message's closed state. Please enter only lower-case letters, numbers, dashes, and underscores.") . '
          ' . + $this->t('Defaults to: %value', ['%value' => '[webform:id]--[element:key]']), + '#pattern' => '/^[a-z0-9-_]+$/', + '#states' => [ + 'visible' => [':input[name="properties[message_close]"]' => ['checked' => TRUE]], + ], + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformName.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformName.php new file mode 100644 index 000000000..453684d4e --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformName.php @@ -0,0 +1,64 @@ +formatTextItemValue($element, $value); + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, array $value) { + $name_parts = []; + $composite_elements = $this->getCompositeElements(); + foreach (Element::children($composite_elements) as $name_part) { + if (!empty($value[$name_part])) { + $delimiter = (in_array($name_part, ['suffix', 'degree'])) ? ', ' : ' '; + $name_parts[] = $delimiter . $value[$name_part]; + } + } + + return [ + 'name' => trim(implode('', $name_parts)), + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformRadiosOther.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformRadiosOther.php new file mode 100644 index 000000000..14488ec73 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformRadiosOther.php @@ -0,0 +1,24 @@ + 0, + // Rating settings. + 'star_size' => 'medium', + 'reset' => FALSE, + ]; + return $properties; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + if (!isset($element['#step'])) { + $element['#step'] = 1; + } + parent::prepare($element, $webform_submission); + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + $element += ['#min' => 1, '#max' => 5]; + return parent::getTestValues($element, $webform, $options); + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + $format = $this->getItemFormat($element); + + switch ($format) { + case 'star': + // Always return the raw value when the rating widget is included in an + // email. + if (!empty($options['email'])) { + return parent::formatTextItem($element, $value, $options); + } + + $build = [ + '#value' => $value, + '#readonly' => TRUE, + ] + $element; + return WebformRatingElement::buildRateIt($build); + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'star'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return parent::getItemFormats() + [ + 'star' => $this->t('Star'), + ]; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['number']['#title'] = $this->t('Rating settings'); + $form['number']['star_size'] = [ + '#type' => 'select', + '#title' => $this->t('Star size'), + '#options' => [ + 'small' => $this->t('Small (@size)', ['@size' => '16px']), + 'medium' => $this->t('Medium (@size)', ['@size' => '24px']), + 'large' => $this->t('Large (@size)', ['@size' => '32px']), + ], + '#required' => TRUE, + ]; + $form['number']['reset'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show reset button'), + '#description' => $this->t('If checked, a reset button will be placed before the rating element.'), + '#return_value' => TRUE, + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformSelectOther.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformSelectOther.php new file mode 100644 index 000000000..8ccd05bbf --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformSelectOther.php @@ -0,0 +1,24 @@ + $this->t('Sign above'), + ] + parent::getDefaultProperties(); + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + if (empty($element['#description'])) { + $element['#description'] = $this->t('Sign above'); + } + parent::prepare($element, $webform_submission); + } + + /** + * {@inheritdoc} + */ + public function formatHtmlItem(array &$element, $value, array $options = []) { + $format = $this->getItemFormat($element); + + switch ($format) { + case 'image': + if (empty($value)) { + return '[' . $this->t('not signed') . ']'; + } + + // Most email clients do not support data uri. + // @see http://stackoverflow.com/questions/6070196/what-is-data-uri-support-like-in-major-email-client-software + if (!empty($options['email'])) { + return '[' . $this->t('signed') . ']'; + } + + return [ + '#type' => 'html_tag', + '#tag' => 'img', + '#attributes' => [ + 'src' => $value, + 'alt' => $this->t('Signature'), + 'class' => ['webform-signature-pad-image'], + ], + '#attached' => ['library' => ['webform/webform.element.signature']], + ]; + + default: + return parent::formatHtmlItem($element, $value, $options); + } + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + $format = $this->getItemFormat($element); + switch ($format) { + case 'image': + case 'status': + $value = ($value) ? '[' . $this->t('signed') . ']' : '[' . $this->t('not signed') . ']'; + } + + return parent::formatTextItem($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'image'; + } + + /** + * {@inheritdoc} + */ + public function getItemFormats() { + return [ + 'raw' => $this->t('Raw value'), + 'status' => $this->t('Status'), + 'image' => $this->t('Image'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getExportDefaultOptions() { + return [ + 'signature_format' => 'status', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportOptionsForm(array &$form, FormStateInterface $form_state, array $export_options) { + parent::buildExportOptionsForm($form, $form_state, $export_options); + if (isset($form['signature'])) { + return; + } + + $form['signature'] = [ + '#type' => 'details', + '#title' => $this->t('Signature options'), + '#open' => TRUE, + ]; + $form['signature']['signature_format'] = [ + '#type' => 'radios', + '#title' => $this->t('Signature format'), + '#options' => [ + 'image' => $this->t('Image: The signature\'s Data URI.', [':href' => 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs']), + 'status' => $this->t("Status: 'signed' or 'no signed'."), + ], + '#default_value' => $export_options['signature_format'], + ]; + } + + /** + * {@inheritdoc} + */ + public function buildExportRecord(array $element, $value, array $export_options) { + $element['#format'] = ($export_options['signature_format'] == 'status') ? 'image' : 'raw'; + return [$this->formatText($element, $value, $export_options)]; + } + + /** + * {@inheritdoc} + */ + public function getTestValues(array $element, WebformInterface $webform, array $options = []) { + return ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAqkAAADjCAYAAACmeo3kAAAgAElEQVR4Xu19CfA3RXnmEw9QDlkRUTxwA8aoIKzitfHaNasYFUlKN4paIet9JOUmkUI3HokmitHo5lAxEYlZCVaihQca1C1dD7a8ool4lEZgVYJGVBIREY+49cB08n7v1/Obnp7umT7eqbL8+P/6fLqn5+n3/AnYYwgYAoaAIdAyAj8P4McA3tryJG1uhoAh0B4CP9HelGxGhoAhYAgYAgMClwC45fDvbwI4xJAxBAwBQ6AWBIyk1rJSNk5DwBAwBOYh8B8AfFJVuVSQ1nmtWWlDwBAwBFZGwEjqyoBbd4aAIWAIrITAkwC8xtPXnQH87UpjsG4MAUPAEIhGwEhqNHRW0RAwBAyBohF4AoA/9YzwrwE8uOiR2+AMAUPAEABgJNW2gSFgCBgCbSLwKABnj0ztvgA+2Oa0bVaGgCHQCgJGUltZSZuHIWAIGAJ7IvDvAVw8AsrrAfyyAWYIGAKGQMkIGEkteXVsbIaAIWAILEPgbQBO8DTxAwD7LGvaahsChoAhkBcBI6l58bXWDQFDwBDYGoELABztGcQvAHjL1oOz/g0BQ8AQGEPASKrtDUPAEDAE2kaAwfzP8UzxtwH8VttTt9kZAoZAzQgYSa159WzshoAhYAiEIfCfAPwugJ8Rxb8G4LCw6lbKEDAEDIH1ETCSuj7m1qMhYAgYAlsg4Iub+pMA/t8Wg7E+DQFDwBCYQsBI6hRC9rshYAgYAm0g4MtA9d8A/Fkb07NZGAKGQGsIGEltbUVtPoaAIWAIjCPwY/WThaKy3WIIGALFImAktdilsYEZAoaAIZAcgSsB7CdafT8A2qvaszcC/w7AicOf3wrgnwwkQ8AQWBcBI6nr4m29GQKGgCGwJQJfBXBzMYCvA7jZlgMquO9LhWPZZQAOLXisNjRDoEkEjKQ2uaw2KUPAEDAEvAj8TwDPEL9YUH//RvHZ7zLe7DG2rwwBQ2A9BIykroe19WQIGAKGwNYI+FKl2ndg71V5IYDneBaLTmZ0Nuv9MVOI3nfASvO3w2kloK0bQ8AQMAQKQYCq60PEWH4dwCsKGVspw3gwgHeMDIYhuxi6q+fnnwHcaADgcgB3sVBmPW+HfHM3kpoPW2vZECgNAUrRHgXgTgA+AeCMTpxBKPU5afiQct5ndzLvsf1HknUb8aORLj9S2slMlrozgL8t7QVfaTx0tHuf6usKQVpXGoZ10wMCRlJ7WGWboyFwLQLfBnCgAOPLiqy0ilOv8x5bT+08xXI9k64xnJ4G4JUjP14C4NatvjAT8xqTMv8aANo822MIJEPASGoyKK0hQ6BoBB4D4A2eEbZOTnqd967N+HEAx6kCfwDgvxe9g7cZ3LsBPGCka9qtPm+bYW3aK7Ux1Ebo5/sA9t10ZNZ5cwgYSW1uSW1ChoAXAX5Mf9vzy0MAvLNhzHqd964l/T8A7qcKMAbojRveB7FTG7vksL1eVdy77HUtg1nsTrN6XgSMpNrGMAT6QICquJd3SFJ/HsA5Hc57Lkll+ecC+J0+XodZs6SEmRc85ygkK58F4LGzWqu/8C6SytnxsmOJD+pf5yJmYCS1iGWwQRgC2RHwOTuw09YlqY8E8EYPuscC+FR21Mvs4MMA7uEZGm2UiYsRjL3B4WXnBYPTof71PwOgdLqXZ4qkng/g3r2AYfPMi4CR1Lz4WuuGQCkIjH1Y7gPgQ6UMMsM4xubd89n3RQBHDlhfrewIzTZ1fBMySgTDLennswCOyrB3S21yDAc5XosnW+rqVTaung/qypbKhjuCAMMqyfzaDKdjz94IjElSW5cojs2757NPSlIZRonZleRjdoXjJ8irATzF8/PLAJzS0cHzQwDXHeb7L8P/X0fN/y0AfqEjTGyqGRDo+aDOAKc1uQEC3wGw/9Av/y1DLG0wnGK7ZDrHv/OMjkHJWyb2PpLKD+z1i12p/AOTjlPvH+J9ylSpVPcz6kPL+yIWZV+6VLbFMGcnAyAx6+GR0vgLB1tmhp86SE3ewlL1sBsyztFIakZwrensCDwRwJ+oXp4E4E+z91xfB2Mf19adHBi0XpOtqwDsV98SJhuxJqm0t6REVQb4ZxnaWtqzJwK0tfzgCCgk98SshyD/tF92cWJJUm87hDDTmcu+O5hC2IXH3qQoBIykRsFmlQpBgF7b/MDKx1RM/sUZk6S2TlJ9ueopBfqpQvbwFsPQJJXSZuJEciUlYScAOHeLARbcp+/SI4dLDElUW3c+kyYjHwFwzwGEXwbwW54LD9X+rWNS8Latd2hGUutdOxs5QDuw31BA/D6AZxo4eyHgk6TSlszZlbUKmY+cM3f9oa1OOGBeuwjGmaI+CRfV/vb8GwIhTkOvB0Cy1vLju+i4+fLCczqA4wUA5pDX8m7IODcjqRnBtaazI/D84dYuO+It3he0PvtgCu/AR9Z6yBDjIxVfB3Czwtcr5/C0PSFVte55r1Lzt56RbC7Ovv1EQqadqSg5bNk+dRdJJabEiR7+zqmVf+stVNfcvWXlPQgYSbVtUTMCjwNwhprA4wG8ruZJZRq7T5LK8EM3yNRfSc2SLMiPZe+OU2OSVK6ZTn5g5jN77mTfe0TnQzoNyT1GKTSJaqu2mH8B4KQBGqZIfbTnhdcmJMSClx5T+5d0OhY+FiOphS+QDW8nAv8VwF+qEr8I4K8Mt70QGLNJ7eEM8KW2bN0Wd9crMCUF+xEAF06od6mzxtFn4+zSyb4bwN1EhZbNJT4pQpftmifNHqQJian97eM0C4EePlCzALHCVSHAQPQfUCO+7w7v26oml3iwY979vZwBP1Z4Gkm9FpC3epwPPw/gdgIvU/nvuXn0XnLvEAksLwAySkKrkugQSapDjSSW8Zj5/ADA3TuJgJD4CO+zuV4+UH2ubvuzvpcnW1LrweljV9VHUr8J4JDYBiuq57MjbD0+7K7lmSIYzFUvQwmZ9CuMpLKUlhzyby3aYtLelHFh+Uw5iulYxa0S94qOxHqGaiS1nrWyke6NwBM8MVFN6uPfKT6i9j0AN+xgY/lUtD2ffVOqWo0XbQlJ6u25FgFpDuGLkEH7VJkcocWYs3KOIZcY+gkwk5l7LMi/vU1BCPR8UAcBZIWKRoBetU9WIzSSGk5SrwRwQNErnG5wUkXLf+sUjul6Kr+ldwB48DDMdwJ4iGfIUkXLn3uWPGt4xtT9spzGj05Gbyx/awSPkFFUGF2FD6Op8L93Pbwkk6w7tT8vPpQwt+pYFgykFdyNgJFU2yE1I3ABgKPVBHq2NZz6SFzuKdDLGUCp8b5i/i2qYEPf5SnHKbajVf6UhDFyhj1ACEnVKu7WpKlzSSr3jY4cESKBtf3WOQK9fKA6X+Zmp8+Ue1pdbSTVv9w+df9nh5SFzW4QMbGLlMr674R3cg/zl3MMIala5U9nqtv3BtTIfKW6f1es4bcBYNYu97R0MZJ2zQwDSNOrkEfuPZY3zVcIah2XMZLa8eI3MHUt0eCUbE+PL6zG62ODp20DW2FyCn8/5Bd3Ba8CsN9krTYLSKLg8+53s34fAEoEWyRZS1Y2RJLK9jXR34X1kvFsUTdGkspxagfOlsN0bbEuzfVpH/TmlrSbCfmcYTh5k6T6t4BPksr4hUyI0MPzIQCMBiGfXu0sQ0mqVs+2RLKW7PlQkso+pBd8S+dTLEklBtqxjA5VxMkeQ2AvBIyk2qaoFQFfqBfOxfb0+Irqj2vrqRslElrNyN96/ThOhaCSuNGxRcb97JXYS0zmkFRtm9rKnltCUnlhpgTV7atvATjOnKhq/RTnHbd90PPia63nQ+AjI6pqk6SGk9SesPKR1BCv5Hw7eLuWp0JQyZGZA9Xe6zQVgkrXkES/FWn0EpJKfLTa32KnbnceFN2zkdSil8cGN4LAmKrfJKm7t4z8uPYWhslHUj8N4E4dvmUhIagcLNpMxByowrz75bbS6u0WvrtLSSrxeQGA5wqger00dngEhU+5hZclfLZWshUEeOs+cWQytqfHV5mBxx0+jIywfysbImAePpJKNeNNAuq2ViTEu1/OmTnpHyD+0JME3rf2c9T9PqlhCzFTU5BUXoB4lt9vAPmfANAEifvTHkPgGgTsg24boTYEtI0XiZcLzL4rHExt80w9Xq1e642kfhjAPTyg9mhjOZekavvvnmyZfe+hvOz5Mk756lwC4JbDDy3EB01BUgmHL7uZBflPffpX3J6R1IoXr9Oha4nY1wEcOmDRU9zPucuvVY6UWlAi1svzRQBHeibbY3rGUO9+B5e+GPaImdw6cyWprCtte1tIM5uKpBIb2Rb/m9JVOpjxjLKncwSMpHa+ASqbvpYGvn7IYnLQMI8vDTfzyqa1ynC1l3ZvcULHJKk92qXOJancoJKYnauC1K+ygQvpxBfKLcT8QRP92iX4rxUZyOYE8x9bRm3CZfaphWz4rYdhJHXrFbD+5yBwHoDjRQV+HEi+HEntTToYip0m96zXm7pfEjMSLnf29WiXOicEldtjMrvbl1VYqtB92EK5WJKqif5pAJ5dMSApJamEQYel4t96NyupeHukG7qR1HRYWkt5EdAfh9MBPHVQCRlJ3Y29DiPE0hcAOCbvkhXVuiSpVwPYdxjdPw8fyKIGm3kwc0JQuaHIiAA9XwZ9F77Q1J7cd/sMgNZumiTNh1LZ2Gpp8zcGhz3GVLWnUwSMpHa68BVOW2e/ORbAp4ag0Pw3n57zse9aUu2dzbIt2MXN2cZSnUgHO0cW2EZv5+CcEFQOY23THErM5qxRDWV94e9CVfcSd841tF6JuKSWpLo5aic9Xi5pn8rzyp4OEejtcO5wiZuZsvxISjLKW7aR1N3LfBmAQ1SR3qRhOg+9hKO3c1AS9tDg8loa36sqllmSNGEicaU9/NSjCVgqCeRUvzl+l+le6RvAuaV63g7goaIxElV6/NvTIQK9Hc4dLnEzU5ZkVHoXXwjgiGGWF414cDcDQuREtDcym/mmh7hGNl9FNc734GGkMmzZVwAcXsUM0g0yRlVrHv7X4r/EJpV1pQ19zdqMmD0UuoN1/FTWM6Iail5j5YykNragjU5Hq9ikqnFuzMdGIRqd1lh2rt4chiRRl+r+3nDgRomVgpmH/7WvmYyTOjdzm8SebVGVzb/V9uQkqcSC5xYFE87fgH/rVXpf295IOl4jqUnhtMYyIfC64TBn89ru1EjqbtC1BMyV7slxSmPw9wB+agCiJxzc2kvvfueAGPLqXgHggKEgna/uElKpwTIxcVIdDPrSGGpuURqMuUmqj6gyIQLTGK8VP5Vr9aihz08AYKittfoubb03G4+R1M2gt44DEdDqNR1I3EjqbiClw5lUc9esagzcOv9aTAcL554hcXVPb+fgOUN8Yc6f9qmUUIU8ktz2tH80NtJL/4cArh8CniijE5KExFmd2UX24lIinNO2Vl8w14qf6jPr6M2OP/smCumgt8M5BBMrUxYCkmD4YnsaSd29XlIK/SMA1x2K96Tm1ipWOnqcLGCrkSQseUtj4qSyP+3h3xtuDvOLVdKQud9RHankiQAYHL+mZw1JqsPjMQDesPL7+jQAr/QsCB24+M2xZyUE5r5cKw3LujEErkGAt1l+EPj/fHw3dmYMOmr4/TMAjjbs9kDgHwDcYvgLpT7XG/7dUzB/6dn/EQBMCvF8gVJvHx7p3T/HM1uTq95wc1uG8TtvMvzHt5XdZOjxI6WxZwN4dGjFQsrFSuNjhy8vmnOk/7H9PQ8Apbb66dnMJRbLRfWMpC6CzypnRkBLbnxxBWmndMthHObdv/eCSPu5ywFQ+sWHuN068/qV0jwzJLm5MhrE7wA4UwyuN4eMWMcpHchem96Ust65x0HHO6fij02rK8+2GtXIrwbwlAHoOXbNsWuj917uGLMvBvCskcHm7jsWoybrGUltclmbmBSN1nlrdVLUMYmPqfvHl3vMacrV6OX9p7TrwGHSlKTy48q95Z617NxKeTGXqGrlpefNAB5RyqRWGoeWJr8MwCkRfet3szapdK5g/rugjNUARCzPNXFf5UVWtmEkNQbRyDq9fKQi4bFqGyIQav9mJHV8kbTDkCxZo/Qmdjtyri6UzfsHpylJtuaovGPHUFK9WEkq5/BVADcfJtOj6jNEuxO61nJf1iaV3oKkSmJP7EgWc3nbPxLAG0cW0nhT6A5PUM7ATgCiNZEcAS1F3SXpMpI6Dr+UPJCc3QPADYbiVwHYL/nKldmgTAThvNIl2frogE2Zo08/qiUkVdYlroxZ3NMj7ZtjVf0OL7kva7sobUFSiZvELKcGxOfdz/4ZIcU5n/a07zebq5HUzaC3jncgcJZwJPhnofL3VZH2hj1mD9q1kaS0kJKa3xXEtCfHKXmRcRLknsmWlAbOtSfUksTeviHyvPkcgDsuOMnHUj0vaHK1qluRVJmelxFKjvOkqU0FgiTEss3e9nwqPKPaMbCjYLNKGRHQwa6n1GAy3WVPYZWmlkDbzlHida5wMvusiIow1Vbtv/tIqs5F31M4JRmCaq6ntLbV6wk3vgcfFlL3pTa5+h2t6Xu8FUnlGkgzCV42mbUrx/NsAC/yNFzTOuXAZdU2DexV4bbOAhCQKurLABw6UcfU/X6A3gbghOGnLw1xHanqvs3wt56Cscu4lp8HcPshmD3D6LinNseVgFdptMhpAE4dfp0b/qh2h58luLFuyvNGq5Rripe6JUnVFyWZJnvp+sr6XJ9/BLCP+KOp+1MiHNCWkdQAkKzIagjEhLiRKhmdMnW1gRfWkf74OZWulED05Dh1pTBzcCYhU5nMClvSpMORBMM5koV2oDUdvYXvkir6udj5MP4agJtFXhhC1yxHOSYfePzQMNOFPiFHJzvalOd+TttonQiEQzLetOJiG9grgm1dTSIgpRSU/pG0Tnlv9iod3AWm9uq/L4APAqA5hIuTSjOJQyZXpP4Cmoxyj1Fqyuc7APYf/r1UdVsTUlLNHEO0pK1zTueVEjF9E4CHJySV0uykpovjlpJUwr9WulSd7Yp992bisul7aCR1U/itc4GAlqKGfvxMkrrnNtJZupyqn6V6tN/VqkG5r6QTTE/hlOQHPoakXgrgsGHbvUYEde/hQKMt9x2Gib4HwAMXTlpLpmlfSeld6c/WJJX4SNMw/neu+KXyUsZ+almj0vdQ0PiMpAbBZIVWQEAeOFMe/XI4lhZ1z8XRDkHyQL1ApI09H8C9V1jXrbvQ6jppe6pDdJG89fAslaR+EcCRA1DvAvCgHkAb5vghAPca/v2SHVmJ5kAiNUi12IrLcyZUoDAHk5CyJPjEztnZv3WwNQ+pO6cMI6HcUFSwzIZz0FtY1kjqQgCtehIEYmxRXcdMc3nE8B92eOzp+SqlqISoR9MImWedGMgzr0ZykOKFW0owUttlppjTWm1Iu+5UEjUt7a/BznfpHkq1XvpSnkOaSmfL24kBXy3iTaeah7UzgoCRVNsaJSDwXmEnOEeKyrGn9LYtAYslY9AhbfRHtEfHqV151nvdO1IayJSeTO0556nVjnLOHH1ltX1zyogQ8gKZSyK4dP6yvjxrtpKkuvHItMd/AID7M+WjSSrbruEikRKDzdoykroZ9NbxgMASKaqR1D230XkAjh/+5CP7vSU+0PZ+Os96r6Yi0o47hmjpy1AvjiTaWSfl97O2+LMlkdTcMY/luelO3K2JeTcEIuVL1g1oNtGkCGibQRILqqlDn16lYRofLeU5E8DjVKHeHKem4ileIpIb9GIqovdJDMHUF8sYohv6fpdUTpKhHOHupETwpB2540vARL5bWxO23OHkPj5ktpK4u3jLJaxF02Mwktr08hY/OX24xOSvlrfcWpwOciyMlm65sFOyL6nm7cFxSs5X2+f2KoWX5MKHSejelB7Pvag+Xy0iGcScVVPYMsIELwB8cqitp/qf87vcR6lsc+f0r8vmTHMsBSGuX7NLXbJaM+oaSZ0BlhVNjoBW08RIZKR0MCRDVfJJFNKgPKTHyEdPjlP6AvR2AA9Ta9WjFF5GNFhCtKTJwNaStLVewY8CuNvQmTYdSTEGeR6Wbpf6hwB+dZj0rwN4RQoAFrShTTFSZqGSaYTlEFP2sWDqbVc1ktr2+pY+O0maYtVnPRIN37pKLMfIR0+OU1rV/1gAZ3VOUjVxXyIBlTnsS5f6pToHpWTet5+W9iOJVulaIRkndck+WoqZrC/PwJR7Ukq4ZX8p+0iJQ1NtGUltajmrmoxWT/8aAIa2mfv0JB0cwybUJusqETrlcgAHzwW7ovIhcXcvBkAbaD6fETFkK5rmrKFK4j43iobu6N0AHjD8sZdsXfKsibHlnVos7ehX8vdZhiGL0YBNYRHzu5REpyT58nIix7XEXCZmfl3WKfkl6HJBOpr0JwBQXeKe2EM/hTS2dtinHITc/GQOe6YEPbD2iY+MP9TWuTdTkVSqfsIuJWm9SJScHW6s1ifkdZO2vqWQP9+4pQarFLW3JvmpxuWzSSUmZpcasqMXljGSuhBAqx6FgD5MTgfw1KiWgH8AcIuh7lcAHB7ZTs3VphyE3Nykkxn/7TK11Dx339hDg6P3ZiqS0tlJktTS7SdT7G8Z0SDnfKVJTi0kNUcA/dg1k7bSsdo53bc8J6iNktmnjEPFrlRgPQM4ECgrlhQBHXbqWACfiuzhCgAHDHW/BeAmke3UXI03+n2GCdDI/zEjk5EkhR9DSq9bfD4C4O7DxHapteUHLad0rASMU8f4lCT1/QBaTym7VsilpTFs19prUoNVEo+QKv8ljoESR0lSv6eyTZVE0Nda+1X7KWlzrTpx62wzBKiKpS0g/5/PUnIg89Hz38dsNrNtOtbkY5dDhySpHG2L73+IV79bqZ7smV8HgKGC+KQglb2R1DNE3OFUamTfiSEJUcmSVHeWLLVtTn1qSol3KrvUXSS1xTM09Zosas8AXgSfVY5AQIedWhpjT6rHUh1KEdParIp0YJj6YEib1O8C2H+zUefrWKv6nwzgT0a660mSKs1iXgjgeQuXoDeSKu15c0rPaiCp8iKY4sKzcCvuVV2aNc1NDjN1cfgXANcRhYxDpV491Z4BnBlga34vBKT0ij/GOky5hqnid2rrHtX9IaGnHFafBXCH4T/476Ma3J8hXv1u2r2kRdXSZV+ih7lboTebVEcepy6Cc3HU5eX+LSW0kx6j1N7ktM+NxVLGNV0qBOEYZLg1SVJbvejH4p6lnpHULLBaoyMI6LBTKWyGepAOjm0oreqf+qi1rt4O9ep3ePaSFlW+d6lIlpTgp3iPSz80nXo7t+RQZrV6OoBXFQiMPHdKTOQg93uKvflFAEcO6/B9Yf/f6kW/qC1nJLWo5Wh6MCQQHwNwWzHLFLZdMt81Scetm0Zxz8mdB+D44U8h5KP1YP6hXv0OxV68+6WjYirJlySpZwp7zRZfPxmNJHe4rT8GQHLK5xQAzGxV2iPfs1Qe9CnnKC+rNOmRoQ5j+pGSVDqp7js00qN5WQx+i+oYSV0En1WegYC2RT0bwKNn1B8r2oMzkG/uWmoY8vH8hoh+0GK4rlCv/t5IqpSgpyIVbwLw8AHI1oP5S8lhCvXxrmNPSgFTrVWCY3aPJmRK1F0236n7ndOevJAv5TnyMvtDANcbBtJyhJQ5WGctu3Txsg7OGm8GAUoimFrOefRzYvcBwPieSx8eGtcdGvmBUMUsbbf0+jqMV4iDgAxe35r9ribtIcSpB0mqxiWF9oLvhrRJLVHlm/L9lXPN7XFfuipdr31uPGLXMWWILHlO8Btz/WFQLV70Y/HOVs9IajZorWGBgHQG4J9TqRzZVo+SVE36Q+2uZLiu8wHcu6FdKokEp/UQAO+cmF8PJFWSnhCTkNAtIdX9rZNUeX7l/mbKEEq57V9D11qXKzHbVM4xjklSW7vox+6HrPVyv3BZB2+NV4GAdpbioFNJc7SUiG0vjRZQA6iSIHC8oSFxWnacikmP24N3v4yPmvJyKPdgiKlJDe/V2BhdqLKlMZ1DMEhtTxnS59wykrSVet5KO9KTALxx7iRF+Y8DOG74b6aTdsljWrvoL4AoX1UjqfmwtZYBSvzoASnTyKWUuvRIUrUUdQ7xaNVxam6UA/duXgjgiOE/LhIevC29u6njozpselL3O23NnHdtyR6S2qESv9EpVelLcNpVl1JohlrjsyTtNuvLuKvSJnWNS0sufKppt8QXoBrwbKA7ESCBpB0qSZV7Ut88pdet6yNUqljr8r0bwAPE4OdIMiRhacme6m0AThgwmaPS7kHdLwkPMTo30caXNtGh5iaJul61GakJmgrxlmpgMsnEnPc7Vf9T7ZROojl+6ai79HIh40ubd//U7kj8u5HUxIBac/+KgFZJM77cPQDwAE75aJvUVKYEKceYqi1pr8Y253r/XiFUVS3ZUzGftgsLM0dq0jpJ1RLmlOd9LzapLwXwzOEFXusCXLrN51oxY5ecm/JysdQLX66HVPebJHXJCgXWTXloBXZpxTpAwGeHOpdQhcCkSRvrpIoaENL/2mWkhIWe+ow5ywM49JGOU/z3MaEVCy6nidixAD4VON7WSapUyaf+oPai7nd7hPGYDwrcV0uLSSl1ad7zNTh2EX+tZVsikZbnLi/ENxgW2OKkLt3pAfWNpAaAZEVmIUA1/xcA3FTUyuVYITMGue6WHEazJrpyYR1nNob0t+g4JSV6c4lY645T0is9tUq+F5Lq7LjX9LRfM+TV3GOs9JSocj7SBn+JqYa8zMqMU/8I4OZzAbTy8xAwkjoPLys9jYBW89MeiBlK5kj8pnu5tk1mupFPSGzMkLZLK6Pte0nG+LGYi2mLjlNLAtVfBuCQYbH570NLW/iF45HrHXOp2dX9aQBOHQq8BMCzFo61xOpSapjS4XNqrhLb0lKj1hDH1eEryeWS9ZORAihRv9HQwZoXl6k90+zvRlKbXdpNJqbV7zQyvz0AEonUjyQYbPtHAO6aweY19bhj2tPEP1YFKA6SNd0AACAASURBVL1UW3Cc0vttrs1gy+r+XEH83f6V6TuZupMpPFt75EU4d6YpiR1tYGkLy6e01Kilp0SVOMpzcwmh/KKI/HGViFazpM3W3pVs8zGSmg3aLhvWQfuX3F53Aejz6m9VmrMk5JTGsLWMUzIG6FxVP7GRtmYx9Ut+ybVdeOqzXqqkU0tpS8FV2obOvQAtmYNcuzXJcciYa0iJ6uaRynFQRlQxSWrILklYJvXBlXBo1lRlCOiP4pcAUNI1VyUdMm3dF1PVUVWbo6+Q8eQssyTklB5Xa45TlGo4J4YYotQySc0VxN/tqR5sUp0pyZywZinOAimtzHXRjx1nyfayvjnJ6C+xQf2l8IXfGJfe2ySpsbtoRj0jqTPAsqKjCFDaRwLkMnGwYAxpCIVYq79bPSy0JGAppi05TsmLCsnqLSIuKS2r+3MF8e+FpEpTktROZ1PnnNzbpZFU+c7Emh1NzT/l7zLGaawDr/zeGElNuToBbRlJDQDJiuxEgLfKcwZHHleQOdOZOz3Xo80KYg+fXONL1e7SkFN6HIyNyugHfC4FcMtUA92gHfmxjCURLZPUXEH8eyGpMprG2ir3kp2TSo/hqo8iuY68pDOO9lyN25sAPHxomLGmDxz+3ZqJ0AbH+HSXRlKnMbISuxHQUs2czlJuJNKQnX9bEl6k1PVNLUXlPK8EsN8wYQaldodtqRiMjWupw5Rrt1V1fypbvF37onV1vyRja9qjEvOSSarUxtQQ7k/7L8RIpqU0VjpOtZpKuajvgZHUopajusGsFbRfA0PjdUmwajgs5y6u/Biksu+Vhy3/fdTcQRVSXjq0LDH1uBDAEcOcWvrg5Azi34sk1Umit5CWlUxSa0iJqo8pbVPKS8ccaaq05WdUlFsPHSw5ewo5SssfhpHU8teo1BHyhvoxEWeS41xD7a6laCQazLzU0pMicL8PjxbipNK85GLhvLDELq5Vdb/8KOd6J1uWpMrL91I78JhzyUhqDGrjdZZqpSQx/zyAnzaSmnaBdrVmJHU9rFvqyWeHmitov8ZNEou1iPGaa0cSThtfXgL4UIrq/r10HPKwpdrKqf6Xtrtm/RcAeG6ij0SrJDVVpp1d69oyST0dwJOHydOGkWYhaz6lOk7J2LtbSJiXrIF8J+b4TGihCOfN1Mt8TJK6ZEUC6xpJDQTKiu2BgPxA8YdvALhbpqD9smOStc8ocrXFRyTXduBH4HwAdxQdLJEU6nFKm9TvAtg/10Qytstx33Bof6lDS4skdQ17VMLfcsYpZ6vMCAm3yriXx5oulaTKvVUbQZMmQjSloso/5NHvE+d9PyOpIdClKWMkNQ2OPbXCm+X7hLqVMQT5Iq8hbdBOWpcDOLgh8HMnQ7hEePTTrvegyrCTZhApSHaLJFXGR81JJJgRiZmR+LSUcUpKC3OZSky9dlubG4yNTxI2as44zloebUIV6scgBTL81vE7ZyR1xVU3kroi2I109SEA98ok6dsFkU7zyLJb2IvlWkYtnaaB/jEzDfynxiadsVi2tvdfjj827JTEqEWSKsOM/Q8AL57aFJG/t5pxShLErbQ0pWackuPaisBHbtc9IiawjVANlQw/xUsfHyOpsasQUa+2j1TEFK1KQgS06iMmnEfscM4A8DhRmbdaqv/neGnG9p27nr7lzzlE54xNBnivjaTqvZci7NjHARw3APg3AO46B8wCy+pwOzlJVqs2qU4tnNIWfO5WkRmnlpq0zO17V3l5Tq159qeYg7YtDT0/ZASQcwH8CMCJw4BqkyanwHH1Noykrg551R1Kux7mgadX/RokkVJUhgGR9mGtSFH5QXqFMJ/gBsklpWAgapcV7IcArl/RbpSmEKnSVH50sKUmDIxUcfeK8PANVZKIVBiNQdIqSXXS+lzvYMgWkxLLUDIV0u7SMlJQ8OvDubW0zTXrS8fRUPLP9+hGwyCpmfiPAE4Y/vvtAB625gR67MtIao+rHjdnHfpnzZu0VoUzDMg9VyLIcWiF1eLH6ExFUHPdzrWU7QsilErYaLcrpceeQtXP2chLV6o2t0MJkEQ+1z5y82uRpEppW04p9NQeeQyANwyFHgvgrKkKK/0u1zyU5K00tKBu5pJUbWJGE4HnDaYC7JC+GfcP6tkKRSNgJDUauu4qaqK4VhYWTY4JfKg9UcmLRPU1b+JOssmx0uaJxDWHdPo8AMcLQGoiZZJMplz/lmxS9Qc1t6ahRZJagqqf+1uatpR01slLUEnjCj3nZRiqkPdDmwjQ2YraiucPHa4pqAmdY3PljKQ2t6RZJqRjd+b0GtYT0LaotcXn8y2Itq9kGUq+eABS3Zj6oSTy0yrkVElqxF3z9YWA4d9SPC2RVGnHSGxyXyJbJKklqPpLJqnyfamRpH4YwD2GgyPEnMMXzq017UuKczRrG0ZSs8LbROMkOO9RWZ3WUoX5bFFrIVdji+9T8TMl589mIqgch5aC1xK6i+v/AQB3EmCm3HstkVQ5lzUucq2RVEnycxP8qQ9DqTapLZHUvwBAs4pdj9zjTjBjJHVq9yb+3UhqYkAbbE5neFpTxaG93ucEYS5xKXwElV7E/HuuOLM1h+7ScXFDpB9z1r0VkqrVkiGqzDk4+cq2RlKdKntNLdGuiyyzzvEp6VIu35eUl8WlezG0vtyzIeZOJLInDY278kZSQ9FOVM5IaiIgG2yGElQelPwAuueTg6F4DptJH4TShoi/16hicvMiET0bwA3ERElQqVLKoeJ33dQaussn/T0isb1uKyRVxy4ODVS+5NhqiaRKx7wSHIJKlaS6TFzcNzVyBykt51xItHc9MvoHk2Q8vkFnyyVnwCp1a9xoqwDTeSc6qxThWINQSdj/GMDTFaFLlcN+7eX14UmJDQ/NnASVeH2wwtBdvrixOSRKa3rD59pz2m4utbR5bNxyjdaQ3ObCj+0+DcArAVwG4NCcHQW2XWqcVOkdXyN30O/K1GVOXv74LXqVkdTAHZywWI0bLeH0rSkPAnyRKUGlmtg9uVXSehgkVyRxh4sfSpBwxGwY7XTGNnLboLpxanV5jixWMZjsquOLG5vLxORtIubhmwE8IvVkMrfHd5TaDXl547/5vuZ+TgNw6tBJzsxWuefB9p2EMNc+mzuHUiWpjqSuYfM8F7OQ8r6QUtSmjD2SlDstnqn7Q5BOWMZIakIwG2iKBJVxO+VHjwcSD82cEj8NnVZf1kCufMtPw/zfUXgyODSJa248dWxRjq90ou+TOOckDu8VMQ+ZTcYF6a7lVdb24mtJUYnPMwG8dADqFAAvqwU0NU75npTyPSyRpEopZAl2u7HbLTQMlSa0TuoqL/5rvm+x862+XikvZfVANjABHtb/F8BhYi5rqKQ1dFr6x99rs0XlAcdYelSJyocElR+gXbf3VFuJ0nD25Z4t0zyGzMkXRSInQeWYpF1lbR8cbRLB9SXJX8tevFSVdMhek2XcHihp/UskqTWnRJXrLVMhvwbAU0Y2jHZGdFzJSOrcN2xheSOpCwFsqLo0iue0GLeTH6K1Pnrs00dQc2fOSb2EPMyfoaSn7IMSaf62BkElSf4MgFuIyeWw6UyJnV77EO/bpf3LPmvaZ9qEhJcfSrpyRYjw4dwCSXWJQvgd5CVpzbNu196V2Jby3jLxyEOHQZeUBWvuGSC/c+8C8KCRBqTkWJo3tOQwOBe7TcobSd0E9uI61Z7UDPx+n5UPbZ1ViCAx1/wxK6jGUyzIcwA8WTkpuXYZ6/PkFeehA7vTXELa96aYb8o2tEPDpQCOWmH/SZJakwrzswDuIBZgCzOOFhyn3HtSkhSVy+ocufjvLdbW925Lcldj+Ck3p9CLqTxD5dlgJDXlyR/QlpHUAJAaL+LLfrR2MGup3nJwU33Jg2INyWPsElMSQ/LJD7Yv8gAlXDwU+b81pTTSa72kD90YzlqKv5Z5hy9Yd+xeWKuevlBuRbBa+FjTLpzvcElSVO4jae/Li++frLW5RvrR9pk184bQi6ksJ82OWricbbyd5nVf82abN1Mr7UPA5yi1xc39AgBHiwF+d8gGQrJV4vPgQdrxkJHBkZxSMsyDLreDlB6C/qBwLKV9hOWYNelaU6J5FoBHD4P5GwB3LXGziTHpyBtrSZx9sNROUt3FOLfdc8yWkoKDtS5su8Y5JlWMmdvWdaTGbtdZw9/uOwyW/gUvGP4tSeoW38qt8Vu9fyOpq0NeTIc+R6m17fLuPTgY/ReFSokvP8nfiYOzzVi81qsBMJTRb25ATh2ELwbwLIHnGradsZvaF+5sTSm+jCJRQ1idrSTOLZJUYsn3uMQLXGn2vlIzUyKpn3P+hJJUGTlDfo+eBIAOV3xKkHLPmXuVZY2kVrlsiwfNg/l9SkW9tqOUVkm7SZVGFogVHaH44ZCxY914KankXNz/Fi/Owga0vWIJkhjflHyXpLWDwss9uJXaPHS5tcR568uHHE+Jl8pduDpJZamES5o/bY2t1szUbI/KPRFDUuUZWpqUO/T8qLackdRqly564L5g6fxY8zBcy27yEgC39MyAan6q0Le2Q+XBTJx+DsADR5CmMxIDmjO/81q4TS26tu0ljjxgS3tKuCQREymZLJmkam/+tcNN+fZPzbZ5PO+4B0k4Snl3JcYlkVQp1S09jF3IOUfhDNedzy4TH5pp3WYoJ4l5aVLukDlXXcZIatXLN2vwJF6vGMiXrLi2NEFm+ZHjoESSB8BWdqj8aN1vOMD4kfBJTTleHtT8QG81zl2LXpI6eGycPhX/xwD84gYmEjKjzNrvwZyXt0TpeK0klWfM7wM4G8CvzFmEFctKkrr1vqxJ2xCyRB8FcLehIM+du49UkmeDTJ9aYgzbkHlXW8ZIarVLN2vgJAZ/CuC2otYWZEsHSOZwrho+GI+fNaPlhTmWnx2M4xnmaszO1PVEcwg6Qm0t5d1F/iglcE9pZhMcFzFnRjP+vxzn2hnN2LfOyFWqWYRW829NWty6PRvAi4b/qCUtqouLyv/fWo2+6wSTKuWt11uStdpV/cQ8NK2pnLfkSSXGsF3+NSy4BSOpBS9OgqHxMKY9JT908qFqk39bW9X1DQA3EQP5waBOz038iMOxg5SUHwCSpDFJqcSJedFfW5hKf2xb6BSZpX2EiTfTkPJD554t7Sp16DUpLUnw6iVpQqeJLSk9cI1pUSVBKXG93aYphaTKcVDTFXJmJtn4GRuR5+SYo/BYIH8OyySpGRfH17SR1JUBX7E7SoootXL2N65r2ny+c8VxuK40QeXfX6I80VMMix/2g4bDhBLSg5XkblcflC7zEKOKi/+/NomPnT8zwTAjjHtKlKKWEt/TYSQJy5UADogFP2O9ks03arPNk5LztaOYzN0ipZBU2tyfOgx+ywvlXPx2lZckdSwElcRfl6lt36fEbpO2jKRuAnv2Tn3OUSRgfPnWjtvJyX4LACUX8qEU9dCFRJDzoYSUHyCS01AJqSRzlE6dURkp1RtIS1FLU11rM4/zhxSLW14CpAPFhcoUJvsLGtCBtPlk8dJIQm3xIuU7snYUiYDl3qNIKST1E0Lz8XQAr5o7kQLLh5BUube1uYX8rZSUtQXCnG5IRlLTYVlCSyQDDDxMlYR8KFnbypPVR1A5thA7NpJPelhy7FQ18X+cIyMD3HQm4FRXUTLF//GgqklSumuqmsxQovqwmdjkLi4/DFwHruEWlyU5z5I9+33e/FtdMMf2Rk2OU/qStGYs3ph3qxSS2po9KtcihKTK+MmnAHiZWESTpMbs6AV1jKQuAK+gqrsyIG0pgRkjqAzb9JeDBJRkgYSFklZnN0qSdSCA6y/A+NMAvjrYk/Jg2poULZjKaFWSeNrNOluxUgigHLAm0Vs7grixlerZz7WkpPmOAsQSJTbSfKN0yaT0UF8zo1nsOy9J6lbYamLfClcIcZySF1itlTKb1NhdHVmvlY0XOf1qq/EAoYSR/8+bnc8znep9/pbbKWkMRKpyaRuqH6r5l5DPsf748WlNSjq1QeWBy7KlxfrUEsFSnC+0Z39JJFDmDOealkLq9V6sJS2qJlslrfXY+10CSZVkrEQb96mzcex3eWHxCXB08gLtYGckNRb5yHpGUvcEjh8vpr50kql/AUC7nO8ofEkA15DMOXU3X4xQJyAeKPzQkcBs9TCn+GEZO78IAEkpY65yHUhOe3u0d3oJAd7lGvjCTZVCEDR2pdjw6mQMJXnz10pSJSmpJRj9YwC8YQB8q9Sb8hKypTYu9bn+JgAPHxplCutHqA7kO+i7VFsIqtQrMtFe7ySVZPTkgZQywC893+c8jPF5MYBbDJUcuaUUkeTphsP/SKp8jyO6rM+6JHfS9nLOWErJgOSLhTpnHq4sLwZUe354wNKR0S2dbWLmkaMO98g5KnJDSSGnuAfoQX24mHxJUl4tgS7hHJQxPB1sJcelrEGSqiXmW6nO554BkihtdYGS5L5Uaf5cXFmeNvuMhsLnXAAnqEakJsNnGmKS1BjUF9Qp4XBeMPzFVeWtaHFjKzcgHYH40S1Fmqg/DGOw/BDA9YYfSfZJ0GmwznnwgFxDUr3ykiXrrmRnKV9kia3i8o4BLj37v5ZZ6h+66DrcVOnSqxocp84C8OhhAUoxNQnZD/ICsBVJleZaW40hBKu5ZaYuV/I99JFzKeV+LADuMXsyItA7SdXqtYxQRzdNFRVz3X8ZwLuFh3p0gytU5G2UIUscCZVdkpy+cjBJMCI6fzG0xK0UZylKT5l2V8flZfrJpy0MNTYfpd01ZF7uEiS8+tJRg1q69BBU2rawFikqd64kUltI01t1miK2UlLquwhKh0ofOZemQi2R99RnbLL2eiepBNLF1uShxniZDP4un6sBfE84AZEU8CPHv+07qKNZxj1sx5GvGwDYDwDV/b5sHfybU1+TvNEj3f0326idxFGqeisA3wZwnWFutc8p2csX2ZB2rNmaZI1lNeP0SpOgckylqYD50aPphjwfavj4lR6KR74n3xzi4NZiKiTDJG2RGUtiV3rig7nHqLwAaHW+tlX38SNZpiQTq7k4VFPeSOreS8WPmPOWp+i/loOtmk1nA41GgJJ/GvuT8PPZWoo6Jj3luChp29J5bwxkEkIZR3hLQshzhh9Kabtbi/0fpePUiPApLdB7zVJU4ikl/Vt8o2V2wNaI2C6SKrUDYxENjKRGf77iKm7xAsSN1GoZAn0jQEJDgkVi6J4tVZg87J8NYB+1LFSh8bAv9XL3dZEIgtE7rrvhttKZwmqSWj0TwEsH7LbyQB9bOikJrMF0Qs5DEuwtYrpqE7jSEx/MfX0lEaUQiuYU7pGe/2M24aUkWpg772rLG0mtduls4J0hoAkNnX/uvwEGJMtnemxP+UElcd0qLm8IFFrV/x4ADwypmKGMjjBAx8F7VWTiIz/WpYQW4zLpBBe1SQIfBYB23Hy22J9yX9ZG8ENe013xXy8AcPTQyOsAPN7ToJHUEJQTljGSmhBMa8oQyISAJjT8eGyRJpN2iEy7q5NH/BGA5xUsPXXLch6A48UabaVa90UV2cJBZsl2lR/7LSX6eg41S1E5F2onXjRBlJas2666+hK3tb17jnlKkqkl1TRTutHQ6VjabonRVudHDlyKbdNIarFLYwMzBK5BQNpQOUjWtqPkwUzPfWnLybGQLPNvpYQ/m/oA0zFxf1FoC2Loc5QqieSFvnaSpJbysa4xu9Qukr22FFifNVu8H6H7L7ac3LeapErP/jHspTlGTeY5sXhtXs9I6uZLYAMwBEYR8Enc1iQ0znOf0h1GspBPbVIWqcrjPC73RPLIvRV9jlK14egwKlHtKU1itrDnTLF/5BzWNqOQsVFbSoUq10XaUn8EwD2HH+dE/XBkttY9lmKfrtaGkdTVoLaODIFZCFAqRJu0Q0StNSVWJCGUnkpHLQ6lBttTDfTpAOjcI5+1pVTsW4cPK93JbNeGlSS1hMQD2uGnVingVJzOWYfIjML6QrzF+zFjuNFFXwDguUNt2v66ZA86/NQuYYAj80ZSo5chvKKR1HCsrKQhsBYCvrSna0ncxlT7zApGu9PXVmB7KteJ5IU2vQeJP24hJdIfQZoe3KcyLMckUr4c6Gu9K+yH78snha30Wu9K6jlqad6a32cZ9qqm7Fxz10BeFCUR1+/nLpMqk6TORX1B+TVfggXDtKqGQFcIaEcpX47p1IDwQ0+nKIZo0U/NEj/pDMF5XQHgmJW96DWJ4jhqlfS5vUHvZ37k+ayxP3ftd4bCohqXT80e6acBOFVMdK3vs5ZCr6mxSX2OTbUn055Kcwqd9S2EpJpN6hTaCX5f6yVIMFRrwhDoAgGtdqNKiR+RXHFHKb0hOWW/+mHfPLxrcIzybQ5N9llmbTs/mkswZJc0m2iBBEhs17ST1uusA/evvb4pDyWmvX7A0ODXAByWsvEdbX1CxAvlpY5nQq7zZqUpebvRkmoZA1Y7je3iRk6SWoKZy5Z4rtK3kdRVYLZODIEgBEhG/wrA9UTpXBI39vUMT7xTdk1ySrXYW4JGXWYhfpCo1nchZTjKNT8qJE8nA/g9lfCgFTs26eCzpf0i9+iJwxZ8O4CHlbkdg0YlVe5rmSxo4kb77acGjba+QrsySkkJ6y5zIAtBtfK6G0ldGXDrzhAYQYAHKCWaMod7aokbD1gSJ/Yl+3FD+gKAR1YsOZXQaskI1fxMP5pbQkSMSf4pmdYY12w2obetdPDJdZGaOixkyKmtUwRPjXXqd2mywLJrEX+tbTgWwKemBlvp7/ISoKX/V4vL5K5EKdI04iQAb6wUi2qGbSS1mqWygTaMgC8W6seGbEgpSNW9h+wpPpU+YaXkgMS15GxRc5dfhtNh3dwq6V02vez/jMFuMsV6zsUidXntZLLFd0Tb+eZe39QYyvYY4o3/O1D8cY10pDo7VytSft9a8fItCaXEV9vkngLgZSMLLsuusUY5910VbW9xuFQBjA3SEFgJAZLDlwOQ7yJJIw9D3vyXPLtU+pQ8UVVKSUpL5JR4/TGApwvgcjvTUKLHcF0kb/qp3a7Xt/92qU2X7Nc5daWaf00zjjljDCnru6B+ZZD6h9RfUkZLUdeS3i4Zc2xdOdd/BHBz0ZA0XZmyyZXrZSQ1djVm1DOSOgMsK2oIJEaAkgyqlmSaUZIaSjxjCSolTLTR42Gq05dy+CTAzt60BameXhISRmIobVFTm03IPklMGYLpYDUQEifiXKvT2a6tLgniWraTcjyaJHMNatzLfFcvVmYha2Vx09LwLcKyJT5OdzY3purXWcqmLjxy7xt/WmEFDeQVQLYuDIERBHTYk4sAHBf5wZ2yN70MwJMqd4aa2kg+0n8pgKMiMY3pj2FpeEFokZw6PKQpxdre9FTrU/PAZ0rqNbV+W/+uI3l8F8BDVtBskBx/HMCRAoC113FN7DURlTbU7wXAcFPuufHEWeEcrFo2jVhzbSb7MpI6CZEVMASyISA9StnJXAcUHr7MmPJzAI4eGSUlMyRNVHe1/PDDS6m0zpC1K97hUjykVIVtMYPN0zIR4qVjTVV/y4DzmtTVrm6Vamauz1pSaW1iMCU9TLV3tmpHzlea/ui9HIK/cxi0GKkrraaR1JWAtm4MAYWATM/Hn6bUbTxQ7zeQMBIxn/2j7MKRUxKpGlWhczeMLyZqbjU/SbF7cseznYtHrvKPGsg425e5z3P159rVBPW+AD6Yu9OM7fP9fQeA/UQfa0gzaafOuL0u8sTlAI5o/IyQqn5JyHWaYp6xPDfHHimRzXm2ZNx29TVtJLW+NbMR140AU2E+ReSMdrNx3sn8ePAwZCgYHpoPHZwo9gmcNm/4PHxbc4baNX3tncuyOSUd2qyg9vBHgVvrmmKvHvYv//2B4eI0p35MWS35q93Bx2eWQrKo7ZpjsNpVh+fKOcpWvXYspzDSXv3uIqCjQ4RIk+U5k1NDMzWnrn43ktrVcttkN0aAgbKf7BnDlQC+CeCmAG4YMUZ+4OjRTlu9HqSmEiLfBz8nQfVlkKo5/NHc7SYl1msQHG233YIES5v5cA1yY+nbtyHEbO7+KK28NMn5jgjzpS8+IaYjUvIaUr40LKocj5HUKpfNBl0ZAiQxJKc/nXDcNA+gtJSkoWUnnSnItMouZXxZ3TcJ00tUBqmchHhq7lv8Lu0oc6un9dq2QKp00H6uYW4nHJoWEDsms3BPD/t2zOZUX2xDsXBmA7lD2m3xXhfbp5HUYpfGBlYxAjwEKRl54OBZLoN0x0yLEoDzAZw3ENOeSamWojKEj3tyqd1pD8ksUtopix83/taT9Fpmmsqp8tRORS1gzXOBdrS3Ens2VUzksXOF+5MxfGX2s5Yyn+06T/UectJPXgpo0+yeEKmoJLwhDlYx57zV8SBgJNW2hSGQFgF+DKh+j31+AOCTAP56kJCSkMbGTI0dQy31tLNUyo/HVLzZHjz5fftAktSQj/vcvUTc6dhD+z/35JY0zh1jbHm9XxlU/p6Z3m/iSHKqs8z1sm8ZZeOVnj2kzUdCTXVkvblRWGL3i9VTWW4MEEPAEFiOgPZCnmrx+4O3NMko/9eTw9MUNrt+185SKaWodFZ7oUdyyvGwH36wWg/p5cNeX8BSCzl8BDVUFbtkL61RV8fqZJ+5yA6lfnSQ0pL/XiSo2imKWLtYqMTFSZWZ2euYQE2IsyM2Vf8ab4voI/Uhs/LwrTtDoDgEfB8j3yB/COB3hximxU2i8AH5VJgpHGr48Xr+QEJ9EPAjT4eLXiXbMksRybpUIS/dMj6C2oINqsNFx9TNNTefBz/HECoxXLqOJdTXtsy8+NN+mhoqmYUv1KbaVP0brqqR1A3Bt66bRUDbQsmJOslpqykzcy+qL9c545Xef0HHjpyS/GriRTJGqSnXq1dy6qBlxrLXDP+RMkaqj6CmNN1YsDWSVPWFSMthKsFLxNsBHCBGvVaa1SRAJWhEp3t1Emu+wwzr5545Enp58pJbdgAAESxJREFU5uSSfieYeptNGEltc11tVtsjwNv37QFcD8CnhUqpJyeb1KvwbAAvUo3yI8wPUwyBZL2TPXZ77MKF9XpdZNup515Ce1JC9S4AD0owKBJUShmZqMI9KaTiCYaWpAnuMaliZqM55qdtLdkPbXl58Yp5N5JMfoNGZOB+ds/LDv9G+1z38MygxDn0LOb5zdTKUwlXNphu+10aSW1/jW2GhkALCPjszGII6pRDFD/sJGMkTvbsiYCM75kqrqdWg7ekltZqZ6JJlTOl/qEEKWQP+uzgmWjhxMT9hIxlyzLcO4wVLcnos4YEFFJDMicqhTTfeiKA1245wR77NpLa46rbnA2B+hDQH+KLABw34yNMyTbtTal69dlS0kSDHzkL7+XfG88bJIDu16VqT5+Kn+vDdMEtPD6zlJhL1RQWPvV2S6YSU/OXv+skCbxIPXdI++rKzZViu4sGzX54hqS8XMyZW7dljaR2u/Q2cUOgKgS0xC1UGuLIqQ7F4yZPBxazD57eCl8GcOuhGO2q952usrOEDseUy5Fo4TCjqvMi9GYA1xG1c9iG+pyk5pKwqAkWWEnb/fLSyT3FcGbumWOHyjq8SDEOM/9/bt0CIapzSEZS61w3G7Uh0BsClGAcNEw6JAzMvQE8fsTelFIRElMSpZ7s9WL3jCYA7xkSVcS2pwlqSwTAZ5aSIxEB+2F2tduKRehVgkoItBSVwfr/XHjz0670PjMlodJcI/RSHPtOWL0RBIyk2tYwBAyB0hF4zhC31I1zl9SNklM6Schg8K4eyS3VsJTKmtoufNV1tIrQ0D2+HrSdJp1RqLJuZT20WUoMOQpZGV/KWDpPtYJjCAaujE5/yux8TIrCfcWHl1L+e44pjzSjaCWZxBxMiylrJLWYpbCBGAKGgAcBqjRJkpwUlUV8JGksww7Lkwg5yamBPA8BTQBCpNi+Hnzrkzsl6LyZpimtpcRLbXd9o9J2qDmcsdKgsU4r2v6X9upHiK7nOvlpaXiONVwHmQZ6MZLawCLaFAyBRhGgNJQ2ZdLR6aMA7iHmy98YRoofKu0QddngDHVWo/isMa2/AHCS6CjG5tEn3c7hRLQGHlN9yBBIsYR+qo+xnPRT9Vr9XWJOe+l9xERjokVI+/eeTSiK2C9GUotYBhuEIWAIKASouvx95Xyi1XYksfQI1+kf2VQMmbJF2BuBzwO4nfjzjWeqlCn1Y774m4s2Wo3fKZMdcLo5nMG0fXDv+/zBAN4x8uLGRIuQUmqLi1rAiWgktYBFsCEYAobAHgg8cwgdcyOFi1PbPRLArwCgc5R+WiVAW2wRSqaZ1MA9cxymxmyDKZmi1Ls120lf0P4ltru+9dbe/HOD0m+xh3L3+W4AD/B0EkPeud8ZX/ZOQ3vmLJV79QLaN5IaAJIVMQQMgVUQoFSEBJUfB/lcBeBxAG4GgBJWmX/blXNOUbQJtCcNAjFSu122wacBYNaw1h5fTFTaid4l4US553lJkN78c20tEw6niKZ4WX2jZySxuEinNzpf+S7BRUy8p0EYSe1ptW2uhkCZCJAM/R6An/IMj3al9JDWxFWSUzpF8X/2pEVAp9rcJRkkOX3GICXVo8gRIzTtTONao2STNtPa3CSHva22Q+3dVlJfoLiC3x6ybBGrmEfatpqzVAyCGeoYSc0AqjVpCBgCkwjQfu/uAB6hPPdlxR8DGDujKF1lFiSmKWxNdTwJ3koFtCr1J0fiylLKR7LmQv644bl4tLxAtLZGnCsD9h+s1iIm3NHUcmqCSinfQxvEdAoH9zuxfxuAA0WFbwE4cgEm0ha1pbi9oZgWW85IarFLYwMzBJpFgFlcfCr7kAlTSkWVfovEJ2T+a5YJyTJFFSmletJ+mESNa9Si7Snxp8nCqZ7LVeqg/ZROn6PIP7Gl5LbXJBRj0uuxC1To+yI9+lPbEoeOwcp5EDCSatvCEDAEciPAjy3DGNFGj/+/f0SHJAAkPvyY2JMfAR0f1ec0pQPKc1RcJ5LTOYHT888mXQ8++9OvAPilIZ5vqp58ZKx3gspz5F2DBkbivFTyKR0EibEOZZdqTa2dCASMpEaAZlUMAUMgGAEe+Bd61KKhDTBO5292LDkKxSl1ufMAHC8alfEmx5yj/mgwwWhNte9goNSY2cwkickRTYL2lv8LwAEC/1bteufsW20j7eoulaJKh6nebX3nrMcqZY2krgKzdWIIdIkAP+aUMsmPbQgQlGZQYtprmscQjHKW4bpdoiTejgjwN9qfyrSzXC9+6FuWclOySY99+VwK4KgFdpC+NdQZq1gmtRlBzr2Ts23p2OT6OR3AUxd2ag5TCwHMWd1Iak50rW1DoG8ErphJUEl2qE7lh7pVaVwNO0KrtHnROHyQIGobSc6nh3iSNF84Viwe9yqJeqwnud4HvuxqLHPGEJat9/fBd0ngGtAsZQk2MkpArgxhNbzzxY7RSGqxS2MDMwSqRkDnF981GX5sKDW1GKfbLzklpXRskyptxp0kSfOFW+qBoPpsb2OCxftWd8x0wt6JPdHy2QIvXQNifwGAWw1dxaRQ3f6NbXwERlIbX2CbniGwEQLa8WZsGEwdyQ9Qr97KGy3PaLeU3DFxgnsoXWLAdH7AZU70HLFAS8OC46GkjaGmriMGt9RRxzVF6SBtXHXorhx2riViGzomksnPqdS6KbJtycsHM6vpcGKh47NyGREwkpoRXGvaEOgcAZ99nYOkdS/wWpf+q4oM/AOAW6rJ9EKifI5SzOdOUrlExezIr885yhJT7P3mMNHHKerPSyX42nwgNktVre95NeM2klrNUtlADYEqEaAUhM4lNx4yR7mP+9KPfJVgFDxopqSlZ7NMu+kb7tkAnpaApBUMxTVD83mSXwTgZxNI/cdCd7FP0yjsuTN4fnwdwPXFnynZZhKQ2Idt0gnOxWqmNocXEnsKRMBIaoGLYkMyBAwBQ2BFBJh2lheJqacXmz2SxZerbGcpzBt6Dd01ta92/a4JfYpYsTJwf4r2lszP6k4gYCTVtoghYAgYAv0i8A0AN5mYPgkaJU2pPNlLRpuqfEYwkI5jKQjqWOrYXoh/zJoTs88rW+ilzlKa9Br+MSuzYh0jqSuCbV0ZAoaAIVAQAoyFqu1N9fBoAtBqelM9V5IiqoElQWX4rYctzKB1HwB/OKQzlX2aHeT4y8A1+CCAo0WRpc5SOkKAqfkLOozGhmIktYJFsiEaAoaAIZAYAZllx9c0CQHV3i0H6NfzpqT4fuKPVAVTshqb4pVE6xkDyZd99Yjt3O37AgDPVZV+YcF+lPFQ2WwqB7i587LyMxEwkjoTMCtuCBgChkADCJCA3WhkHgz3Q0eqnpx4fKRoiSqYpIjhpZxzjoOakRFI/mOJbwNbb3IK3HvvUKXePki0Jyt7CtCTnyYcbi2MoMaguFEdI6kbAW/dGgKGgCGwIgL8UDNsz74ATlUqbTmMXsJLyTkTG0pRDxJ/jI2FSumpLysXm+4lMsKSba0lnmxriXMT1+N8AHccBrWkrSXzsrqRCBhJjQTOqhkChoAhUAEC/Eh/HMCRAWMlQSVJ6Ck8GKVr71MST4aaOi4CBy2xc5CTGNG8oifTiYDttlcRX1YpFloi0daOUj/ZmYYgZh2KqmMktajlsMEYAoaAIZAUgUsBHBbQIp1IqIbuiaASllR2qCT3Ojg/ySlJEsmXPbsRGCOodFw7JnJfaqnsYwGcZQtRFwJGUutaLxutIWAIGAKhCOisOr563wFwQifhpfT8fUH1YzzufTib7WnoLr1Wen/miAlKbGYpHamBUSp4CbOnMgSMpFa2YDZcQ8AQMAQCEeCH+uIdZWmrR+lSTw5SDg4SI2Yuuo7AJ4bI+MJWfQDAiZHSv8ClbabYmIkEJ5jKLtgcpSreLkZSK148G7ohYAgYAhMI/BmAk0WZbwF44WAf2SM5JRQkRpTc8f/dQ8knw03NeXz2rLHEak6/rZQl3rwoHDwyoTtHRkHQpgOx0thWcK56HkZSq14+G7whYAgYApMI0HmKaU+vAPCpydJtF9B52znbjwF44EzJJwkWia4MMZUiM1Xb6P/b7HyZveTcY6TarM926QjnnqUZqnpZj2LnaSS12KWxgRkChoAhYAgkRkA7Ss0lliSlDNCv7RvZDk0ILP7p9IJRgk0iKTN7yVo0Q3nozEsD63NtKBE/fGjM1PzTa1F8CSOpxS+RDdAQMAQMAUMgAQI+R6lQlTKJFckpQ0npx5yk5i0OifyxI1XmXhpkMwzxRVtg91i4qXnrUmRpI6lFLosNyhAwBAwBQyAhAlQD/28A1xVthnjyP2GI0+mCweshvQ7Ab0RI/RJOraqmtI20HDxDdlEaTWn33Een+X06gFfNbcTKl4eAkdTy1sRGZAgYAoaAIZAOAaqVqV6WjlK7bBWd1JSEaUwlTVUyVf4xhCrdzOpqSduLytF/DcDPRZpL6BBgMU5wdSHZ0WiNpHa02DZVQ8AQMAQ6RECr+X02jySjVBWTeEoyqyV9VCmzPbM9nb+RrgSwn6caA/Y/LBJTEl+uiUxpG2rCMX8GVmN1BIykrg65dWgIGAKGgCGwEgI6jqnP5vE5AJ4M4FYjY/oegJcCeLmp9aNX7esAbuqpfTWAey4gqDrCgnnzRy9RmRWNpJa5LjYqQ8AQMAQMgeUIaCmqzAO/K9MRe6ZKn/VpR2lPPAK7Mp/Fpir1rV1MKLH4WVnNVRAwkroKzNaJIWAIGAKGwMoI6KxSlKKSMFG1/4rBScc3JAbkZ0B4U+mnWbCxzGcvAfCsiC7oJMX1k/bCXDOaavSaoCICxjqqGEmtY51slIaAIWAIGALhCPhicVKKesAO1f65AH7ViE44yDNKSq/+HwB40XARmNHENUVJRElQ5UNHKRJXI6hz0aygvJHUChbJhmgIGAKGgCEQjIAvm9HnABw24q1PtT5JjklOgyGOKugkn/8UUZt1aX9K6bh8uHZjjm4R3ViV0hAwklraith4DAFDwBAwBGIRoKTtZSoe6vcB7ONpkOp/Svio2renXARIQv8cwJ3UEKni5+UihvSWO1sb2R4IGEm1DWEIGAKGgCHQAgI+W8WxedHJ5kkmPS122Sk5PXkgoVpSyqD/vFjQqc2exhEwktr4Atv0DAFDwBDoAAGqgc8JmCcJDsmNSU8DwNqoCNfyDAAHe/q3y8VGi7JVt0ZSt0Le+jUEDAFDwBBYisCDAJy2Ixe8bJ8ONjQHMNvTpajnq+9zjHK90fGN5hmm3s+Hf3EtG0ktbklsQIaAIWAIGAITCFAd/B4Adw1AihmNfslSmAYgtW0RH0H9FoDfGLJKGTnddn026d1I6iawW6eGgCFgCBgCCxD4MoBbT9Snat85RhnBWQD2ClV9ERkstNQKwJfehZHU0lfIxmcIGAKGgCEgEdiVwYjl6M3/4sH21MhpuXuHQf4pEf+ZIe3sfmKoJKi0TbX1K3f9VhmZkdRVYLZODAFDwBAwBBIhMJbB6EODw81bjNwkQjpfM1yjE0eaZ+xTElQLzp8P/2paNpJazVLZQA0BQ8AQMAQGBGQGI/6JdqeHGzpVIDB2yeDgLwTwCHNuq2IdVxmkkdRVYLZODAFDwBAwBBIjQFUxiem3TeqWGNn8zVGNf5DqxmxQ8+NeXQ9GUqtbMhuwIWAIGAKGgCFQNQJMvEBvfj43BHA6gFdUPSMbfBYEjKRmgdUaNQQMAUPAEDAEDAFDwBBYgoCR1CXoWV1DwBAwBAwBQ8AQMAQMgSwIGEnNAqs1aggYAoaAIWAIGAKGgCGwBAEjqUvQs7qGgCFgCBgChoAhYAgYAlkQMJKaBVZr1BAwBAwBQ8AQMAQMAUNgCQJGUpegZ3UNAUPAEDAEDAFDwBAwBLIg8P8B48hP42lVITQAAAAASUVORK5CYII=']; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSelectSort.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSelectSort.php new file mode 100644 index 000000000..1c4be3008 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSelectSort.php @@ -0,0 +1,65 @@ + TRUE, + 'multiple_error' => '', + // Table settings. + 'js_select' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleValues(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getItemDefaultFormat() { + return 'ol'; + } + + /** + * {@inheritdoc} + */ + public function getElementSelectorOptions(array $element) { + return $this->getTableSelectElementSelectorOptions($element, '[checkbox]'); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSort.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSort.php new file mode 100644 index 000000000..3a2db794f --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTableSort.php @@ -0,0 +1,75 @@ +hasMultipleValues($element)) { + $element['#element_validate'][] = [get_class($this), 'validateMultipleOptions']; + } + + parent::prepare($element, $webform_submission); + + // Add missing element class. + $element['#attributes']['class'][] = str_replace('_', '-', $element['#type']); + + // Add one column header is not #header is specified. + if (!isset($element['#header'])) { + $element['#header'] = [ + (isset($element['#title']) ? $element['#title'] : ''), + ]; + } + + // Convert associative array of options into one column row. + if (isset($element['#options'])) { + foreach ($element['#options'] as $options_key => $options_value) { + if (is_string($options_value)) { + $element['#options'][$options_key] = [ + ['value' => $options_value], + ]; + } + } + } + + $element['#attached']['library'][] = 'webform/webform.element.' . $element['#type']; + } + + /** + * {@inheritdoc} + */ + public function setDefaultValue(array &$element) { + if (isset($element['#default_value']) && is_array($element['#default_value'])) { + $element['#default_value'] = array_combine($element['#default_value'], $element['#default_value']); + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['options']['js_select'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Select all'), + '#description' => $this->t('If checked, a select all checkbox will be added to the header.'), + '#return_value' => TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + 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')); + + $selectors = []; + foreach ($element['#options'] as $value => $text) { + if (is_array($text)) { + $text = $value; + } + $selectors[":input[name=\"{$name}[{$value}]$input_selector\"]"] = $text . ' [' . $type . ']'; + } + return [$title => $selectors]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTelephone.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTelephone.php new file mode 100644 index 000000000..620cc97b0 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTelephone.php @@ -0,0 +1,121 @@ + 'tel:' . $value['phone'], + '@tel' => $value['phone'], + '@ext' => $value['ext'], + '@type' => $value['type'], + ]; + if ($value['ext'] && $value['type']) { + $telephone = $this->t('@type: @tel x@ext', $t_args); + } + elseif ($value['ext']) { + $telephone = $this->t('@tel x@ext', $t_args); + } + elseif ($value['type']) { + $telephone = $this->t('@type: @tel', $t_args); + } + else { + $telephone = $this->t('@tel', $t_args); + } + return ['telephone' => ['#markup' => $telephone]]; + } + + /** + * {@inheritdoc} + */ + protected function formatTextItemValue(array $element, array $value) { + $t_args = [ + '@tel' => $value['phone'], + '@ext' => $value['ext'], + '@type' => $value['type'], + ]; + if ($value['ext'] && $value['type']) { + return ['telephone' => $this->t('@type: @tel x@ext', $t_args)]; + } + elseif ($value['ext']) { + return ['telephone' => $this->t('@tel x@ext', $t_args)]; + } + elseif ($value['type']) { + return ['telephone' => $this->t('@type: @tel', $t_args)]; + } + else { + return ['telephone' => $this->t('@tel', $t_args)]; + } + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + $form['composite']['phone__international'] = [ + '#title' => $this->t('Enhance support for international phone numbers'), + '#type' => 'checkbox', + '#return_value' => TRUE, + '#description' => $this->t('Enhance the telephone element\'s international support using the jQuery International Telephone Input plugin.', [':href' => 'http://intl-tel-input.com/']), + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTermSelect.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTermSelect.php new file mode 100644 index 000000000..0fca4169a --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTermSelect.php @@ -0,0 +1,127 @@ + '', + 'breadcrumb' => FALSE, + 'breadcrumb_delimiter' => ' › ', + 'tree_delimiter' => '-', + ]; + + unset($properties['options']); + unset($properties['options_randomize']); + return $properties; + } + + /** + * {@inheritdoc} + */ + public function getRelatedTypes(array $element) { + return []; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + $form['term_reference'] = [ + '#type' => 'fieldset', + '#title' => t('Term reference settings'), + '#weight' => -40, + ]; + $form['term_reference']['vocabulary'] = [ + '#type' => 'webform_entity_select', + '#title' => $this->t('Vocabulary'), + '#target_type' => 'taxonomy_vocabulary', + '#selection_handler' => 'default:taxonomy_vocabulary', + ]; + $form['term_reference']['breadcrumb'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Display term hierarchy using breadcrumbs.'), + '#return_value' => TRUE, + ]; + $form['term_reference']['breadcrumb_delimiter'] = [ + '#type' => 'textfield', + '#title' => $this->t('Breadcrumb delimiter'), + '#size' => 10, + '#states' => [ + 'visible' => [ + [':input[name="properties[breadcrumb]"]' => ['checked' => TRUE]], + 'or', + [':input[name="properties[format]"]' => ['value' => 'breadcrumb']], + ], + 'required' => [ + [':input[name="properties[breadcrumb]"]' => ['checked' => TRUE]], + 'or', + [':input[name="properties[format]"]' => ['value' => 'breadcrumb']], + ], + ], + ]; + $form['term_reference']['tree_delimiter'] = [ + '#type' => 'textfield', + '#title' => $this->t('Tree delimiter'), + '#size' => 10, + '#states' => [ + 'visible' => [ + ':input[name="properties[breadcrumb]"]' => [ + 'checked' => FALSE, + ], + ], + 'required' => [ + ':input[name="properties[breadcrumb]"]' => [ + 'checked' => FALSE, + ], + ], + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + // Overrides: \Drupal\webform\Plugin\WebformElement\WebformEntityReferenceTrait::validateConfigurationForm. + parent::validateConfigurationForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function getTargetType(array $element) { + return 'taxonomy_term'; + } + + /** + * {@inheritdoc} + */ + protected function setOptions(array &$element) { + TermSelectElement::setOptions($element); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTime.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTime.php new file mode 100644 index 000000000..5146d9c7c --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformTime.php @@ -0,0 +1,109 @@ + FALSE, + 'multiple__header_label' => '', + // Time settings. + 'time_format' => '', + 'min' => '', + 'max' => '', + 'step' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + if (empty($value)) { + return ''; + } + + $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 parent::formatTextItem($element, $value, $options); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + // Append supported time input format to #default_value description. + $form['element']['default_value']['#description'] .= '
          ' . $this->t('Accepts any time in any GNU Date Input Format. Strings such as now, +2 hours, and 4:30 PM are all valid.'); + + // Time. + $form['time'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Time settings'), + ]; + $form['time']['time_format'] = [ + '#type' => 'webform_select_other', + '#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. Time format is used to format the submitted value."), + '#options' => [ + 'g:i A' => $this->t('12 hour (@time)', ['@time' => date('g:i A')]), + 'g:i:s A' => $this->t('12 hour with seconds (@time)', ['@time' => date('g:i:s A')]), + 'H:i' => $this->t('24 hour (@time)', ['@time' => date('H:i')]), + 'H:i:s' => $this->t('24 hour with seconds (@time)', ['@time' => date('H:i:s')]), + ], + '#other__option_label' => $this->t('Custom...'), + '#other__placeholder' => $this->t('Custom time format...'), + '#other__description' => $this->t('Enter time format using Time Input Format.'), + ]; + $form['time']['min'] = [ + '#type' => 'webform_time', + '#title' => $this->t('Min'), + '#description' => $this->t('Specifies the minimum time.'), + ]; + $form['time']['max'] = [ + '#type' => 'webform_time', + '#title' => $this->t('Max'), + '#description' => $this->t('Specifies the maximum time.'), + ]; + $form['time']['step'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('Step'), + '#description' => $this->t('Specifies the minute intervals.'), + '#options' => [ + '' => $this->t('1 minute'), + 30 => $this->t('5 minutes'), + 600 => $this->t('10 minutes'), + 900 => $this->t('15 minutes'), + 1200 => $this->t('20 minutes'), + 1800 => $this->t('30 minutes'), + ], + '#other__type' => 'number', + '#other__description' => $this->t('Enter interval in seconds.'), + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggle.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggle.php new file mode 100644 index 000000000..3d2053a53 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggle.php @@ -0,0 +1,51 @@ + 'light', + 'toggle_size' => 'medium', + 'on_text' => '', + 'off_text' => '', + ]; + $properties['title_display'] = 'after'; + return $properties; + } + + /** + * {@inheritdoc} + */ + public function formatTextItem(array &$element, $value, array $options = []) { + $format = $this->getItemFormat($element); + + switch ($format) { + case 'value': + $on_text = (!empty($element['#on_text'])) ? $element['#on_text'] : $this->t('Yes'); + $off_text = (!empty($element['#off_text'])) ? $element['#off_text'] : $this->t('No'); + return ($value) ? $on_text : $off_text; + + case 'raw': + default: + return ($value) ? 1 : 0; + } + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggleTrait.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggleTrait.php new file mode 100644 index 000000000..af43a19f1 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggleTrait.php @@ -0,0 +1,61 @@ + 'fieldset', + '#title' => $this->t('toggle settings'), + ]; + $form['toggle']['toggle_theme'] = [ + '#type' => 'select', + '#title' => $this->t('Toggle theme'), + '#options' => [ + 'light' => $this->t('Light'), + 'dark' => $this->t('Dark'), + 'iphone' => $this->t('iPhone'), + 'modern' => $this->t('Modern'), + 'soft' => $this->t('Soft'), + ], + '#required' => TRUE, + ]; + $form['toggle']['toggle_size'] = [ + '#type' => 'select', + '#title' => $this->t('Toggle size'), + '#options' => [ + 'small' => $this->t('Small (@size)', ['@size' => '16px']), + 'medium' => $this->t('Medium (@size)', ['@size' => '24px']), + 'large' => $this->t('Large (@size)', ['@size' => '32px']), + ], + '#required' => TRUE, + ]; + $form['toggle']['on_text'] = [ + '#type' => 'textfield', + '#title' => $this->t('Toggle on text'), + ]; + $form['toggle']['off_text'] = [ + '#type' => 'textfield', + '#title' => $this->t('Toggle off text'), + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggles.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggles.php new file mode 100644 index 000000000..558896820 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformToggles.php @@ -0,0 +1,66 @@ + 'light', + 'toggle_size' => 'medium', + 'on_text' => '', + 'off_text' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function supportsMultipleValues() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function hasMultipleValues(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission) { + $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) { + $text .= ' [' . $this->t('Toggle') . ']'; + } + return $selectors; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformVideoFile.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformVideoFile.php new file mode 100644 index 000000000..3d2906b57 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformVideoFile.php @@ -0,0 +1,27 @@ +t('HTML5 Video player (MP4 only)'); + return $formats; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformElement/WebformWizardPage.php b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformWizardPage.php new file mode 100644 index 000000000..c49521a28 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformElement/WebformWizardPage.php @@ -0,0 +1,104 @@ + '', + 'open' => FALSE, + 'prev_button_label' => '', + 'next_button_label' => '', + ]; + } + + /** + * {@inheritdoc} + */ + public function getTranslatableProperties() { + return array_merge(parent::getTranslatableProperties(), ['prev_button_label', 'next_button_label']); + } + + /** + * {@inheritdoc} + */ + public function isInput(array $element) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function isContainer(array $element) { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function isRoot() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state) { + $form = parent::form($form, $form_state); + + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $form_state->getFormObject()->getWebform(); + + $form['wizard_page'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Page settings'), + ]; + $form['wizard_page']['prev_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Previous page button label'), + '#description' => $this->t('This is used for the Next Page button on the page before this page break.') . '
          ' . + $this->t('Defaults to: %value', ['%value' => $this->getDefaultSettings($webform, 'wizard_prev_button_label')]), + ]; + $form['wizard_page']['next_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Next page button label'), + '#description' => $this->t('This is used for the Previous Page button on the page after this page break.') . '
          ' . + $this->t('Defaults to: %value', ['%value' => $this->getDefaultSettings($webform, 'wizard_next_button_label')]), + ]; + return $form; + } + + /** + * Get default from webform or global settings. + * + * @param \Drupal\webform\WebformInterface $webform + * A webform. + * @param string $name + * The name of the setting. + * + * @return string + * The setting's value. + */ + protected function getDefaultSettings(WebformInterface $webform, $name) { + return $webform->getSetting($name) ?: \Drupal::config('webform.settings')->get("settings.default_$name"); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/DelimitedWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/DelimitedWebformExporter.php new file mode 100644 index 000000000..521880ec3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/DelimitedWebformExporter.php @@ -0,0 +1,107 @@ + ',', + ]; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + parent::setConfiguration($configuration); + if ($this->configuration['delimiter'] == '\t') { + $this->configuration['delimiter'] = "\t"; + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + if (isset($form['delimiter'])) { + return $form; + } + + $states = [ + 'visible' => [ + [':input.js-webform-exporter' => ['value' => 'delimited']], + ], + ]; + $form['warning'] = [ + '#type' => 'webform_message', + '#message_type' => 'warning', + '#message_message' => $this->t('Warning: Opening delimited text files with spreadsheet applications may expose you to formula injection or other security vulnerabilities. When the submissions contain data from untrusted users and the downloaded file will be used with spreadsheets, use Microsoft Excel format.', [':href' => 'https://www.google.com/search?q=spreadsheet+formula+injection']), + '#states' => $states, + ]; + $form['delimiter'] = [ + '#type' => 'select', + '#title' => $this->t('Delimiter text format'), + '#description' => $this->t('This is the delimiter used in the CSV/TSV file when downloading webform results. Using tabs in the export is the most reliable method for preserving non-latin characters. You may want to change this to another character depending on the program with which you anticipate importing results.'), + '#required' => TRUE, + '#options' => [ + ',' => $this->t('Comma (,)'), + '\t' => $this->t('Tab (\t)'), + ';' => $this->t('Semicolon (;)'), + ':' => $this->t('Colon (:)'), + '|' => $this->t('Pipe (|)'), + '.' => $this->t('Period (.)'), + ' ' => $this->t('Space ( )'), + ], + '#states' => $states, + '#default_value' => $this->configuration['delimiter'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function getFileExtension() { + switch ($this->configuration['delimiter']) { + case "\t": + return 'tsv'; + + default: + return 'csv'; + } + } + + /** + * {@inheritdoc} + */ + public function writeHeader() { + $header = $this->buildHeader(); + fputcsv($this->fileHandle, $header, $this->configuration['delimiter']); + } + + /** + * {@inheritdoc} + */ + public function writeSubmission(WebformSubmissionInterface $webform_submission) { + $record = $this->buildRecord($webform_submission); + fputcsv($this->fileHandle, $record, $this->configuration['delimiter']); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/DocumentBaseWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/DocumentBaseWebformExporter.php new file mode 100644 index 000000000..a596184b5 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/DocumentBaseWebformExporter.php @@ -0,0 +1,47 @@ + 'submission-[webform_submission:serial]', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + if (isset($form['file_name'])) { + return $form; + } + + $form['file_name'] = [ + '#type' => 'textfield', + '#title' => $this->t('File name'), + '#description' => $this->t('Submission file names must be unique.'), + '#required' => TRUE, + '#default_value' => $this->configuration['file_name'], + '#states' => [ + 'visible' => [ + [':input.js-webform-exporter' => ['value' => 'json']], + 'or', + [':input.js-webform-exporter' => ['value' => 'yaml']], + ], + ], + ]; + return $form; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/FileHandleTraitWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/FileHandleTraitWebformExporter.php new file mode 100644 index 000000000..d0eac5b05 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/FileHandleTraitWebformExporter.php @@ -0,0 +1,38 @@ +fileHandle = fopen($this->getExportFilePath(), 'w'); + } + + /** + * {@inheritdoc} + */ + public function openExport() { + $this->fileHandle = fopen($this->getExportFilePath(), 'a'); + } + + /** + * {@inheritdoc} + */ + public function closeExport() { + fclose($this->fileHandle); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/JsonWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/JsonWebformExporter.php new file mode 100644 index 000000000..11cae0e3d --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/JsonWebformExporter.php @@ -0,0 +1,33 @@ +getSubmissionBaseName($webform_submission) . '.json'; + $json = Json::encode($webform_submission->toArray(TRUE, TRUE)); + + $archiver = new ArchiveTar($this->getArchiveFilePath(), 'gz'); + $archiver->addString($file_name, $json); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/TableWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/TableWebformExporter.php new file mode 100644 index 000000000..c38bd5c4f --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/TableWebformExporter.php @@ -0,0 +1,127 @@ + FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + if (isset($form['excel'])) { + return $form; + } + + $form['excel'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Open HTML table in Excel'), + '#description' => $this->t('If checked, the download file extension will be change from .html to .xls.'), + '#default_value' => $this->configuration['excel'], + '#states' => [ + 'visible' => [ + [':input.js-webform-exporter' => ['value' => 'table']], + ], + ], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function writeHeader() { + $header = $this->buildHeader(); + + $file_handle = $this->fileHandle; + + if ($this->configuration['source_entity']) { + $title = $this->configuration['source_entity']->label(); + } + elseif ($this->configuration['webform']) { + $title = $this->configuration['webform']->label(); + } + else { + $title = ''; + } + + $thead = []; + foreach ($header as $item) { + $thead[] = '' . htmlentities($item) . ''; + } + + fwrite($file_handle, ''); + fwrite($file_handle, ''); + fwrite($file_handle, ''); + fwrite($file_handle, ''); + if ($title) { + fwrite($file_handle, '' . $title . ''); + } + fwrite($file_handle, ''); + fwrite($file_handle, ''); + + fwrite($file_handle, ''); + fwrite($file_handle, ''); + fwrite($file_handle, implode(PHP_EOL, $thead)); + fwrite($file_handle, ''); + fwrite($file_handle, ''); + } + + /** + * {@inheritdoc} + */ + public function writeSubmission(WebformSubmissionInterface $webform_submission) { + $record = $this->buildRecord($webform_submission); + + $file_handle = $this->fileHandle; + + $row = []; + foreach ($record as $item) { + $row[] = ''; + } + + fwrite($file_handle, ''); + fwrite($file_handle, implode(PHP_EOL, $row)); + fwrite($file_handle, ''); + } + + /** + * {@inheritdoc} + */ + public function writeFooter() { + $file_handle = $this->fileHandle; + + fwrite($file_handle, ''); + fwrite($file_handle, '
          ' . nl2br(htmlentities($item)) . '
          '); + fwrite($file_handle, ''); + fwrite($file_handle, ''); + } + + /** + * {@inheritdoc} + */ + public function getFileExtension() { + return ($this->configuration['excel']) ? 'xls' : 'html'; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php new file mode 100644 index 000000000..bf3ef8ffa --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/TabularBaseWebformExporter.php @@ -0,0 +1,211 @@ +getConfiguration(); + $this->fieldDefinitions = $this->getFieldDefinitions(); + $elements = $this->getElements(); + + $header = []; + foreach ($this->fieldDefinitions as $field_definition) { + // Build a webform element for each field definition so that we can + // use WebformElement::buildExportHeader(array $element, $export_options). + $element = [ + '#type' => ($field_definition['type'] == 'entity_reference') ? 'entity_autocomplete' : 'element', + '#admin_title' => '', + '#title' => (string) $field_definition['title'], + '#webform_key' => (string) $field_definition['name'], + ]; + $header = array_merge($header, $this->elementManager->invokeMethod('buildExportHeader', $element, $export_options)); + } + + // Build element columns headers. + foreach ($elements as $element) { + $header = array_merge($header, $this->elementManager->invokeMethod('buildExportHeader', $element, $export_options)); + } + return $header; + } + + /****************************************************************************/ + // Record. + /****************************************************************************/ + + /** + * Build export record using a webform submission. + * + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * + * @return array + * An array containing the export record. + */ + protected function buildRecord(WebformSubmissionInterface $webform_submission) { + $export_options = $this->getConfiguration(); + $this->fieldDefinitions = $this->getFieldDefinitions(); + $elements = $this->getElements(); + + $record = []; + + // Build record field definition columns. + foreach ($this->fieldDefinitions as $field_definition) { + $this->formatRecordFieldDefinitionValue($record, $webform_submission, $field_definition); + } + + // Build record element columns. + $data = $webform_submission->getData(); + foreach ($elements as $column_name => $element) { + $value = (isset($data[$column_name])) ? $data[$column_name] : ''; + $record = array_merge($record, $this->elementManager->invokeMethod('buildExportRecord', $element, $value, $export_options)); + } + return $record; + } + + /** + * Get the field definition value from a webform submission entity. + * + * @param array $record + * The record to be added to the export file. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * A webform submission. + * @param array $field_definition + * The field definition for the value. + */ + protected function formatRecordFieldDefinitionValue(array &$record, WebformSubmissionInterface $webform_submission, array $field_definition) { + $export_options = $this->getConfiguration(); + + $field_name = $field_definition['name']; + $field_type = $field_definition['type']; + switch ($field_type) { + case 'created': + case 'changed': + $record[] = date('Y-m-d H:i:s', $webform_submission->get($field_name)->value); + break; + + case 'entity_reference': + $element = [ + '#type' => 'entity_autocomplete', + '#target_type' => $field_definition['target_type'], + ]; + $value = $webform_submission->get($field_name)->target_id; + $record = array_merge($record, $this->elementManager->invokeMethod('buildExportRecord', $element, $value, $export_options)); + break; + + case 'entity_url': + case 'entity_title': + if (empty($webform_submission->entity_type->value) || empty($webform_submission->entity_id->value)) { + $record[] = ''; + break; + } + $entity_type = $webform_submission->entity_type->value; + $entity_id = $webform_submission->entity_id->value; + $entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id); + if ($entity) { + $record[] = ($field_type == 'entity_url' && $entity->hasLinkTemplate('canonical')) ? $entity->toUrl()->setOption('absolute', TRUE)->toString() : $entity->label(); + } + else { + $record[] = ''; + } + break; + + default: + $record[] = $webform_submission->get($field_name)->value; + break; + } + } + + /****************************************************************************/ + // Webform definitions and elements. + /****************************************************************************/ + + /** + * Get a webform's field definitions. + * + * @return array + * An associative array containing a webform's field definitions. + */ + protected function getFieldDefinitions() { + if (isset($this->fieldDefinitions)) { + return $this->fieldDefinitions; + } + + $export_options = $this->getConfiguration(); + + $this->fieldDefinitions = $this->entityStorage->getFieldDefinitions(); + $this->fieldDefinitions = $this->entityStorage->checkFieldDefinitionAccess($this->getWebform(), $this->fieldDefinitions); + if ($export_options['excluded_columns']) { + $this->fieldDefinitions = array_diff_key($this->fieldDefinitions, $export_options['excluded_columns']); + } + + // Add custom entity reference field definitions which rely on the + // entity type and entity id. + if ($export_options['entity_reference_format'] == 'link' && isset($this->fieldDefinitions['entity_type']) && isset($this->fieldDefinitions['entity_id'])) { + $this->fieldDefinitions['entity_title'] = [ + 'name' => 'entity_title', + 'title' => t('Submitted to: Entity title'), + 'type' => 'entity_title', + ]; + $this->fieldDefinitions['entity_url'] = [ + 'name' => 'entity_url', + 'title' => t('Submitted to: Entity URL'), + 'type' => 'entity_url', + ]; + } + + return $this->fieldDefinitions; + } + + /** + * Get webform elements. + * + * @return array + * An associative array containing webform elements keyed by name. + */ + protected function getElements() { + if (isset($this->elements)) { + return $this->elements; + } + + $export_options = $this->getConfiguration(); + $this->elements = $this->getWebform()->getElementsInitializedFlattenedAndHasValue('view'); + if ($export_options['excluded_columns']) { + $this->elements = array_diff_key($this->elements, $export_options['excluded_columns']); + } + return $this->elements; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformExporter/YamlWebformExporter.php b/web/modules/contrib/webform/src/Plugin/WebformExporter/YamlWebformExporter.php new file mode 100644 index 000000000..fa90011f6 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformExporter/YamlWebformExporter.php @@ -0,0 +1,35 @@ +getSubmissionBaseName($webform_submission) . '.yml'; + $yaml = Yaml::encode($webform_submission->toArray(TRUE, TRUE)); + $yaml = WebformYaml::tidy($yaml); + + $archiver = new ArchiveTar($this->getArchiveFilePath(), 'gz'); + $archiver->addString($file_name, $yaml); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformHandler/BrokenWebformHandler.php b/web/modules/contrib/webform/src/Plugin/WebformHandler/BrokenWebformHandler.php new file mode 100644 index 000000000..8125e8070 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformHandler/BrokenWebformHandler.php @@ -0,0 +1,40 @@ + $this->getLabel(), '@id' => $this->getHandlerId()]; + return [ + 'message' => [ + '#markup' => $this->t('This handler is broken or missing. You might need to enable the original module.', $t_args), + ], + ]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformHandler/DebugWebformHandler.php b/web/modules/contrib/webform/src/Plugin/WebformHandler/DebugWebformHandler.php new file mode 100644 index 000000000..35643fc74 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformHandler/DebugWebformHandler.php @@ -0,0 +1,33 @@ + 'Submitted values are:
          ' . WebformYaml::tidy(Yaml::encode($webform_submission->getData())) . '
          ']; + drupal_set_message(\Drupal::service('renderer')->render($build), 'warning'); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformHandler/EmailWebformHandler.php b/web/modules/contrib/webform/src/Plugin/WebformHandler/EmailWebformHandler.php new file mode 100644 index 000000000..83da81ac8 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformHandler/EmailWebformHandler.php @@ -0,0 +1,752 @@ +mailManager = $mail_manager; + $this->configFactory = $config_factory; + $this->tokenManager = $token_manager; + $this->elementManager = $element_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')->get('webform.email'), + $container->get('plugin.manager.mail'), + $container->get('config.factory'), + $container->get('webform.token_manager'), + $container->get('plugin.manager.webform.element') + ); + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return [ + '#settings' => $this->getEmailConfiguration(), + ] + parent::getSummary(); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'to_mail' => 'default', + 'cc_mail' => '', + 'bcc_mail' => '', + 'from_mail' => 'default', + 'from_name' => 'default', + 'subject' => 'default', + 'body' => 'default', + 'excluded_elements' => [], + 'html' => TRUE, + 'attachments' => FALSE, + 'debug' => FALSE, + ]; + } + + /** + * Get configuration default values. + * + * @return array + * Configuration default values. + */ + protected function getDefaultConfigurationValues() { + if (isset($this->defaultValues)) { + return $this->defaultValues; + } + + $webform_settings = $this->configFactory->get('webform.settings'); + $site_settings = $this->configFactory->get('system.site'); + $body_format = ($this->configuration['html']) ? 'html' : 'text'; + $default_mail = $webform_settings->get('mail.default_to_mail') ?: $site_settings->get('mail') ?: ini_get('sendmail_from'); + + $this->defaultValues = [ + 'to_mail' => $default_mail, + 'cc_mail' => $default_mail, + 'bcc_mail' => $default_mail, + 'from_mail' => $default_mail, + 'from_name' => $webform_settings->get('mail.default_from_name') ?: $site_settings->get('name'), + 'subject' => $webform_settings->get('mail.default_subject') ?: 'Webform submission from: [webform_submission:source-entity]', + 'body' => $this->getBodyDefaultValues($body_format), + ]; + + return $this->defaultValues; + } + + /** + * Get configuration default value. + * + * @param string $name + * Configuration name. + * + * @return string|array + * Configuration default value. + */ + protected function getDefaultConfigurationValue($name) { + $default_values = $this->getDefaultConfigurationValues(); + return $default_values[$name]; + } + + /** + * Get mail configuration values. + * + * @return array + * An associative array containing email configuration values. + */ + protected function getEmailConfiguration() { + $configuration = $this->getConfiguration(); + $email = []; + foreach ($configuration['settings'] as $key => $value) { + if ($value === 'default') { + $email[$key] = $this->getDefaultConfigurationValue($key); + } + else { + $email[$key] = $value; + } + } + return $email; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $mail_element_options = []; + $text_element_options = []; + $elements = $this->webform->getElementsInitializedAndFlattened(); + foreach ($elements as $key => $element) { + $title = (isset($element['#title'])) ? new FormattableMarkup('@title (@key)', ['@title' => $element['#title'], '@key' => $key]) : $key; + if (isset($element['#type']) && in_array($element['#type'], ['email', 'hidden', 'value', 'select', 'radios', 'textfield', 'webform_email_multiple', 'webform_email_confirm'])) { + // Note: Token must use the :raw webform mail elements. + // For example a select menu's option value would be used to route an + // email address. + $mail_element_options["[webform_submission:values:$key:raw]"] = $title; + } + $text_element_options["[webform_submission:values:$key:value]"] = $title; + } + + $default_optgroup = (string) $this->t('Default'); + $elements_optgroup = (string) $this->t('Elements'); + + // 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 + $form['#attributes']['novalidate'] = 'novalidate'; + + // To. + $form['to'] = [ + '#type' => 'details', + '#title' => $this->t('Send to'), + '#open' => TRUE, + ]; + $form['to']['to_mail'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('To email'), + '#options' => [ + WebformSelectOther::OTHER_OPTION => $this->t('Custom to email address...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('to_mail')], + $elements_optgroup => $mail_element_options, + ], + '#other__placeholder' => $this->t('Enter to email address...'), + '#other__type' => 'webform_email_multiple', + '#other__allow_tokens' => TRUE, + '#required' => TRUE, + '#parents' => ['settings', 'to_mail'], + '#default_value' => $this->configuration['to_mail'], + ]; + $form['to']['cc_mail'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('CC email'), + '#options' => [ + '' => '', + WebformSelectOther::OTHER_OPTION => $this->t('Custom CC email address...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('cc_mail')], + $elements_optgroup => $mail_element_options, + ], + '#other__placeholder' => $this->t('Enter CC email address...'), + '#other__type' => 'webform_email_multiple', + '#parents' => ['settings', 'cc_mail'], + '#other__allow_tokens' => TRUE, + '#default_value' => $this->configuration['cc_mail'], + ]; + $form['to']['bcc_mail'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('BCC email'), + '#options' => [ + '' => '', + WebformSelectOther::OTHER_OPTION => $this->t('Custom BCC email address...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('bcc_mail')], + $elements_optgroup => $mail_element_options, + ], + '#other__placeholder' => $this->t('Enter BCC email address...'), + '#other__type' => 'webform_email_multiple', + '#other__allow_tokens' => TRUE, + '#parents' => ['settings', 'bcc_mail'], + '#default_value' => $this->configuration['bcc_mail'], + ]; + + // From. + $form['from'] = [ + '#type' => 'details', + '#title' => $this->t('Send from'), + '#open' => TRUE, + ]; + $form['from']['from_mail'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('From email'), + '#options' => [ + WebformSelectOther::OTHER_OPTION => $this->t('Custom from email address...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('from_mail')], + $elements_optgroup => $mail_element_options, + ], + '#other__placeholder' => $this->t('Enter from email address...'), + '#other__type' => 'webform_email_multiple', + '#other__allow_tokens' => TRUE, + '#required' => TRUE, + '#parents' => ['settings', 'from_mail'], + '#default_value' => $this->configuration['from_mail'], + ]; + $form['from']['from_name'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('From name'), + '#options' => [ + '' => '', + WebformSelectOther::OTHER_OPTION => $this->t('Custom from name...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('from_name')], + $elements_optgroup => $text_element_options, + ], + '#other__placeholder' => $this->t('Enter from name...'), + '#parents' => ['settings', 'from_name'], + '#default_value' => $this->configuration['from_name'], + ]; + + // Message. + $form['message'] = [ + '#type' => 'details', + '#title' => $this->t('Message'), + '#open' => TRUE, + ]; + $form['message']['subject'] = [ + '#type' => 'webform_select_other', + '#title' => $this->t('Subject'), + '#options' => [ + WebformSelectOther::OTHER_OPTION => $this->t('Custom subject...'), + $default_optgroup => ['default' => $this->getDefaultConfigurationValue('subject')], + $elements_optgroup => $text_element_options, + ], + '#other__placeholder' => $this->t('Enter subject...'), + '#required' => TRUE, + '#parents' => ['settings', 'subject'], + '#default_value' => $this->configuration['subject'], + ]; + + // Body. + // Building a custom select other element that toggles between + // HTML (CKEditor) and Plain text (CodeMirror) custom body elements. + $body_options = [ + WebformSelectOther::OTHER_OPTION => $this->t('Custom body...'), + 'default' => $this->t('Default'), + $elements_optgroup => $text_element_options, + ]; + + $body_default_format = ($this->configuration['html']) ? 'html' : 'text'; + $body_default_values = $this->getBodyDefaultValues(); + if (isset($body_options[$this->configuration['body']])) { + $body_default_value = $this->configuration['body']; + $body_custom_default_value = $body_default_values[$body_default_format]; + } + else { + $body_default_value = WebformSelectOther::OTHER_OPTION; + $body_custom_default_value = $this->configuration['body']; + } + $form['message']['body'] = [ + '#type' => 'select', + '#title' => $this->t('Body'), + '#options' => $body_options, + '#required' => TRUE, + '#parents' => ['settings', 'body'], + '#default_value' => $body_default_value, + ]; + foreach ($body_default_values as $format => $default_value) { + // Custom body. + $custom_default_value = ($format === $body_default_format) ? $body_custom_default_value : $default_value; + if ($format == 'html') { + $form['message']['body_custom_' . $format] = [ + '#type' => 'webform_html_editor', + ]; + } + else { + $form['message']['body_custom_' . $format] = [ + '#type' => 'webform_codemirror', + '#mode' => $format, + ]; + } + $form['message']['body_custom_' . $format] += [ + '#title' => $this->t('Body custom value (@format)', ['@label' => $format]), + '#title_display' => 'hidden', + '#parents' => ['settings', 'body_custom_' . $format], + '#default_value' => $custom_default_value, + '#states' => [ + 'visible' => [ + ':input[name="settings[body]"]' => ['value' => WebformSelectOther::OTHER_OPTION], + ':input[name="settings[html]"]' => ['checked' => ($format == 'html') ? TRUE : FALSE], + ], + 'required' => [ + ':input[name="settings[body]"]' => ['value' => WebformSelectOther::OTHER_OPTION], + ':input[name="settings[html]"]' => ['checked' => ($format == 'html') ? TRUE : FALSE], + ], + ], + ]; + + // Default body. + $form['message']['body_default_' . $format] = [ + '#type' => 'webform_codemirror', + '#mode' => $format, + '#title' => $this->t('Body default value (@format)', ['@label' => $format]), + '#title_display' => 'hidden', + '#default_value' => $default_value, + '#attributes' => ['readonly' => 'readonly', 'disabled' => 'disabled'], + '#states' => [ + 'visible' => [ + ':input[name="settings[body]"]' => ['value' => 'default'], + ':input[name="settings[html]"]' => ['checked' => ($format == 'html') ? TRUE : FALSE], + ], + ], + ]; + } + $form['message']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + + // Elements. + $form['elements'] = [ + '#type' => 'details', + '#title' => $this->t('Included email values'), + '#open' => $this->configuration['excluded_elements'] ? TRUE : FALSE, + ]; + $form['elements']['excluded_elements'] = [ + '#type' => 'webform_excluded_elements', + '#description' => $this->t('The selected elements will be included in the [webform_submission:values] token. Individual values may still be printed if explicitly specified as a [webform_submission:values:?] in the email body template.'), + '#webform' => $this->webform, + '#default_value' => $this->configuration['excluded_elements'], + '#parents' => ['settings', 'excluded_elements'], + ]; + + // Settings. + $form['settings'] = [ + '#type' => 'details', + '#title' => $this->t('Settings'), + ]; + $form['settings']['html'] = [ + '#type' => 'checkbox', + '#title' => t('Send email as HTML'), + '#return_value' => TRUE, + '#access' => $this->supportsHtml(), + '#parents' => ['settings', 'html'], + '#default_value' => $this->configuration['html'], + ]; + $form['settings']['attachments'] = [ + '#type' => 'checkbox', + '#title' => t('Include files as attachments'), + '#return_value' => TRUE, + '#access' => $this->supportsAttachments(), + '#parents' => ['settings', 'attachments'], + '#default_value' => $this->configuration['attachments'], + ]; + $form['settings']['debug'] = [ + '#type' => 'checkbox', + '#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'], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $values = $form_state->getValues(); + + // Set custom body based on the selected format. + if ($values['body'] === WebformSelectOther::OTHER_OPTION) { + $body_format = ($values['html']) ? 'html' : 'text'; + $values['body'] = $values['body_custom_' . $body_format]; + } + unset( + $values['body_custom_text'], + $values['body_default_html'] + ); + + foreach ($this->configuration as $name => $value) { + if (isset($values[$name])) { + $this->configuration[$name] = $values[$name]; + } + } + } + + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + $is_results_disabled = $webform_submission->getWebform()->getSetting('results_disabled'); + $is_completed = ($webform_submission->getState() == WebformSubmissionInterface::STATE_COMPLETED); + if ($is_results_disabled || $is_completed) { + $message = $this->getMessage($webform_submission); + $this->sendMessage($message); + } + } + + /** + * {@inheritdoc} + */ + public function getMessage(WebformSubmissionInterface $webform_submission) { + $token_data = [ + 'webform-submission-options' => [ + 'email' => TRUE, + 'excluded_elements' => $this->configuration['excluded_elements'], + 'html' => ($this->configuration['html'] && $this->supportsHtml()), + ], + ]; + + $message = $this->configuration; + + // Replace 'default' values and [tokens] with configuration default values. + foreach ($message as $key => $value) { + if ($value === 'default') { + $message[$key] = $this->getDefaultConfigurationValue($key); + } + if (is_string($message[$key])) { + $message[$key] = $this->tokenManager->replace($message[$key], $webform_submission, $token_data); + } + } + + // Trim the message body. + $message['body'] = trim($message['body']); + + // Alter body based on the mail system sender. + if ($this->configuration['html'] && $this->supportsHtml()) { + switch ($this->getMailSystemSender()) { + case 'swiftmailer': + // SwiftMailer requires that the body be valid Markup. + $message['body'] = Markup::create($message['body']); + break; + } + } + else { + // Since Drupal might be rendering a token into the body as markup + // we need to decode all HTML entities which are being sent as plain text. + $message['body'] = html_entity_decode($message['body']); + } + + // Add attachments. + $message['attachments'] = []; + if ($this->configuration['attachments'] && $this->supportsAttachments()) { + $elements = $this->webform->getElementsInitializedAndFlattened(); + foreach ($elements as $key => $element) { + $element_handler = $this->elementManager->getElementInstance($element); + // Only elements that extend the 'Managed file' element can add + // file attachments. + if (!($element_handler instanceof WebformManagedFileBase)) { + continue; + } + + // Check if the element is excluded and should not attach any files. + if (isset($this->configuration['excluded_elements'][$key])) { + continue; + } + + // Get file ids. + $fids = $webform_submission->getData($key); + if (empty($fids)) { + continue; + } + + /** @var \Drupal\file\FileInterface[] $files */ + $files = File::loadMultiple(is_array($fids) ? $fids : [$fids]); + foreach ($files as $file) { + $filepath = \Drupal::service('file_system')->realpath($file->getFileUri()); + $message['attachments'][] = [ + 'filecontent' => file_get_contents($filepath), + 'filename' => $file->getFilename(), + 'filemime' => $file->getMimeType(), + // Add URL to be used by resend webform. + 'file' => $file, + ]; + } + } + } + + // Add webform submission. + $message['webform_submission'] = $webform_submission; + + return $message; + } + + /** + * {@inheritdoc} + */ + public function sendMessage(array $message) { + // Send mail. + $to = $message['to_mail']; + $from = $message['from_mail'] . (($message['from_name']) ? ' <' . $message['from_name'] . '>' : ''); + $current_langcode = \Drupal::languageManager()->getCurrentLanguage()->getId(); + $this->mailManager->mail('webform', 'email.' . $this->getHandlerId(), $to, $current_langcode, $message, $from); + + // Log message. + $context = [ + '@form' => $this->getWebform()->label(), + '@title' => $this->label(), + ]; + $this->logger->notice('@form webform sent @title email.', $context); + + // Debug by displaying send email onscreen. + if ($this->configuration['debug']) { + $t_args = [ + '%from_name' => $message['from_name'], + '%from_mail' => $message['from_mail'], + '%to_mail' => $message['to_mail'], + '%subject' => $message['subject'], + ]; + $build = []; + $build['message'] = [ + '#markup' => $this->t('%subject sent to %to_mail from %from_name [%from_mail].', $t_args), + '#prefix' => '', + '#suffix' => '', + ]; + if ($message['html']) { + $build['body'] = [ + '#markup' => $message['body'], + '#allowed_tags' => Xss::getAdminTagList(), + '#prefix' => '
          ', + '#suffix' => '
          ', + ]; + } + else { + $build['body'] = [ + '#markup' => $message['body'], + '#prefix' => '
          ',
          +          '#suffix' => '
          ', + ]; + } + drupal_set_message(\Drupal::service('renderer')->render($build), 'warning'); + } + } + + /** + * {@inheritdoc} + */ + public function resendMessageForm(array $message) { + $element = []; + $element['to_mail'] = [ + '#type' => 'webform_email_multiple', + '#title' => $this->t('To email'), + '#default_value' => $message['to_mail'], + ]; + $element['from_mail'] = [ + '#type' => 'webform_email_multiple', + '#title' => $this->t('From email'), + '#required' => TRUE, + '#default_value' => $message['from_mail'], + ]; + $element['from_name'] = [ + '#type' => 'textfield', + '#title' => $this->t('From name'), + '#required' => TRUE, + '#default_value' => $message['from_name'], + ]; + $element['subject'] = [ + '#type' => 'textfield', + '#title' => $this->t('Subject'), + '#default_value' => $message['subject'], + ]; + $element['body'] = [ + '#type' => ($message['html']) ? 'webform_html_editor' : 'webform_codemirror', + '#title' => $this->t('Message'), + '#required' => TRUE, + '#default_value' => $message['body'], + ]; + $element['html'] = [ + '#type' => 'value', + '#value' => $message['html'], + ]; + $element['attachments'] = [ + '#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' => '
          ', + '#suffix' => '
          ', + ]; + } + $element['files'] = [ + '#type' => 'item', + '#title' => $this->t('Attachments'), + '#markup' => \Drupal::service('renderer')->render($file_links), + ]; + } + + // Preload HTML Editor and CodeMirror so that they can be properly + // initialized when loaded via AJAX. + $element['#attached']['library'][] = 'webform/webform.element.html_editor'; + $element['#attached']['library'][] = 'webform/webform.element.codemirror.text'; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function getMessageSummary(array $message) { + return [ + '#settings' => $message, + ] + parent::getSummary(); + } + + /** + * Check that HTML emails are supported. + * + * @return bool + * TRUE if HTML email is supported. + */ + protected function supportsHtml() { + return TRUE; + } + + /** + * Check that emailing files as attachments is supported. + * + * @return bool + * 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 (\Drupal::configFactory()->get('system.mail')->get('interface.default') == 'test_mail_collector') { + return TRUE; + } + return \Drupal::moduleHandler()->moduleExists('mailsystem'); + } + + /** + * Get the Mail System's sender module name. + * + * @return string + * The Mail System's sender module name. + */ + protected function getMailSystemSender() { + $mailsystem_config = $this->configFactory->get('mailsystem.settings'); + $mailsystem_sender = $mailsystem_config->get('webform.sender') ?: $mailsystem_config->get('defaults.sender'); + return $mailsystem_sender; + } + + /** + * Get message body default values, which can be formatted as text or html. + * + * @param string $format + * If a format (text or html) is provided the default value for the + * specified format is return. If no format is specified an associative + * array containing the text and html default body values will be returned. + * + * @return string|array + * A single (text or html) default body value or an associative array + * containing both the text and html default body values. + */ + protected function getBodyDefaultValues($format = NULL) { + $webform_settings = $this->configFactory->get('webform.settings'); + $formats = [ + 'text' => $webform_settings->get('mail.default_body_text') ?: '[webform_submission:values]', + 'html' => $webform_settings->get('mail.default_body_html') ?: '[webform_submission:values]', + ]; + return ($format === NULL) ? $formats : $formats[$format]; + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php b/web/modules/contrib/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php new file mode 100644 index 000000000..3ec29ce43 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/WebformHandler/RemotePostWebformHandler.php @@ -0,0 +1,459 @@ +moduleHandler = $module_handler; + $this->httpClient = $http_client; + $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')->get('webform.remote_post'), + $container->get('module_handler'), + $container->get('http_client'), + $container->get('webform.token_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + $configuration = $this->getConfiguration(); + + // If the saving of results is disabled clear update and delete URL. + if ($this->getWebform()->getSetting('results_disabled')) { + $configuration['settings']['update_url'] = ''; + $configuration['settings']['delete_url'] = ''; + } + + return [ + '#settings' => $configuration['settings'], + ] + parent::getSummary(); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $field_names = array_keys(\Drupal::service('entity_field.manager')->getBaseFieldDefinitions('webform_submission')); + $excluded_data = array_combine($field_names, $field_names); + return [ + 'type' => 'x-www-form-urlencoded', + 'insert_url' => '', + 'update_url' => '', + 'delete_url' => '', + 'excluded_data' => $excluded_data, + 'custom_data' => '', + 'insert_custom_data' => '', + 'update_custom_data' => '', + 'delete_custom_data' => '', + 'debug' => FALSE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $webform = $this->getWebform(); + $results_disabled = $webform->getSetting('results_disabled'); + + $form['insert_url'] = [ + '#type' => 'url', + '#title' => $this->t('Insert URL'), + '#description' => $this->t('The full URL to POST to when a new webform submission is saved. E.g. http://www.mycrm.com/form_insert_handler.php'), + '#required' => TRUE, + '#default_value' => $this->configuration['insert_url'], + ]; + + $form['update_url'] = [ + '#type' => 'url', + '#title' => $this->t('Update URL'), + '#description' => $this->t('The full URL to POST to when an existing webform submission is updated. E.g. http://www.mycrm.com/form_insert_handler.php'), + '#default_value' => $this->configuration['update_url'], + '#access' => !$results_disabled, + ]; + + $form['delete_url'] = [ + '#type' => 'url', + '#title' => $this->t('Delete URL'), + '#description' => $this->t('The full URL to POST to call when a webform submission is deleted. E.g. http://www.mycrm.com/form_delete_handler.php'), + '#default_value' => $this->configuration['delete_url'], + '#access' => !$results_disabled, + ]; + + $form['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 JSON format.'), + '#options' => [ + 'x-www-form-urlencoded' => $this->t('x-www-form-urlencoded'), + 'json' => $this->t('JSON'), + ], + '#required' => TRUE, + '#default_value' => $this->configuration['type'], + ]; + + $form['submission_data'] = [ + '#type' => 'details', + '#title' => $this->t('Submission data'), + ]; + $form['submission_data']['excluded_data'] = [ + '#type' => 'webform_excluded_columns', + '#title' => $this->t('Posted data'), + '#title_display' => 'invisible', + '#webform' => $webform, + '#required' => TRUE, + '#parents' => ['settings', 'excluded_data'], + '#default_value' => $this->configuration['excluded_data'], + ]; + + $form['custom_data'] = [ + '#type' => 'details', + '#title' => $this->t('Custom data'), + '#description' => $this->t('Custom data will take precedence over submission data. You may use tokens.'), + ]; + + $form['custom_data']['custom_data'] = [ + '#type' => 'webform_codemirror', + '#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['custom_data']['insert_custom_data'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Insert data'), + '#description' => $this->t("Enter custom data that will be included when a new webform submission is saved."), + '#parents' => ['settings', 'insert_custom_data'], + '#states' => [ + 'visible' => [ + [':input[name="settings[update_url]"]' => ['filled' => TRUE]], + 'or', + [':input[name="settings[delete_url]"]' => ['filled' => TRUE]], + ], + ], + '#default_value' => $this->configuration['insert_custom_data'], + ]; + $form['custom_data']['update_custom_data'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Update data'), + '#description' => $this->t("Enter custom data that will be included when a webform submission is updated."), + '#parents' => ['settings', 'update_custom_data'], + '#states' => ['visible' => [':input[name="settings[update_url]"]' => ['filled' => TRUE]]], + '#default_value' => $this->configuration['update_custom_data'], + ]; + $form['custom_data']['delete_custom_data'] = [ + '#type' => 'webform_codemirror', + '#mode' => 'yaml', + '#title' => $this->t('Delete data'), + '#description' => $this->t("Enter custom data that will be included when a webform submission is deleted."), + '#parents' => ['settings', 'delete_custom_data'], + '#states' => ['visible' => [':input[name="settings[delete_url]"]' => ['filled' => TRUE]]], + '#default_value' => $this->configuration['delete_custom_data'], + ]; + $form['custom_data']['token_tree_link'] = $this->tokenManager->buildTreeLink(); + + $form['debug'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable debugging'), + '#description' => $this->t('If checked, posted submissions will be displayed onscreen to all users.'), + '#return_value' => TRUE, + '#default_value' => $this->configuration['debug'], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $values = $form_state->getValues(); + foreach ($this->configuration as $name => $value) { + if (isset($values[$name])) { + $this->configuration[$name] = $values[$name]; + } + } + } + + /** + * {@inheritdoc} + */ + public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { + $operation = ($update) ? 'update' : 'insert'; + $this->remotePost($operation, $webform_submission); + } + + /** + * {@inheritdoc} + */ + public function postDelete(WebformSubmissionInterface $webform_submission) { + $this->remotePost('delete', $webform_submission); + } + + /** + * Execute a remote post. + * + * @param string $operation + * The type of webform submission operation to be posted. Can be 'insert', + * 'update', or 'delete'. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * The webform submission to be posted. + */ + protected function remotePost($operation, WebformSubmissionInterface $webform_submission) { + $request_url = $this->configuration[$operation . '_url']; + if (empty($request_url)) { + return; + } + + $request_type = $this->configuration['type']; + $request_post_data = $this->getPostData($operation, $webform_submission); + + try { + switch ($request_type) { + case 'json': + $response = $this->httpClient->post($request_url, ['json' => $request_post_data]); + break; + + case 'x-www-form-urlencoded': + default: + $response = $this->httpClient->post($request_url, ['form_params' => $request_post_data]); + break; + } + } + catch (RequestException $request_exception) { + $message = $request_exception->getMessage(); + $response = $request_exception->getResponse(); + + // Encode HTML entities to prevent broken markup from breaking the page. + $message = nl2br(htmlentities($message)); + + // If debugging is enabled, display the error message on screen. + $this->debug($message, $operation, $request_url, $request_type, $request_post_data, $response, 'error'); + + // Log error message. + $context = [ + '@form' => $this->getWebform()->label(), + '@operation' => $operation, + '@type' => $request_type, + '@url' => $request_url, + '@message' => $message, + 'link' => $this->getWebform()->toLink(t('Edit'), 'handlers-form')->toString(), + ]; + $this->logger->error('@form webform remote @type post (@operation) to @url failed. @message', $context); + return; + } + + // If debugging is enabled, display the request and response. + $this->debug(t('Remote post successful!'), $operation, $request_url, $request_type, $request_post_data, $response, 'warning'); + } + + /** + * Get a webform submission's post data. + * + * @param string $operation + * The type of webform submission operation to be posted. Can be 'insert', + * 'update', or 'delete'. + * @param \Drupal\webform\WebformSubmissionInterface $webform_submission + * The webform submission to be posted. + * + * @return array + * A webform submission converted to an associative array. + */ + protected function getPostData($operation, WebformSubmissionInterface $webform_submission) { + // Get submission and elements data. + $data = $webform_submission->toArray(TRUE); + + // Flatten data. + // Prioritizing elements before the submissions fields. + $data = $data['data'] + $data; + unset($data['data']); + + // Excluded selected submission data. + $data = array_diff_key($data, $this->configuration['excluded_data']); + + // Append custom data. + if (!empty($this->configuration['custom_data'])) { + $data = Yaml::decode($this->configuration['custom_data']) + $data; + } + + // Append operation data. + if (!empty($this->configuration[$operation . '_custom_data'])) { + $data = Yaml::decode($this->configuration[$operation . '_custom_data']) + $data; + } + + // Replace tokens. + $data = $this->tokenManager->replace($data, $webform_submission); + + return $data; + } + + /** + * Display debugging information. + * + * @param string $message + * Message to be displayed. + * @param string $operation + * The operation being performed, can be either insert, update, or delete. + * @param string $request_url + * The remote URL the request is being posted to. + * @param string $request_type + * The type of remote post. + * @param string $request_post_data + * The webform submission data being posted. + * @param \Psr\Http\Message\ResponseInterface|null $response + * The response returned by the remote server. + * @param string $type + * The type of message to be displayed to the end use. + */ + protected function debug($message, $operation, $request_url, $request_type, $request_post_data, ResponseInterface $response = NULL, $type = 'warning') { + if (empty($this->configuration['debug'])) { + return; + } + + $build = []; + + // Message. + $build['message'] = [ + '#markup' => $message, + '#prefix' => '', + '#suffix' => '', + ]; + + // Operation. + $build['operation'] = [ + '#type' => 'item', + '#title' => $this->t('Remote operation'), + '#markup' => $operation, + ]; + + // Request. + $build['request_url'] = [ + '#type' => 'item', + '#title' => $this->t('Request URL'), + '#markup' => $request_url, + ]; + $build['request_type'] = [ + '#type' => 'item', + '#title' => $this->t('Request type'), + '#markup' => $request_type, + ]; + $build['request_post_data'] = [ + '#type' => 'item', + '#title' => $this->t('Request data'), + 'data' => [ + '#markup' => htmlspecialchars(Yaml::encode($request_post_data)), + '#prefix' => '
          ',
          +        '#suffix' => '
          ', + ], + ]; + + $build['returned'] = [ + '#markup' => $this->t('...returned...'), + '#prefix' => '', + '#suffix' => '', + ]; + + // Response. + if ($response) { + $build['response_code'] = [ + '#type' => 'item', + '#title' => $this->t('Response status code'), + '#markup' => $response->getStatusCode(), + ]; + $build['response_header'] = [ + '#type' => 'details', + '#title' => $this->t('Response header'), + 'data' => [ + '#markup' => htmlspecialchars(Yaml::encode($response->getHeaders())), + '#prefix' => '
          ',
          +          '#suffix' => '
          ', + ], + ]; + $build['response_body'] = [ + '#type' => 'details', + '#title' => $this->t('Response body'), + 'data' => [ + '#markup' => htmlspecialchars($response->getBody()), + '#prefix' => '
          ',
          +          '#suffix' => '
          ', + ], + ]; + } + else { + $build['response_code'] = [ + '#markup' => t('No response. Please see the recent log messages.'), + '#prefix' => '

          ', + '#suffix' => '

          ', + ]; + } + + drupal_set_message(\Drupal::service('renderer')->renderPlain($build), $type); + } + +} diff --git a/web/modules/contrib/webform/src/Plugin/views/field/WebformSubmissionBulkForm.php b/web/modules/contrib/webform/src/Plugin/views/field/WebformSubmissionBulkForm.php new file mode 100644 index 000000000..e2a6db6d3 --- /dev/null +++ b/web/modules/contrib/webform/src/Plugin/views/field/WebformSubmissionBulkForm.php @@ -0,0 +1,21 @@ +t('No submission selected.'); + } + +} diff --git a/web/modules/contrib/webform/src/Routing/WebformRouteSubscriber.php b/web/modules/contrib/webform/src/Routing/WebformRouteSubscriber.php new file mode 100644 index 000000000..fa5d5b68b --- /dev/null +++ b/web/modules/contrib/webform/src/Routing/WebformRouteSubscriber.php @@ -0,0 +1,27 @@ +all() as $route) { + if (!$route->hasOption('_admin_route') && ( + strpos($route->getPath(), '/admin/structure/webform/') === 0 + || strpos($route->getPath(), '/webform/results/') !== FALSE + )) { + $route->setOption('_admin_route', TRUE); + } + } + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementAccessTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementAccessTest.php new file mode 100644 index 000000000..161b43087 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementAccessTest.php @@ -0,0 +1,184 @@ +createUsers(); + } + + /** + * Test element access. + */ + public function testElementAccess() { + $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); + $webform->set('elements', $elements); + $webform->save(); + + // Create a webform submission. + $this->drupalLogin($this->normalUser); + $sid = $this->postSubmission($webform); + $webform_submission = WebformSubmission::load($sid); + + // Check admins have 'administer webform element access' permission. + $this->drupalLogin($this->adminWebformUser); + $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->assertNoFieldById('edit-properties-access-create-roles-anonymous'); + + /* Create access */ + + // Check anonymous role access. + $this->drupalLogout(); + $this->drupalGet('webform/test_element_access'); + $this->assertFieldByName('access_create_roles_anonymous'); + $this->assertNoFieldByName('access_create_roles_authenticated'); + $this->assertNoFieldByName('access_create_users'); + + // Check authenticated access. + $this->drupalLogin($this->normalUser); + $this->drupalGet('webform/test_element_access'); + $this->assertNoFieldByName('access_create_roles_anonymous'); + $this->assertFieldByName('access_create_roles_authenticated'); + $this->assertNoFieldByName('access_create_users'); + + // Check admin user access. + $this->drupalLogin($this->adminSubmissionUser); + $this->drupalGet('webform/test_element_access'); + $this->assertNoFieldByName('access_create_roles_anonymous'); + $this->assertFieldByName('access_create_roles_authenticated'); + $this->assertFieldByName('access_create_users'); + + /* Update access */ + + // Check anonymous role access. + $this->drupalLogout(); + $this->drupalGet($webform_submission->getTokenUrl()); + $this->assertFieldByName('access_update_roles_anonymous'); + $this->assertNoFieldByName('access_update_roles_authenticated'); + $this->assertNoFieldByName('access_update_users'); + + // Check authenticated role access. + $this->drupalLogin($this->normalUser); + $this->drupalGet("/webform/test_element_access/submissions/$sid/edit"); + $this->assertNoFieldByName('access_update_roles_anonymous'); + $this->assertFieldByName('access_update_roles_authenticated'); + $this->assertNoFieldByName('access_update_users'); + + // Check admin user access. + $this->drupalLogin($this->adminSubmissionUser); + $this->drupalGet("/admin/structure/webform/manage/test_element_access/submission/$sid/edit"); + $this->assertNoFieldByName('access_update_roles_anonymous'); + $this->assertFieldByName('access_update_roles_authenticated'); + $this->assertFieldByName('access_update_users'); + + /* View, Table, Customize, and Download access */ + + $urls = [ + ['path' => "/admin/structure/webform/manage/test_element_access/submission/$sid"], + ['path' => '/admin/structure/webform/manage/test_element_access/results/table'], + ['path' => '/admin/structure/webform/manage/test_element_access/results/table/custom'], + ['path' => '/admin/structure/webform/manage/test_element_access/results/download'], + ['path' => '/admin/structure/webform/manage/test_element_access/results/download', 'options' => ['query' => ['download' => 1]]], + ]; + foreach ($urls as $url) { + $url += ['options' => []]; + + // Check anonymous role access. + $this->drupalLogout(); + $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() . ')'); + + // Check authenticated role access. + $this->drupalLogin($this->adminWebformUser); + $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() . ')'); + + // Check admin user access. + $this->drupalLogin($this->adminSubmissionUser); + $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() . ')'); + } + + /* Download token access */ + $urls = [ + 'token' => [ + 'path' => '/admin/structure/webform/manage/test_element_access/results/download', + ], + ',Token,' => [ + 'path' => '/admin/structure/webform/manage/test_element_access/results/download', + 'options' => ['query' => ['download' => 1, 'excluded_columns' => '']], + ], + ]; + foreach ($urls as $raw => $url) { + $url += ['options' => []]; + + // Check anonymous role access. + $this->drupalLogout(); + $this->drupalGet($url['path'], $url['options']); + $this->assertNoRaw($raw, 'Anonymous user can not access token'); + + // Check authenticated role access. + $this->drupalLogin($this->normalUser); + $this->drupalGet($url['path'], $url['options']); + $this->assertNoRaw($raw, 'Authenticated user can not access token'); + + // Check admin webform access. + $this->drupalLogin($this->adminWebformUser); + $this->drupalGet($url['path'], $url['options']); + $this->assertRaw($raw, 'Admin webform user can access token'); + + // Check admin submission access. + $this->drupalLogin($this->adminSubmissionUser); + $this->drupalGet($url['path'], $url['options']); + $this->assertRaw($raw, 'Admin submission user can access token'); + } + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementAttributesTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementAttributesTest.php new file mode 100644 index 000000000..34dc83bb3 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementAttributesTest.php @@ -0,0 +1,36 @@ +drupalPostForm('webform/test_element_attributes', [], t('Submit')); + $this->assertRaw("webform_element_attributes: + class: + - one + - two + - four + style: 'color: red' + custom: test"); + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementCodeMirrorTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementCodeMirrorTest.php new file mode 100644 index 000000000..62cc7cbc8 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementCodeMirrorTest.php @@ -0,0 +1,91 @@ +drupalGet('webform/test_element_codemirror'); + $this->assertRaw(''); + $this->assertRaw(''); + + /**************************************************************************/ + // code:yaml + /**************************************************************************/ + + // Check YAML. + $this->drupalGet('webform/test_element_codemirror'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Check associative array as the #default_value. + $this->drupalPostForm('webform/test_element_codemirror', [], t('Submit')); + $this->assertRaw('yaml_array: + one: One + two: Two + three: Three'); + + // Check invalid YAML. + $edit = [ + 'yaml_basic' => "'not: valid", + ]; + $this->drupalPostForm('webform/test_element_codemirror', $edit, t('Submit')); + $this->assertRaw('YAML basic is not valid.'); + + // Check valid YAML. + $edit = [ + 'yaml_basic' => 'is: valid', + ]; + $this->drupalPostForm('webform/test_element_codemirror', $edit, t('Submit')); + $this->assertNoRaw('YAML basic is not valid.'); + + /**************************************************************************/ + // code:html + /**************************************************************************/ + + // Check HTML. + $this->drupalGet('webform/test_element_codemirror'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Check invalid HTML. + $edit = [ + 'html_basic' => "bold", + ]; + $this->drupalPostForm('webform/test_element_codemirror', $edit, t('Submit')); + $this->assertRaw('HTML basic is not valid.'); + $this->assertRaw('expected '>''); + + // Check valid HTML. + $edit = [ + 'html_basic' => 'bold', + ]; + $this->drupalPostForm('webform/test_element_codemirror', $edit, t('Submit')); + $this->assertNoRaw('HTML basic is not valid.'); + $this->assertNoRaw('expected '>''); + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementCompositeTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementCompositeTest.php new file mode 100644 index 000000000..9a6374a7a --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementCompositeTest.php @@ -0,0 +1,90 @@ +drupalGet('webform/test_element_composite'); + + // Check webform contact basic. + $this->assertRaw('
          '); + $this->assertNoRaw('Contact basic'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Check custom name title, description, and required. + $this->assertRaw(''); + $this->assertRaw(''); + $this->assertRaw('Custom contact name description'); + + // Check custom state type and not required. + $this->assertRaw(''); + $this->assertRaw(''); + + // Check custom country access. + $this->assertNoRaw('edit-contact-advanced-country'); + + // Check credit card. + $this->assertRaw('
          '); + $this->assertRaw('Credit Card'); + $this->assertNoRaw('Credit Card'); + $this->assertRaw('The credit card element is experimental and insecure because it stores submitted information as plain text.'); + $this->assertRaw(''); + $this->assertRaw(''); + + /* Processing */ + + // Check contact composite value. + $this->drupalPostForm('webform/test_element_composite', [], t('Submit')); + $this->assertRaw("contact_basic: + name: 'John Smith' + company: Acme + email: example@example.com + phone: 123-456-7890 + address: '100 Main Street' + address_2: 'PO BOX 999' + city: 'Hill Valley' + state_province: 'New Jersey' + postal_code: 11111-1111 + country: 'United States'"); + + // Check contact validate required composite elements. + $edit = [ + 'contact_basic[name]' => '', + ]; + $this->drupalPostForm('webform/test_element_composite', $edit, t('Submit')); + $this->assertRaw('Name field is required.'); + + // Check creditcard composite value. + $this->drupalPostForm('webform/test_element_composite', [], t('Submit')); + $this->assertRaw("creditcard_basic: + name: 'John Smith' + type: VI + number: '4111111111111111' + civ: '111' + expiration_month: '1' + expiration_year: '2025'"); + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php new file mode 100644 index 000000000..dd9a7ede8 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementCustomPropertiesTest.php @@ -0,0 +1,88 @@ +drupalCreateUser([ + 'administer webform', + ]); + $this->drupalLogin($admin_user); + + // Get Webform storage. + $webform_storage = \Drupal::entityTypeManager()->getStorage('webform'); + + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $webform_storage->load('contact'); + + // Set name element. + $name_element = [ + '#type' => 'textfield', + '#title' => 'Your Name', + '#default_value' => '[webform-authenticated-user:display-name]', + '#required' => TRUE, + ]; + + // Check that name element render array does not contain custom property + // or data. + $this->assertEqual($webform->getElementDecoded('name'), $name_element); + + // Check that name input does not contain custom data. + $this->drupalGet('webform/contact'); + $this->assertRaw(''); + + // Submit empty custom property and data. + $edit = [ + 'properties[custom_data]' => '', + ]; + $this->drupalPostForm('admin/structure/webform/manage/contact/element/name/edit', $edit, t('Save')); + + // Get updated contact webform. + $webform_storage->resetCache(); + $webform = $webform_storage->load('contact'); + + // Check that name element render array still does not contain custom + // property or data. + $this->assertEqual($webform->getElementDecoded('name'), $name_element); + + // Add custom property and data. + $edit = [ + 'properties[custom_data]' => 'custom-data', + ]; + $this->drupalPostForm('admin/structure/webform/manage/contact/element/name/edit', $edit, t('Save')); + + // Get updated contact webform. + $webform_storage->resetCache(); + $webform = $webform_storage->load('contact'); + + // Check that name element does contain custom property or data. + $name_element += [ + '#custom_data' => 'custom-data', + ]; + $this->assertEqual($webform->getElementDecoded('name'), $name_element); + + // Check that name input does contain custom data. + $this->drupalGet('webform/contact'); + $this->assertRaw(''); + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementDateTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementDateTest.php new file mode 100644 index 000000000..445f2f7e8 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementDateTest.php @@ -0,0 +1,123 @@ +drupalGet('webform/test_element_dates'); + $this->assertFieldByName('date_default', '2009-08-18'); + $this->assertFieldByName('datetime_default[date]', '2009-08-18'); + $this->assertFieldByName('datetime_default[time]', '16:00:00'); + $this->assertFieldByName('datelist_default[month]', '8'); + + // Check 'datelist' and 'datetime' #default_value. + $form = $webform_dates->getSubmissionForm(); + $this->assert(is_string($form['elements']['date_elements']['date_default']['#default_value']), 'date_default #default_value is a string.'); + $this->assert($form['elements']['datetime_elements']['datetime_default']['#default_value'] instanceof DrupalDateTime, 'datelist_default #default_value instance of \Drupal\Core\Datetime\DrupalDateTime.'); + $this->assert($form['elements']['datelist_elements']['datelist_default']['#default_value'] instanceof DrupalDateTime, 'datelist_default #default_value instance of \Drupal\Core\Datetime\DrupalDateTime.'); + + /* Date Validation */ + + // Check date #max validation. + $edit = ['date_min_max' => '2010-08-18']; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('date (min/max) must be on or before 2009-12-31.'); + + // Check date #min validation. + $edit = ['date_min_max' => '2006-08-18']; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('date (min/max) must be on or after 2009-01-01.'); + + // Check dynamic date. + $this->drupalGet('webform/test_element_dates'); + $min = \Drupal::service('date.formatter')->format(strtotime('-1 year'), 'html_date'); + $max = \Drupal::service('date.formatter')->format(strtotime('+1 year'), 'html_date'); + $default_value = \Drupal::service('date.formatter')->format(strtotime('now'), 'html_date'); + $this->assertRaw(''); + + /* Datetime Validation */ + + // Check datetime #max validation. + $edit = ['datetime_min_max[date]' => '2010-08-18']; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('datetime (min/max) must be on or before 2009-12-31.'); + + // Check datetime #min validation. + $edit = ['datetime_min_max[date]' => '2006-08-18']; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('datetime (min/max) must be on or after 2009-01-01.'); + + /* Datelist Validation */ + + // Check datelist #max validation. + $edit = [ + 'datelist_min_max[year]' => '2010', + 'datelist_min_max[month]' => '8', + 'datelist_min_max[day]' => '18', + ]; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('datelist (min/max) must be on or before 2009-12-31.'); + + // Check datelist #min validation. + $edit = [ + 'datelist_min_max[year]' => '2006', + 'datelist_min_max[month]' => '8', + 'datelist_min_max[day]' => '18', + ]; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('datelist (min/max) must be on or after 2009-01-01.'); + + /* Time element and validation */ + + // Check time element. + $this->drupalGet('webform/test_element_dates'); + $this->assertRaw(''); + $this->assertRaw(''); + + // Check time validation. + $edit = ['time_24_hour' => 'not-valid']; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('time 24 hour must be a valid time.'); + + // Check time #max validation. + $edit = [ + 'time_min_max' => '12:00', + ]; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('time (min/max) must be on or after 14:00.'); + + // Check time #min validation. + $edit = [ + 'time_min_max' => '22:00', + ]; + $this->drupalPostForm('webform/test_element_dates', $edit, t('Submit')); + $this->assertRaw('time (min/max) must be on or before 18:00.'); + } + +} diff --git a/web/modules/contrib/webform/src/Tests/Element/WebformElementEmailTest.php b/web/modules/contrib/webform/src/Tests/Element/WebformElementEmailTest.php new file mode 100644 index 000000000..95d43ef62 --- /dev/null +++ b/web/modules/contrib/webform/src/Tests/Element/WebformElementEmailTest.php @@ -0,0 +1,127 @@ +drupalGet('webform/test_element_email'); + $this->assertRaw(''); + $this->assertRaw(''); + $this->assertRaw('Multiple email addresses may be separated by commas.'); + + // Check email multiple invalid second email address. + $edit = [ + 'email_multiple_basic' => 'example@example.com, Not a valid email address', + ]; + $this->drupalPostForm('webform/test_element_email', $edit, t('Submit')); + $this->assertRaw('The email address Not a valid email address is not valid.'); + + // Check email multiple invalid token email address. + $edit = [ + 'email_multiple_basic' => 'example@example.com, [token]', + ]; + $this->drupalPostForm('webform/test_element_email', $edit, t('Submit')); + $this->assertRaw('The email address [token] is not valid.'); + + // Check email multiple valid second email address. + $edit = [ + 'email_multiple_basic' => 'example@example.com, other@other.com', + ]; + $this->drupalPostForm('webform/test_element_email', $edit, t('Submit')); + $this->assertRaw("email_multiple_basic: 'example@example.com, other@other.com'"); + + // Check email multiple valid token email address (via #allow_tokens). + $edit = [ + 'email_multiple_advanced' => 'example@example.com, [token]', + ]; + $this->drupalPostForm('webform/test_element_email', $edit, t('Submit')); + $this->assertRaw("email_multiple_advanced: 'example@example.com, [token]'"); + + /**************************************************************************/ + // email_confirm + /**************************************************************************/ + + $this->drupalGet('webform/test_element_email'); + + // Check basic email confirm. + $this->assertRaw('
          '); + $this->assertRaw('