diff --git a/composer.lock b/composer.lock index 84452c5fa..46724ad5e 100644 --- a/composer.lock +++ b/composer.lock @@ -314,29 +314,29 @@ }, { "name": "chillerlan/php-qrcode", - "version": "4.4.1", + "version": "4.4.2", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "f5e243f3b61a60934780579430a951460f40888d" + "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/f5e243f3b61a60934780579430a951460f40888d", - "reference": "f5e243f3b61a60934780579430a951460f40888d", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4", + "reference": "345ed8e4ffb56e6b3fcd9f42e3970b9026fa6ce4", "shasum": "" }, "require": { - "chillerlan/php-settings-container": "^2.1.4 || ^3.1", + "chillerlan/php-settings-container": "^2.1.6 || ^3.2.1", "ext-mbstring": "*", "php": "^7.4 || ^8.0" }, "require-dev": { - "phan/phan": "^5.4", + "phan/phan": "^5.4.5", "phpmd/phpmd": "^2.15", "phpunit/phpunit": "^9.6", "setasign/fpdf": "^1.8.2", - "squizlabs/php_codesniffer": "^3.8" + "squizlabs/php_codesniffer": "^3.11" }, "suggest": { "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", @@ -379,19 +379,15 @@ ], "support": { "issues": "https://github.com/chillerlan/php-qrcode/issues", - "source": "https://github.com/chillerlan/php-qrcode/tree/4.4.1" + "source": "https://github.com/chillerlan/php-qrcode/tree/4.4.2" }, "funding": [ - { - "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", - "type": "custom" - }, { "url": "https://ko-fi.com/codemasher", "type": "ko_fi" } ], - "time": "2024-01-06T16:56:58+00:00" + "time": "2024-11-15T15:36:24+00:00" }, { "name": "chillerlan/php-settings-container", @@ -4106,17 +4102,17 @@ }, { "name": "drupal/editoria11y", - "version": "2.1.20", + "version": "2.1.21", "source": { "type": "git", "url": "https://git.drupalcode.org/project/editoria11y.git", - "reference": "2.1.20" + "reference": "2.1.21" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.20.zip", - "reference": "2.1.20", - "shasum": "67f8e2eb17ed3d3db6cd93782ed1faa88c206640" + "url": "https://ftp.drupal.org/files/projects/editoria11y-2.1.21.zip", + "reference": "2.1.21", + "shasum": "6ec1bb5cfa15408e3372d7e0d0a1355030a9f20b" }, "require": { "drupal/core": "^9 || ^10 || ^11" @@ -4127,8 +4123,8 @@ "type": "drupal-module", "extra": { "drupal": { - "version": "2.1.20", - "datestamp": "1731097435", + "version": "2.1.21", + "datestamp": "1731602531", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4521,7 +4517,7 @@ ], "authors": [ { - "name": "Berdir", + "name": "berdir", "homepage": "https://www.drupal.org/user/214652" }, { @@ -5491,16 +5487,16 @@ }, { "name": "drupal/hdbt", - "version": "6.8.5", + "version": "6.8.7", "source": { "type": "git", "url": "https://github.com/City-of-Helsinki/drupal-hdbt.git", - "reference": "45ae632cf0b62b990b8098f9a07f53a348fa1bf1" + "reference": "788a852f02962d5c38be053989b6a18ba00daf0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt/zipball/45ae632cf0b62b990b8098f9a07f53a348fa1bf1", - "reference": "45ae632cf0b62b990b8098f9a07f53a348fa1bf1", + "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt/zipball/788a852f02962d5c38be053989b6a18ba00daf0b", + "reference": "788a852f02962d5c38be053989b6a18ba00daf0b", "shasum": "" }, "require": { @@ -5519,23 +5515,23 @@ "Drupal" ], "support": { - "source": "https://github.com/City-of-Helsinki/drupal-hdbt/tree/6.8.5", + "source": "https://github.com/City-of-Helsinki/drupal-hdbt/tree/6.8.7", "issues": "https://github.com/City-of-Helsinki/drupal-hdbt/issues" }, - "time": "2024-11-14T10:30:35+00:00" + "time": "2024-11-18T15:33:25+00:00" }, { "name": "drupal/hdbt_admin", - "version": "3.2.7", + "version": "3.2.8", "source": { "type": "git", "url": "https://github.com/City-of-Helsinki/drupal-hdbt-admin.git", - "reference": "76454c97d12ea80c2e6859fea8d5926dca80bf7e" + "reference": "d1c02090ec6536e3d43abeb236a535416b97e669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt-admin/zipball/76454c97d12ea80c2e6859fea8d5926dca80bf7e", - "reference": "76454c97d12ea80c2e6859fea8d5926dca80bf7e", + "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt-admin/zipball/d1c02090ec6536e3d43abeb236a535416b97e669", + "reference": "d1c02090ec6536e3d43abeb236a535416b97e669", "shasum": "" }, "require": { @@ -5563,10 +5559,10 @@ "Drupal" ], "support": { - "source": "https://github.com/City-of-Helsinki/drupal-hdbt-admin/tree/3.2.7", + "source": "https://github.com/City-of-Helsinki/drupal-hdbt-admin/tree/3.2.8", "issues": "https://github.com/City-of-Helsinki/drupal-hdbt-admin/issues" }, - "time": "2024-11-01T05:47:57+00:00" + "time": "2024-11-18T15:28:13+00:00" }, { "name": "drupal/health_check", @@ -5933,16 +5929,16 @@ }, { "name": "drupal/helfi_platform_config", - "version": "4.7.1", + "version": "4.7.3", "source": { "type": "git", "url": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config.git", - "reference": "680984849f6f204b63af73efb0ae531ba121ff58" + "reference": "45e879f0704b1f2b9d5f9c934fe662702483b9b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/City-of-Helsinki/drupal-helfi-platform-config/zipball/680984849f6f204b63af73efb0ae531ba121ff58", - "reference": "680984849f6f204b63af73efb0ae531ba121ff58", + "url": "https://api.github.com/repos/City-of-Helsinki/drupal-helfi-platform-config/zipball/45e879f0704b1f2b9d5f9c934fe662702483b9b7", + "reference": "45e879f0704b1f2b9d5f9c934fe662702483b9b7", "shasum": "" }, "require": { @@ -6068,10 +6064,10 @@ ], "description": "HELfi platform config", "support": { - "source": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/tree/4.7.1", + "source": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/tree/4.7.3", "issues": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/issues" }, - "time": "2024-11-11T07:44:23+00:00" + "time": "2024-11-18T15:26:52+00:00" }, { "name": "drupal/helfi_proxy", @@ -8512,6 +8508,10 @@ "name": "nerdstein", "homepage": "https://www.drupal.org/user/1557710" }, + { + "name": "ptmkenny", + "homepage": "https://www.drupal.org/user/97885" + }, { "name": "rlhawk", "homepage": "https://www.drupal.org/user/352283" @@ -10879,16 +10879,16 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v8.15.0", + "version": "v8.16.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b" + "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b", - "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85", + "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85", "shasum": "" }, "require": { @@ -10931,9 +10931,9 @@ ], "support": { "issues": "https://github.com/elastic/elasticsearch-php/issues", - "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0" + "source": "https://github.com/elastic/elasticsearch-php/tree/v8.16.0" }, - "time": "2024-08-14T14:32:50+00:00" + "time": "2024-11-14T22:23:33+00:00" }, { "name": "ezyang/htmlpurifier", @@ -18290,16 +18290,16 @@ }, { "name": "twig/twig", - "version": "v3.14.2", + "version": "v3.15.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a" + "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", - "reference": "0b6f9d8370bb3b7f1ce5313ed8feb0fafd6e399a", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/2d5b3964cc21d0188633d7ddce732dc8e874db02", + "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02", "shasum": "" }, "require": { @@ -18353,7 +18353,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.14.2" + "source": "https://github.com/twigphp/Twig/tree/v3.15.0" }, "funding": [ { @@ -18365,7 +18365,7 @@ "type": "tidelift" } ], - "time": "2024-11-07T12:36:22+00:00" + "time": "2024-11-17T15:59:19+00:00" }, { "name": "twistor/flysystem-stream-wrapper", diff --git a/conf/cmi/core.entity_view_display.media.image.image_gallery.yml b/conf/cmi/core.entity_view_display.media.image.image_gallery.yml new file mode 100644 index 000000000..d9bbd4dc3 --- /dev/null +++ b/conf/cmi/core.entity_view_display.media.image.image_gallery.yml @@ -0,0 +1,38 @@ +uuid: 276c125d-e2db-44ce-9576-21c45385e0dc +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.image_gallery + - field.field.media.image.field_media_image + - field.field.media.image.field_photographer + - media.type.image + - responsive_image.styles.image_gallery_1_1 + module: + - responsive_image +_core: + default_config_hash: Ky7rUnc3u7APd8dH1E5EogiDjUtvkJvq0ZHfgG6OlQw +id: media.image.image_gallery +targetEntityType: media +bundle: image +mode: image_gallery +content: + field_media_image: + type: responsive_image + label: hidden + settings: + responsive_image_style: image_gallery_1_1 + image_link: '' + image_loading: + attribute: eager + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + field_photographer: true + langcode: true + name: true + search_api_excerpt: true + thumbnail: true + uid: true diff --git a/conf/cmi/core.entity_view_mode.media.image_gallery.yml b/conf/cmi/core.entity_view_mode.media.image_gallery.yml new file mode 100644 index 000000000..aede92c6f --- /dev/null +++ b/conf/cmi/core.entity_view_mode.media.image_gallery.yml @@ -0,0 +1,13 @@ +uuid: c52c9e44-0d46-417c-a8d9-372120b2c68a +langcode: en +status: true +dependencies: + module: + - media +_core: + default_config_hash: X_EeW1ealqkAjjQhcc2ehkJxyT9VZ9QH5iTYi-6NeYY +id: media.image_gallery +label: 'Image gallery' +description: '' +targetEntityType: media +cache: true diff --git a/conf/cmi/image.style.0.7_1086w_1630h_LQ.yml b/conf/cmi/image.style.0.7_1086w_1630h_LQ.yml new file mode 100644 index 000000000..09db82eff --- /dev/null +++ b/conf/cmi/image.style.0.7_1086w_1630h_LQ.yml @@ -0,0 +1,26 @@ +uuid: 74ef3c22-d7ea-4cb1-8aab-01ba6b79bde5 +langcode: en +status: true +dependencies: + module: + - focal_point + - image_style_quality +_core: + default_config_hash: AeqLUQ96Cqn70SDi5us0fS8EdVFFfMUYhKl3Zf6DcBo +name: 0.7_1086w_1630h_LQ +label: 0.7_1086w_1630h_LQ +effects: + 1c1da0f4-bb6a-4f11-88eb-8cd33a8e0da2: + uuid: 1c1da0f4-bb6a-4f11-88eb-8cd33a8e0da2 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 1086 + height: 1630 + crop_type: focal_point + c319bca0-92f7-4467-a5ec-1b902ce2be1b: + uuid: c319bca0-92f7-4467-a5ec-1b902ce2be1b + id: image_style_quality + weight: 2 + data: + quality: 65 diff --git a/conf/cmi/image.style.0.7_352w_572h.yml b/conf/cmi/image.style.0.7_352w_572h.yml new file mode 100644 index 000000000..6aef21fe9 --- /dev/null +++ b/conf/cmi/image.style.0.7_352w_572h.yml @@ -0,0 +1,19 @@ +uuid: f0276d08-9181-4923-9b87-49e9bf5ee5f4 +langcode: en +status: true +dependencies: + module: + - focal_point +_core: + default_config_hash: cBN24Ntaz6rn8wtPWjCbT3-Hxq8qynjPGSmJFAV31Hk +name: 0.7_352w_572h +label: 0.7_352w_572h +effects: + 11ff7a5b-af0b-4eac-a4a3-7bc3b9ac6188: + uuid: 11ff7a5b-af0b-4eac-a4a3-7bc3b9ac6188 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 352 + height: 572 + crop_type: focal_point diff --git a/conf/cmi/image.style.0.7_414w_621h.yml b/conf/cmi/image.style.0.7_414w_621h.yml new file mode 100644 index 000000000..c93cd0363 --- /dev/null +++ b/conf/cmi/image.style.0.7_414w_621h.yml @@ -0,0 +1,19 @@ +uuid: 9ec716c1-6f6d-4d08-971f-a9d9230e43b7 +langcode: en +status: true +dependencies: + module: + - focal_point +_core: + default_config_hash: 9G0iI4Xc80IIrMDVw3MufI9ChwpU4MTr2DmAQBEsNww +name: 0.7_414w_621h +label: 0.7_414w_621h +effects: + dc8a6ee8-839c-4972-9eb8-5df5305dc75d: + uuid: dc8a6ee8-839c-4972-9eb8-5df5305dc75d + id: focal_point_scale_and_crop + weight: 1 + data: + width: 414 + height: 621 + crop_type: focal_point diff --git a/conf/cmi/image.style.0.7_543w_815h.yml b/conf/cmi/image.style.0.7_543w_815h.yml new file mode 100644 index 000000000..b94f5f88f --- /dev/null +++ b/conf/cmi/image.style.0.7_543w_815h.yml @@ -0,0 +1,19 @@ +uuid: 6ff8a4a5-849c-4f94-8f22-1eb670793bdc +langcode: en +status: true +dependencies: + module: + - focal_point +_core: + default_config_hash: YnfcElLVLgfDdm94VOY-D0FddU7red0-u7cRiayv98Q +name: 0.7_543w_815h +label: 0.7_543w_815h +effects: + 6535b34f-21c8-4005-8561-d1936245e319: + uuid: 6535b34f-21c8-4005-8561-d1936245e319 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 543 + height: 815 + crop_type: focal_point diff --git a/conf/cmi/image.style.0.7_704w_1144h_LQ.yml b/conf/cmi/image.style.0.7_704w_1144h_LQ.yml new file mode 100644 index 000000000..74a2411b0 --- /dev/null +++ b/conf/cmi/image.style.0.7_704w_1144h_LQ.yml @@ -0,0 +1,26 @@ +uuid: 19aa2c22-c3e9-47cd-b767-9fca520ae4ba +langcode: en +status: true +dependencies: + module: + - focal_point + - image_style_quality +_core: + default_config_hash: pAcV7IybMiPiagIA-dUy4lmdI5voNIOyoE0Pkse5eV0 +name: 0.7_704w_1144h_LQ +label: 0.7_704w_1144h_LQ +effects: + 289822b3-1d38-43a4-a18e-0344b4ef9161: + uuid: 289822b3-1d38-43a4-a18e-0344b4ef9161 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 704 + height: 1144 + crop_type: focal_point + c5118eb7-9ad5-4756-9515-d6358c38bd75: + uuid: c5118eb7-9ad5-4756-9515-d6358c38bd75 + id: image_style_quality + weight: 2 + data: + quality: 65 diff --git a/conf/cmi/image.style.0.7_828w_1242h_LQ.yml b/conf/cmi/image.style.0.7_828w_1242h_LQ.yml new file mode 100644 index 000000000..445ae0479 --- /dev/null +++ b/conf/cmi/image.style.0.7_828w_1242h_LQ.yml @@ -0,0 +1,26 @@ +uuid: 5ebc2957-c7ca-4606-ba96-49486e6630e4 +langcode: en +status: true +dependencies: + module: + - focal_point + - image_style_quality +_core: + default_config_hash: mfxUI0GJzq_vdiXkYMnF9RUM07CwW2KKkEz14ofxTt8 +name: 0.7_828w_1242h_LQ +label: 0.7_828w_1242h_LQ +effects: + fb181588-37f9-4269-ae4e-7c93d76ffebe: + uuid: fb181588-37f9-4269-ae4e-7c93d76ffebe + id: focal_point_scale_and_crop + weight: 1 + data: + width: 828 + height: 1242 + crop_type: focal_point + a3284e2a-d636-4454-b7b6-3958d1db2b35: + uuid: a3284e2a-d636-4454-b7b6-3958d1db2b35 + id: image_style_quality + weight: 2 + data: + quality: 65 diff --git a/conf/cmi/image.style.1_1086w_1086h_LQ.yml b/conf/cmi/image.style.1_1086w_1086h_LQ.yml new file mode 100644 index 000000000..c95dc1320 --- /dev/null +++ b/conf/cmi/image.style.1_1086w_1086h_LQ.yml @@ -0,0 +1,26 @@ +uuid: 9f76056d-d05c-40e9-9455-9caf237c2229 +langcode: en +status: true +dependencies: + module: + - focal_point + - image_style_quality +_core: + default_config_hash: mi1XUO2QcAKfZXGktQi8uH0YqSJQRPwztvP2d1hROLs +name: 1_1086w_1086h_LQ +label: 1_1086w_1086h_LQ +effects: + 3fe5b5a5-6dcd-4410-865d-22d39e20ae22: + uuid: 3fe5b5a5-6dcd-4410-865d-22d39e20ae22 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 1086 + height: 1086 + crop_type: focal_point + 348c0fc5-7317-49a8-9a52-934e77165825: + uuid: 348c0fc5-7317-49a8-9a52-934e77165825 + id: image_style_quality + weight: 2 + data: + quality: 65 diff --git a/conf/cmi/image.style.1_414w_414h.yml b/conf/cmi/image.style.1_414w_414h.yml new file mode 100644 index 000000000..21e0d1173 --- /dev/null +++ b/conf/cmi/image.style.1_414w_414h.yml @@ -0,0 +1,19 @@ +uuid: 0f96fcb7-856c-4d80-9a22-b1e88f44ca2b +langcode: en +status: true +dependencies: + module: + - focal_point +_core: + default_config_hash: hYrryvmZIFbqVuUffmELrMQU_CaSN30AKFOW_jHVzjA +name: 1_414w_414h +label: 1_414w_414h +effects: + 9ef8198f-6739-4a26-b0d7-157e42648cc4: + uuid: 9ef8198f-6739-4a26-b0d7-157e42648cc4 + id: focal_point_scale_and_crop + weight: 1 + data: + width: 414 + height: 414 + crop_type: focal_point diff --git a/conf/cmi/image.style.1_543w_543h.yml b/conf/cmi/image.style.1_543w_543h.yml new file mode 100644 index 000000000..05eb86b0e --- /dev/null +++ b/conf/cmi/image.style.1_543w_543h.yml @@ -0,0 +1,19 @@ +uuid: ca8cb2c4-7e3c-4a4a-ae75-9520f628f369 +langcode: en +status: true +dependencies: + module: + - focal_point +_core: + default_config_hash: M4ETy7uSDV6Ww_h9CjHqPNr_G1ssAwvhYUP93h5drbs +name: 1_543w_543h +label: 1_543w_543h +effects: + ed62a58f-6f7b-47ff-b8c9-3574fb9abeba: + uuid: ed62a58f-6f7b-47ff-b8c9-3574fb9abeba + id: focal_point_scale_and_crop + weight: 1 + data: + width: 543 + height: 543 + crop_type: focal_point diff --git a/conf/cmi/image.style.1_828w_828h_LQ.yml b/conf/cmi/image.style.1_828w_828h_LQ.yml new file mode 100644 index 000000000..2d33d9a48 --- /dev/null +++ b/conf/cmi/image.style.1_828w_828h_LQ.yml @@ -0,0 +1,26 @@ +uuid: d0a56f46-9d91-47a8-9269-57fda40b75ed +langcode: en +status: true +dependencies: + module: + - focal_point + - image_style_quality +_core: + default_config_hash: 5FDh9emCj1EzDriYUSM9-SHEBOkHjy6jkFjGruPs5Hw +name: 1_828w_828h_LQ +label: 1_828w_828h_LQ +effects: + c4554106-234a-4d04-a6cf-c7ae4f8886dd: + uuid: c4554106-234a-4d04-a6cf-c7ae4f8886dd + id: focal_point_scale_and_crop + weight: 1 + data: + width: 828 + height: 828 + crop_type: focal_point + ee011418-cb33-48a5-af34-10bfac5b3e64: + uuid: ee011418-cb33-48a5-af34-10bfac5b3e64 + id: image_style_quality + weight: 2 + data: + quality: 65 diff --git a/conf/cmi/language/en/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml b/conf/cmi/language/en/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml index 0fa0190c3..86e588bf7 100644 --- a/conf/cmi/language/en/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml +++ b/conf/cmi/language/en/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml @@ -58,6 +58,7 @@ elements: | '#title': Grants kayttotarkoitus: '#title': 'Purpose of use' + '#help': 'Indicate the purpose for which the grant is applied for. If necessary, specify the different uses. Also tell us what is intended to be achieved with the grant and what goals are associated with the activities to be supported.' compensation_purpose: '#title': 'Brief description of the purpose(s) of the grant(s) applied for' '#counter_maximum_message': '%d/5000 characters remaining' @@ -195,6 +196,7 @@ elements: | hankkeen_kohderyhmat_erityisryhmat: '#title': 'Does the project target a special group?' '#counter_maximum_message': '%d/1250 characters remaining' + '#help': 'For which special group? If not implemented, leave the field blank.' hankkeen_kohderyhmat_tavoitus: '#title': 'How will the target groups of the project be reached?' '#counter_maximum_message': '%d/1250 characters remaining' @@ -209,6 +211,7 @@ elements: | '#counter_maximum_message': '%d/1250 characters remaining' hankkeen_kohderyhmat_postinrot: '#title': 'In which postal code area(s) in Helsinki will the project be implemented?' + '#help': 'Please enter up to three (3) postal code areas in the field and separate them with a comma (,).' hankkeen_kohderyhmat_miksi_alue: '#title': 'Why is/are that particular area(s) selected?' '#counter_maximum_message': '%d/1250 characters remaining' diff --git a/conf/cmi/language/sv/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml b/conf/cmi/language/sv/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml index f37b2d9c8..13e525d6d 100644 --- a/conf/cmi/language/sv/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml +++ b/conf/cmi/language/sv/webform.webform.iakkaiden_kulttuuri_ja_liikunta.yml @@ -58,6 +58,7 @@ elements: | '#title': 'Typ av stöd' kayttotarkoitus: '#title': 'Användningsän damål' + '#help': 'Ange för vilket ändamål understödet ansöks och vid behov specificera de olika ändamålen. Ange också vad understödet är avsett för och vilka mål som är relaterade till den understödda verksamheten.' compensation_purpose: '#title': 'En kort beskrivning av användningsändamålet för det/de understöd som ansöks' '#counter_maximum_message': '%d/5000 tecken kvar' @@ -195,6 +196,7 @@ elements: | hankkeen_kohderyhmat_erityisryhmat: '#title': 'Riktar sig projektet till någon särskild grupp?' '#counter_maximum_message': '%d/1250 tecken kvar' + '#help': 'Till vilken särskild grupp? Om svaret är ”nej”, lämna tomt.' hankkeen_kohderyhmat_tavoitus: '#title': 'Hur ska ni nå ut med projektet till målgrupperna?' '#counter_maximum_message': '%d/1250 tecken kvar' @@ -209,6 +211,7 @@ elements: | '#counter_maximum_message': '%d/1250 tecken kvar' hankkeen_kohderyhmat_postinrot: '#title': 'Inom vilket/vilka postnummerområden i Helsingfors kommer projektet att genomföras?' + '#help': 'Ange upp till tre (3) postnummerområden och separera dem med kommatecken (,).' hankkeen_kohderyhmat_miksi_alue: '#title': 'Varför har ni valt just det/de områdena?' '#counter_maximum_message': '%d/1250 tecken kvar' diff --git a/conf/cmi/responsive_image.styles.image_gallery_1_1.yml b/conf/cmi/responsive_image.styles.image_gallery_1_1.yml new file mode 100644 index 000000000..4dfddfc68 --- /dev/null +++ b/conf/cmi/responsive_image.styles.image_gallery_1_1.yml @@ -0,0 +1,38 @@ +uuid: c0fef26d-9cc5-4756-b247-5a9c3f7b4550 +langcode: en +status: true +dependencies: + config: + - image.style.1_1086w_1086h_LQ + - image.style.1_414w_414h + - image.style.1_543w_543h + - image.style.1_828w_828h_LQ + module: + - helfi_image_styles +_core: + default_config_hash: 29_zAX_g0m2zPiaRWVs5QHweA-Yhu0_yQcb9LewDXMM +id: image_gallery_1_1 +label: 'Image gallery 1:1' +image_style_mappings: + - + image_mapping_type: image_style + image_mapping: 1_414w_414h + breakpoint_id: helfi_image_styles.m + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 1_828w_828h_LQ + breakpoint_id: helfi_image_styles.m + multiplier: 2x + - + image_mapping_type: image_style + image_mapping: 1_543w_543h + breakpoint_id: helfi_image_styles.xs + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 1_1086w_1086h_LQ + breakpoint_id: helfi_image_styles.xs + multiplier: 2x +breakpoint_group: helfi_image_styles +fallback_image_style: 1_414w_414h diff --git a/conf/cmi/responsive_image.styles.image_gallery_2_3.yml b/conf/cmi/responsive_image.styles.image_gallery_2_3.yml new file mode 100644 index 000000000..f5a90b0ca --- /dev/null +++ b/conf/cmi/responsive_image.styles.image_gallery_2_3.yml @@ -0,0 +1,50 @@ +uuid: 4da06bdc-4ddf-479d-bd7a-e7ccbcf93a7e +langcode: en +status: true +dependencies: + config: + - image.style.0.7_1086w_1630h_LQ + - image.style.0.7_352w_572h + - image.style.0.7_414w_621h + - image.style.0.7_543w_815h + - image.style.0.7_704w_1144h_LQ + - image.style.0.7_828w_1242h_LQ + module: + - helfi_image_styles +_core: + default_config_hash: FsfbGbQamEyCjnhveCelHwebu8SHFK8rfWmoeeh7jEc +id: image_gallery_2_3 +label: 'Image gallery 2:3' +image_style_mappings: + - + image_mapping_type: image_style + image_mapping: 0.7_414w_621h + breakpoint_id: helfi_image_styles.m + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 0.7_828w_1242h_LQ + breakpoint_id: helfi_image_styles.m + multiplier: 2x + - + image_mapping_type: image_style + image_mapping: 0.7_352w_572h + breakpoint_id: helfi_image_styles.s + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 0.7_704w_1144h_LQ + breakpoint_id: helfi_image_styles.s + multiplier: 2x + - + image_mapping_type: image_style + image_mapping: 0.7_543w_815h + breakpoint_id: helfi_image_styles.xs + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 0.7_1086w_1630h_LQ + breakpoint_id: helfi_image_styles.xs + multiplier: 2x +breakpoint_group: helfi_image_styles +fallback_image_style: 0.7_414w_621h diff --git a/conf/cmi/responsive_image.styles.image_gallery_3_2.yml b/conf/cmi/responsive_image.styles.image_gallery_3_2.yml new file mode 100644 index 000000000..02858796e --- /dev/null +++ b/conf/cmi/responsive_image.styles.image_gallery_3_2.yml @@ -0,0 +1,50 @@ +uuid: 0011a33c-b46a-4354-af09-3ea3e656a790 +langcode: en +status: true +dependencies: + config: + - image.style.1.5_1120w_746h_LQ + - image.style.1.5_378w_252h + - image.style.1.5_452w_301h + - image.style.1.5_560w_373h + - image.style.1.5_756w_504h_LQ + - image.style.1.5_904w_602h_LQ + module: + - helfi_image_styles +_core: + default_config_hash: l_qdU6nExbacBLGCv5ZQHXE54vA49nRWaLcmwRL479U +id: image_gallery_3_2 +label: 'Image gallery 3:2' +image_style_mappings: + - + image_mapping_type: image_style + image_mapping: 1.5_452w_301h + breakpoint_id: helfi_image_styles.m + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 1.5_904w_602h_LQ + breakpoint_id: helfi_image_styles.m + multiplier: 2x + - + image_mapping_type: image_style + image_mapping: 1.5_378w_252h + breakpoint_id: helfi_image_styles.s + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 1.5_756w_504h_LQ + breakpoint_id: helfi_image_styles.s + multiplier: 2x + - + image_mapping_type: image_style + image_mapping: 1.5_560w_373h + breakpoint_id: helfi_image_styles.xs + multiplier: 1x + - + image_mapping_type: image_style + image_mapping: 1.5_1120w_746h_LQ + breakpoint_id: helfi_image_styles.xs + multiplier: 2x +breakpoint_group: helfi_image_styles +fallback_image_style: 1.5_452w_301h diff --git a/e2e/utils/copying_helpers.ts b/e2e/utils/copying_helpers.ts index fa20439ed..2a293a4c0 100644 --- a/e2e/utils/copying_helpers.ts +++ b/e2e/utils/copying_helpers.ts @@ -117,13 +117,13 @@ const makeApplicationCopy = async ( logger(`Navigated to: ${viewPageURL}.`); // Make sure we get there. - const applicationIdContainer = await page.locator('.webform-submission__application_id'); + const applicationIdContainer = page.locator('.webform-submission__application_id'); const applicationIdContainerText = await applicationIdContainer.textContent(); expect(applicationIdContainerText).toContain(originalApplicationId); // Make sure the application can be copied. If not, skip this test. - const isCopyApplicationButtonVisible = await page.locator('#copy-application-modal-form-link').isVisible(); - if (!isCopyApplicationButtonVisible) { + const copyApplicationDialogButton = page.locator('#copy-application-button'); + if (!await copyApplicationDialogButton.isVisible()) { logger(`Copying disabled for application: ${originalApplicationId}. Skipping test.`); test.skip(true, 'Skip copy test'); } @@ -131,8 +131,23 @@ const makeApplicationCopy = async ( // Copy the original application. logger(`Copying application: ${originalApplicationId}...`); - await page.locator('#copy-application-modal-form-link').click(); - await page.locator('#copy-application-modal-form-submit').click(); + // click the copy button + await copyApplicationDialogButton.click(); + + const dialogSelector = '.dialog.application-copy-dialog'; + const actionButtonSelector = '#helfi-dialog__action-button'; + + // Wait for the dialog to appear + await page.waitForSelector(dialogSelector); + + // Check if the dialog is visible + const isDialogVisible = await page.locator(dialogSelector).isVisible(); + + // Assert that the dialog is visible + expect(isDialogVisible).toBe(true); + + // Click the action button within the specific dialog + await page.locator(`${dialogSelector} ${actionButtonSelector}`).click(); // Wait for a redirect to the new application and store the new application ID and submission URL. await logCurrentUrl(page); diff --git a/public/modules/custom/grants_handler/grants_handler.libraries.yml b/public/modules/custom/grants_handler/grants_handler.libraries.yml index 9ba38aff5..029766017 100644 --- a/public/modules/custom/grants_handler/grants_handler.libraries.yml +++ b/public/modules/custom/grants_handler/grants_handler.libraries.yml @@ -61,3 +61,11 @@ grants-dialog: js/grants-dialog.js: { } dependencies: - core/drupal + +application-copy-dialog: + js: + js/application-copy-dialog.js: {} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings diff --git a/public/modules/custom/grants_handler/grants_handler.module b/public/modules/custom/grants_handler/grants_handler.module index d8680f8d6..df4f2a8c1 100644 --- a/public/modules/custom/grants_handler/grants_handler.module +++ b/public/modules/custom/grants_handler/grants_handler.module @@ -21,7 +21,6 @@ use Drupal\Core\Url; use Drupal\grants_attachments\AttachmentHandlerHelper; use Drupal\grants_handler\ApplicationHelpers; use Drupal\grants_handler\Event\UserLogoutEvent; -use Drupal\grants_handler\Form\CopyApplicationModalForm; use Drupal\grants_handler\GrantsErrorStorage; use Drupal\grants_handler\Helpers; use Drupal\grants_handler\Plugin\WebformElement\CompensationsComposite; @@ -1771,6 +1770,8 @@ function grants_handler_preprocess_webform_submission_information(array &$variab $webform = $submission->getWebForm(); $thirdPartySettings = $webform->getThirdPartySettings('grants_metadata'); + $applicationNumber = $submissionData['application_number'] ?? ApplicationHelpers::createApplicationNumber($submission); + $printApplicationUrl = Url::fromRoute( 'grants_webform_print.submission_print', [ @@ -1836,38 +1837,36 @@ function grants_handler_preprocess_webform_submission_information(array &$variab !$isApplicationArchived && $isApplicationOpen ) { + $copyApplicationUrl = Url::fromRoute( - 'grants_handler.copy_application_modal', + 'grants_handler.copy_application', [ 'submission_id' => $submissionData['application_number'], - 'nojs' => 'ajax', - ], - [ - 'attributes' => [ - 'class' => ['use-ajax', 'hds-button', 'hds-button--supplementary'], - 'data-dialog-type' => 'modal', - 'data-dialog-options' => json_encode(CopyApplicationModalForm::getDataDialogOptions()), - // Add this id so that we can test this form. - 'id' => 'copy-application-modal-form-link', - ], ] ); - $copyApplicationLinkText = [ - '#theme' => 'edit-label-with-icon', - '#icon' => 'copy', - '#text_label' => t('Copy application', [], $tOpts), + + // Attach the JavaScript file. + $variables['#attached']['library'][] = 'grants_handler/grants-dialog'; + $variables['#attached']['library'][] = 'grants_handler/application-copy-dialog'; + + // Render a Twig template. + $html_content = \Drupal::service('twig')->render('themes/custom/hdbt_subtheme/templates/webform/application-copy-dialog-content.html.twig', [ + 'applicationNumber' => $applicationNumber, + 'webformTitle' => $webform->label(), + ]); + + // Define the settings you want to pass. + $variables['#attached']['drupalSettings']['grants_handler'] = [ + 'copyUrl' => $copyApplicationUrl->toString(), + 'htmlContent' => $html_content, ]; - $copyApplicationLink = Link::fromTextAndUrl($copyApplicationLinkText, $copyApplicationUrl); - $variables['copyApplicationLink'] = $copyApplicationLink; + $variables['copyText'] = t('Copy application', [], $tOpts); } $variables['formTitle'] = $webform->label(); $variables['printLink'] = ''; $variables['copyLink'] = ''; - $submissionData = $submission->getData(); - $applicationNumber = $submissionData['application_number'] ?? ApplicationHelpers::createApplicationNumber($submission); - $variables['applicationNumber'] = $applicationNumber; $variables['isEditable'] = $applicationStatusService->isSubmissionEditable($submission); $variables['isEditPage'] = 'grants_handler.edit_application' === \Drupal::routeMatch() @@ -1901,6 +1900,7 @@ function grants_handler_preprocess_webform_submission_information(array &$variab '#theme' => 'webform_submission_attachment_list', '#submission' => $submission, ]; + } /** diff --git a/public/modules/custom/grants_handler/grants_handler.routing.yml b/public/modules/custom/grants_handler/grants_handler.routing.yml index 837324868..b183f94e7 100644 --- a/public/modules/custom/grants_handler/grants_handler.routing.yml +++ b/public/modules/custom/grants_handler/grants_handler.routing.yml @@ -22,6 +22,14 @@ grants_handler.view_application: requirements: _custom_access: '\Drupal\grants_handler\Controller\ApplicationController::accessByApplicationNumber' +grants_handler.copy_application: + path: '/hakemus/{submission_id}/kopioi' + defaults: + _title_callback: '\Drupal\grants_handler\Controller\ApplicationController::getTitle' + _controller: '\Drupal\grants_handler\Controller\CopyApplicationAjaxController' + requirements: + _custom_access: '\Drupal\grants_handler\Controller\ApplicationController::accessByApplicationNumber' + grants_handler.edit_application: path: '/hakemus/{webform}/{webform_submission}/muokkaa' defaults: @@ -45,22 +53,6 @@ grants_handler.message_read: requirements: _custom_access: '\Drupal\grants_handler\Controller\ApplicationController::accessByApplicationNumber' -grants_handler.copy_application: - path: '/hakemus/{submission_id}/kopioi' - defaults: - _title: 'Copy application' - _form: 'Drupal\grants_handler\Form\CopyApplicationForm' - requirements: - _custom_access: '\Drupal\grants_handler\Controller\ApplicationController::accessByApplicationNumber' - -grants_handler.copy_application_modal: - path: '/hakemus/{submission_id}/kopioi/{nojs}' - defaults: - _title: 'You are copying an application' - _form: 'Drupal\grants_handler\Form\CopyApplicationModalForm' - requirements: - _custom_access: '\Drupal\grants_handler\Controller\ApplicationController::accessByApplicationNumber' - grants_handler.atv_print_view: path: '/hakemus/{submission_id}/tulosta' defaults: diff --git a/public/modules/custom/grants_handler/js/application-copy-dialog.js b/public/modules/custom/grants_handler/js/application-copy-dialog.js new file mode 100644 index 000000000..ae1477e94 --- /dev/null +++ b/public/modules/custom/grants_handler/js/application-copy-dialog.js @@ -0,0 +1,36 @@ +(function (Drupal) { + Drupal.behaviors.copyApplicationModalForm = { + attach: function (context, settings) { + const triggerButton = document.getElementById('copy-application-button'); + + triggerButton.addEventListener('click', function (event) { + + event.preventDefault(); + + const htmlContent = drupalSettings.grants_handler.htmlContent; + const copyUrl = drupalSettings.grants_handler.copyUrl; + + Drupal.dialogFunctions.createDialog({ + dialogContent: htmlContent, + actionButtonText: Drupal.t('Copy application', [], { context: 'grants_handler' }), + backButtonText: Drupal.t('Close', [], { context: 'grants_handler' }), + closeButtonText: Drupal.t('Close', [], { context: 'grants_handler' }), + actionButtonCallback: () => { + // Redirect to a new URL + window.location.href = copyUrl; + /* + We probably should handle the whole copy process here, but for + now we just redirect to the copy URL. + + Much better UX would be to show spinner here while copying + application and then redirect when it's ready + + */ + }, + dialogTitle: Drupal.t('Copy application', [], { context: 'grants_handler' }), + customSelector: 'application-copy-dialog' + }) + }); + }, + }; +})(Drupal); diff --git a/public/modules/custom/grants_handler/js/grants-dialog.js b/public/modules/custom/grants_handler/js/grants-dialog.js index 925eead27..a1f651173 100644 --- a/public/modules/custom/grants_handler/js/grants-dialog.js +++ b/public/modules/custom/grants_handler/js/grants-dialog.js @@ -12,31 +12,51 @@ * closes the dialog. * @param {Function} actionButtonCallback - The function to execute when * the "action" button is clicked. + * @param dialogTitle + * If we want to override the default title + * @param {Function} closeButtonCallback + * The function to execute when the "close" button is clicked. + * @param {Function} backButtonCallback + * The function to execute when the "back" button is clicked. + * @param escapeButtonCallback + * The function to execute when the "escape" button is clicked. + * @param customSelector + * If we want to add a custom class to the dialog container */ - createDialog: (dialogContent, actionButtonText, backButtonText, closeButtonText, actionButtonCallback = null) => { - const dialogTitle = Drupal.t('Attention', {}, { context: 'grants_handler' }); + createDialog: ({ + dialogContent, + actionButtonText, + backButtonText, + closeButtonText, + dialogTitle = Drupal.t('Attention', {}, { context: 'grants_handler' }), + actionButtonCallback = null, + closeButtonCallback = null, + backButtonCallback = null, + escapeButtonCallback = null, + customSelector = '', + }) => { const actionButtonHTML = actionButtonText && ``; const backButtonHTML = backButtonText && ``; const closeButtonHTML = closeButtonText && ``; const dialogHTML = ` -
-
- -
- ${closeButtonHTML} -

${dialogTitle}

-
-
- ${dialogContent} -
-
- ${actionButtonHTML} - ${backButtonHTML} -
-
+
+
+ +
+ ${closeButtonHTML} +

${dialogTitle}

- `; +
+ ${dialogContent} +
+
+ ${actionButtonHTML} + ${backButtonHTML} +
+
+
+ `; // TODO: Surveys use very similar javascript dialog implementation. // This and the survey implementation could possibly be merged with some @@ -54,7 +74,7 @@ const closeButton = document.getElementById('helfi-dialog__close-button'); const dialog = document.getElementById('helfi-dialog__container'); const dialogFocusTrap = window.focusTrap.createFocusTrap('#helfi-dialog__container', { - initialFocus: () => '#helfi-dialog__title' + initialFocus: () => '#helfi-dialog__title', }); // Activate the focus trap so that the user needs to react to the dialog. @@ -68,18 +88,30 @@ // Add click event listener to back button backButton.addEventListener('click', () => { dialogFocusTrap.deactivate(); + // If we have a callback, execute it. + if (backButtonCallback) { + backButtonCallback(); + } Drupal.dialogFunctions.removeDialog(dialog); }); // Add click event listener to close button closeButton.addEventListener('click', () => { dialogFocusTrap.deactivate(); + // If we have a callback, execute it. + if (closeButtonCallback) { + closeButtonCallback(); + } Drupal.dialogFunctions.removeDialog(dialog); }); // Add event listener to ESC button to remove the dialog document.body.addEventListener('keydown', function (event) { if (event.key === 'Escape') { + // If we have a escapeButtonCallback, execute it also when pressing escape. + if (escapeButtonCallback) { + escapeButtonCallback(); + } Drupal.dialogFunctions.removeDialog(dialog); } }); @@ -90,8 +122,7 @@ document.body.style.paddingRight = `${ window.innerWidth - document.documentElement.clientWidth }px`; - } - else { + } else { document.body.style.removeProperty('padding-right'); } }, @@ -105,6 +136,6 @@ dialog.remove(); Drupal.dialogFunctions.toggleNoScroll(false); Drupal.dialogFunctions.setBodyPaddingRight(false); - } - } + }, + }; })(Drupal); diff --git a/public/modules/custom/grants_handler/js/webform.form.unsaved.js b/public/modules/custom/grants_handler/js/webform.form.unsaved.js index 99e66c6b8..4f96d10cd 100644 --- a/public/modules/custom/grants_handler/js/webform.form.unsaved.js +++ b/public/modules/custom/grants_handler/js/webform.form.unsaved.js @@ -108,7 +108,7 @@ }); }); } - } + }, }; $('a').on('click', function (event) { @@ -116,18 +116,18 @@ if (unsaved && !containingElement.contains(event.target) && !event.target.getAttribute('href').startsWith('#')) { event.preventDefault(); - return Drupal.dialogFunctions.createDialog( - Drupal.t('You have unsaved changes. Are you sure you want to leave?', {}, { context: 'grants_handler' }), - Drupal.t('Leave the application', {}, { context: 'grants_handler' }), - Drupal.t('Back to application', {}, { context: 'grants_handler' }), - Drupal.t('Close', {}, { context: 'grants_handler' }), - () => { + return Drupal.dialogFunctions.createDialog({ + dialogContent: Drupal.t('You have unsaved changes. Are you sure you want to leave?', {}, { context: 'grants_handler' }), + actionButtonText: Drupal.t('Leave the application', {}, { context: 'grants_handler' }), + backButtonText: Drupal.t('Back to application', {}, { context: 'grants_handler' }), + closeButtonText: Drupal.t('Close', {}, { context: 'grants_handler' }), + actionButtonCallback: () => { unsaved = false; const dialog = document.getElementById('helfi-dialog__container'); Drupal.dialogFunctions.removeDialog(dialog); window.top.location.href = event.currentTarget.href; - } - ); + }, + }); } }); @@ -147,18 +147,18 @@ if (unsaved && (e.which === 116 || (e.which === 82 && (e.ctrlKey || e.metaKey)))) { e.preventDefault(); // Prevent F5 and Ctrl+R / Cmd+R refresh - return Drupal.dialogFunctions.createDialog( - Drupal.t('You have unsaved changes. Are you sure you want to refresh?', {}, { context: 'grants_handler' }), - Drupal.t('Refresh the page', {}, { context: 'grants_handler' }), - Drupal.t('Back to application', {}, { context: 'grants_handler' }), - Drupal.t('Close', {}, { context: 'grants_handler' }), - () => { + return Drupal.dialogFunctions.createDialog({ + dialogContent: Drupal.t('You have unsaved changes. Are you sure you want to refresh?', {}, { context: 'grants_handler' }), + actionButtonText: Drupal.t('Refresh the page', {}, { context: 'grants_handler' }), + backButtonText: Drupal.t('Back to application', {}, { context: 'grants_handler' }), + closeButtonText: Drupal.t('Close', {}, { context: 'grants_handler' }), + actionButtonCallback: () => { unsaved = false; const dialog = document.getElementById('helfi-dialog__container'); Drupal.dialogFunctions.removeDialog(dialog); location.reload(); - } - ); + }, + }); } }); diff --git a/public/modules/custom/grants_handler/src/ApplicationHelpers.php b/public/modules/custom/grants_handler/src/ApplicationHelpers.php index 973188460..5e67ec59f 100644 --- a/public/modules/custom/grants_handler/src/ApplicationHelpers.php +++ b/public/modules/custom/grants_handler/src/ApplicationHelpers.php @@ -412,6 +412,7 @@ public static function updateFieldOptions(array &$form, array $newOptions, array $currentField = &$currentField['#element'][$fieldName]; } else { + // If we don't have current field array, we can't update the options. if (!is_iterable($currentField)) { return; } diff --git a/public/modules/custom/grants_handler/src/Controller/ApplicationController.php b/public/modules/custom/grants_handler/src/Controller/ApplicationController.php index 272cb2d1f..ffd9e645e 100644 --- a/public/modules/custom/grants_handler/src/Controller/ApplicationController.php +++ b/public/modules/custom/grants_handler/src/Controller/ApplicationController.php @@ -114,7 +114,8 @@ public function access(AccountInterface $account, string $webform, string $webfo * @return \Drupal\Core\Access\AccessResultInterface * The access result. * - * @throws \GuzzleHttp\Exception\GuzzleException + * @throws \Drupal\grants_mandate\CompanySelectException + * @throws \Drupal\grants_profile\GrantsProfileException */ public function accessByApplicationNumber(AccountInterface $account, string $submission_id): AccessResultInterface { try { diff --git a/public/modules/custom/grants_handler/src/Controller/CopyApplicationAjaxController.php b/public/modules/custom/grants_handler/src/Controller/CopyApplicationAjaxController.php new file mode 100644 index 000000000..0830d3549 --- /dev/null +++ b/public/modules/custom/grants_handler/src/Controller/CopyApplicationAjaxController.php @@ -0,0 +1,199 @@ +get('grants_handler.application_getter_service'), + $container->get('grants_handler.application_init_service'), + $container->get('grants_events.events_service'), + $container->get('grants_handler.application_status_service'), + ); + } + + /** + * Builds the response. + * + * @param string $submission_id + * The submission ID. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * The redirect response. + */ + public function __invoke(string $submission_id): RedirectResponse { + $oldApplicationUrl = $this->getOldApplicationUrl($submission_id); + + try { + $webform_submission = $this->getWebformSubmission($submission_id); + $webform = $webform_submission->getWebForm(); + $this->makeSureCopyingIsEnabled($webform); + + $newSubmission = $this->initNewApplication($webform->id(), $webform_submission->getData()); + $this->logEvent($newSubmission, $webform_submission->getData()); + + return $this->getNewApplicationRedirect($webform, $newSubmission); + } + catch (\Throwable $e) { + $this->messenger()->addError('Application copying failed.'); + $this->getLogger('grants_handler') + ->error('Application copying failed: @message', ['@message' => $e->getMessage()]); + return new RedirectResponse($oldApplicationUrl->toString()); + } + } + + /** + * Gets the URL of the old application. + * + * @param string $submission_id + * The submission ID. + * + * @return \Drupal\Core\Url + * The URL of the old application. + */ + private function getOldApplicationUrl(string $submission_id): Url { + return Url::fromRoute('grants_handler.view_application', ['submission_id' => $submission_id]); + } + + /** + * Gets the webform submission object. + * + * @param string $submission_id + * The submission ID. + * + * @return \Drupal\webform\Entity\WebformSubmission + * The webform submission object. + * + * @throws \RuntimeException + * If the webform submission cannot be retrieved. + */ + private function getWebformSubmission(string $submission_id): WebformSubmission { + try { + return $this->applicationGetterService->submissionObjectFromApplicationNumber($submission_id); + } + catch (EntityStorageException | CompanySelectException $e) { + throw new \RuntimeException('Failed to get webform submission.'); + } + } + + /** + * Validates the application. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform object. + * + * @throws \RuntimeException + * If the application cannot be copied. + */ + private function makeSureCopyingIsEnabled(WebformInterface $webform): void { + $thirdPartySettings = $webform->getThirdPartySettings('grants_metadata'); + $isApplicationArchived = $thirdPartySettings["status"] === 'archived' ?? TRUE; + $isApplicationOpen = $this->applicationStatusService->isApplicationOpen($webform); + + if ($thirdPartySettings["disableCopying"] === 1 || $isApplicationArchived || !$isApplicationOpen) { + throw new \RuntimeException('Application copying disabled.'); + } + } + + /** + * Initializes a new application with copied data. + * + * @param string $webform_id + * The webform ID. + * @param array $data + * The data to initialize the new application with. + * + * @return \Drupal\webform\Entity\WebformSubmission + * The new webform submission object. + * + * @throws \RuntimeException + * If the new application cannot be initialized. + */ + private function initNewApplication(string $webform_id, array $data): WebformSubmission { + try { + return $this->applicationInitService->initApplication($webform_id, $data); + } + catch (\Throwable $e) { + throw new \RuntimeException('Failed to initialize new application.'); + } + } + + /** + * Logs the event of copying the application. + * + * @param \Drupal\webform\Entity\WebformSubmission $newSubmission + * The new webform submission object. + * @param array $oldData + * The data of the old application. + * + * @throws \Drupal\grants_events\EventException + */ + private function logEvent(WebformSubmission $newSubmission, array $oldData): void { + $newData = $newSubmission->getData(); + $this->messenger() + ->addStatus($this->t('Grant application copied, new id: @number', ['@number' => $newData['application_number']], ['context' => 'grants_handler'])); + $this->eventsService->logEvent($newData['application_number'], 'HANDLER_APP_COPIED', $this->t('Application copied from application id: @id', ['@id' => $oldData['application_number']], ['context' => 'grants_handler']) + ->render(), $newData['application_number']); + } + + /** + * Gets the redirect response for the new application. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform object. + * @param \Drupal\webform\Entity\WebformSubmission $newSubmission + * The new webform submission object. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * The redirect response. + */ + private function getNewApplicationRedirect(WebformInterface $webform, WebformSubmission $newSubmission): RedirectResponse { + $newApplicationUrl = Url::fromRoute('grants_handler.edit_application', [ + 'webform' => $webform->id(), + 'webform_submission' => $newSubmission->id(), + ]); + return new RedirectResponse($newApplicationUrl->toString()); + } + +} diff --git a/public/modules/custom/grants_handler/src/Form/CopyApplicationForm.php b/public/modules/custom/grants_handler/src/Form/CopyApplicationForm.php deleted file mode 100644 index a40dcec15..000000000 --- a/public/modules/custom/grants_handler/src/Form/CopyApplicationForm.php +++ /dev/null @@ -1,146 +0,0 @@ -setDebug(NULL); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): MessageForm|static { - return new static( - $container->get('grants_handler.application_init_service'), - $container->get('grants_handler.application_getter_service') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId(): string { - return 'grants_handler_copy_application'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, string $submission_id = ''): array { - $tOpts = ['context' => 'grants_handler']; - - try { - $webform_submission = $this->applicationGetterService->submissionObjectFromApplicationNumber($submission_id); - - if ($webform_submission != NULL) { - $form_state->setStorage(['submission' => $webform_submission]); - } - } - catch (\Exception | GuzzleException $e) { - $this->logger(self::LOGGER_CHANNEL)->error('Failed to load submission: @error', ['@error' => $e->getMessage()]); - } - $form['copyFrom'] = [ - '#type' => 'markup', - '#markup' => 'Tähän vois sitte laittaa hakemuksen perussettejä, tai vaikka koko hakemus näytille.', - ]; - - $form['actions'] = [ - '#type' => 'actions', - ]; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Copy application', [], $tOpts), - ]; - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) {} - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state): void { - $storage = $form_state->getStorage(); - /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */ - $webform_submission = $storage['submission']; - $webform = $webform_submission->getWebForm(); - - // Init new application with copied data. - try { - $newSubmission = $this->applicationInitService->initApplication($webform->id(), $webform_submission->getData()); - } - catch (EntityStorageException | - GrantsProfileException | - AtvDocumentNotFoundException | - AtvFailedToConnectException | - ProfileDataException | - TokenExpiredException | - GuzzleException $e) { - $newSubmission = FALSE; - } - - if ($newSubmission) { - - $newData = $newSubmission->getData(); - - $this->messenger() - ->addStatus( - $this->t( - 'Grant application copied(@number)', - [ - '@number' => $newData['application_number'], - ] - ) - ); - - $form_state->setRedirect( - 'grants_handler.completion', - ['submission_id' => $newData['application_number']], - [ - 'attributes' => [ - 'data-drupal-selector' => 'application-saved-successfully-link', - ], - ] - ); - } - else { - $this->messenger()->addError('Grant application copy failed.'); - } - } - -} diff --git a/public/modules/custom/grants_handler/src/Form/CopyApplicationModalForm.php b/public/modules/custom/grants_handler/src/Form/CopyApplicationModalForm.php deleted file mode 100644 index 7e2c463eb..000000000 --- a/public/modules/custom/grants_handler/src/Form/CopyApplicationModalForm.php +++ /dev/null @@ -1,300 +0,0 @@ -setDebug(NULL); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): CopyApplicationModalForm|static { - - // Create a new form object and inject its services. - $form = new static( - $container->get('renderer'), - $container->get('grants_events.events_service'), - $container->get('grants_handler.application_status_service'), - $container->get('grants_handler.application_init_service'), - $container->get('grants_handler.application_getter_service') - ); - $form->setRequestStack($container->get('request_stack')); - $form->setStringTranslation($container->get('string_translation')); - $form->setMessenger($container->get('messenger')); - - return $form; - - } - - /** - * {@inheritdoc} - */ - public function getFormId(): string { - return 'grants_profile_copy_application_modal_form'; - } - - /** - * Helper method so we can have consistent dialog options. - * - * @return string[] - * An array of jQuery UI elements to pass on to our dialog form. - */ - public static function getDataDialogOptions(): array { - return [ - 'width' => '33%', - 'closeText' => t('Close'), - ]; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, string $submission_id = '', string $nojs = ''): array { - $tOpts = ['context' => 'grants_handler']; - - // Add the core AJAX library. - $form['#attached']['library'][] = 'core/drupal.ajax'; - $form['#theme'] = 'application_copy_modal_form'; - - try { - $webform_submission = $this->applicationGetterService->submissionObjectFromApplicationNumber($submission_id); - - if ($webform_submission != NULL) { - // Set webform submission template. - $build = [ - '#theme' => 'submission_for_modal_form', - '#submission' => $webform_submission, - '#submission_id' => $submission_id, - ]; - - $form_state->setStorage(['submission' => $webform_submission]); - $form['modal_markup'] = [ - '#markup' => $this->renderer->render($build), - ]; - } - - } - catch (\Exception | GuzzleException $e) { - $this->logger('copy_application_modal_form')->error('Error: %error', [ - '%error' => $e->getMessage(), - ]); - } - - // Add a link to show this form in a modal dialog if we're not already in - // one. - if ($nojs == 'nojs') { - $form['use_ajax_container'] = [ - '#type' => 'details', - '#open' => TRUE, - ]; - $form['use_ajax_container']['use_ajax'] = [ - '#type' => 'link', - '#title' => $this->t('See this form as a modal.', [], $tOpts), - '#url' => Url::fromRoute('grants_handler.copy_application_modal', ['nojs' => 'ajax']), - '#attributes' => [ - 'class' => ['use-ajax'], - 'data-dialog-type' => 'modal', - 'data-dialog-options' => json_encode(static::getDataDialogOptions()), - // Add this id so that we can test this form. - 'id' => 'copy-application-modal-form-link', - ], - ]; - } - - // This element is responsible for displaying form errors in the AJAX - // dialog. - if ($nojs == 'ajax') { - $form['status_messages'] = [ - '#type' => 'status_messages', - '#weight' => -999, - ]; - } - - // Add a submit button that handles the submission of the form. - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Use application as base', [], $tOpts), - '#ajax' => [ - 'callback' => '::ajaxSubmitForm', - 'event' => 'click', - ], - '#attributes' => [ - 'id' => 'copy-application-modal-form-submit', - ], - ]; - - // Set the form to not use AJAX if we're on a nojs path. When this form is - // within the modal dialog, Drupal will make sure we're using an AJAX path - // instead of a nojs one. - if ($nojs == 'nojs') { - unset($form['actions']['submit']['#ajax']); - } - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state): void { - $storage = $form_state->getStorage(); - /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */ - $webform_submission = $storage['submission']; - $webform = $webform_submission->getWebForm(); - - $isApplicationOpen = $this->applicationStatusService->isApplicationOpen($webform); - $thirdPartySettings = $webform->getThirdPartySettings('grants_metadata'); - - // If copying is disabled in 3rd party settings, do not allow forward. - if ($thirdPartySettings["disableCopying"] == 1 || - $thirdPartySettings["status"] === 'archived' || - !$isApplicationOpen - ) { - $form_state->setErrorByName('modal_markup', 'Copying is disabled for this form.'); - } - - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state): void { - $storage = $form_state->getStorage(); - /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */ - $webform_submission = $storage['submission']; - $oldData = $webform_submission->getData(); - $webform = $webform_submission->getWebForm(); - - // Init new application with copied data. - try { - $newSubmission = $this->applicationInitService->initApplication($webform->id(), $webform_submission->getData()); - $newData = $newSubmission->getData(); - } - catch (\Exception $e) { - $newSubmission = FALSE; - $newData = []; - } - - if ($newSubmission) { - $this->messenger() - ->addStatus( - $this->t( - 'Grant application copied, new id: @number', - [ - '@number' => $newData['application_number'], - ], - ['context' => 'grants_handler'] - ) - ); - - $storage['newSubmission'] = $newSubmission; - $form_state->setStorage($storage); - - $this->eventsService->logEvent( - $newData['application_number'], - 'HANDLER_APP_COPIED', - $this->t('Application copied from application id: @id', ['@id' => $oldData['application_number']], ['context' => 'grants_handler']), - $newData['application_number'] - ); - - $form_state->setRedirect( - 'grants_handler.edit_application', - [ - 'webform_submission' => $newSubmission->id(), - 'webform' => $webform->id(), - ] - ); - } - else { - $this->messenger()->addError('Grant application copy failed'); - } - } - - /** - * Implements the submit handler for the modal dialog AJAX call. - * - * @param array $form - * Render array representing from. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * Current form state. - * - * @return \Drupal\Core\Ajax\AjaxResponse - * Array of AJAX commands to execute on submit of the modal form. - */ - public function ajaxSubmitForm(array &$form, FormStateInterface $form_state) { - $tOpts = ['context' => 'grants_handler']; - - // We begin building a new ajax reponse. - $response = new AjaxResponse(); - - // If the user submitted the form and there are errors, show them the - // input dialog again with error messages. Since the title element is - // required, the empty string wont't validate and there will be an error. - if ($form_state->getErrors()) { - // If there are errors, we can show the form again with the errors in - // the status_messages section. - $form['status_messages'] = [ - '#type' => 'status_messages', - '#weight' => -10, - ]; - $response->addCommand(new OpenModalDialogCommand($this->t('Errors', [], $tOpts), $form, static::getDataDialogOptions())); - } - else { - // No errors, we load things from form state. - $storage = $form_state->getStorage(); - /** @var \Drupal\webform\Entity\WebformSubmission $webform_submission */ - $webform_submission = $storage['newSubmission']; - $webform = $webform_submission->getWebForm(); - - // Create url redirect for this new submission. - $url = Url::fromRoute('grants_handler.edit_application', - [ - 'webform_submission' => $webform_submission->id(), - 'webform' => $webform->id(), - ]); - $response->addCommand(new CloseModalDialogCommand()); - $command = new RedirectCommand($url->toString()); - $response->addCommand($command); - } - - // Finally return our response. - return $response; - } - -} diff --git a/public/modules/custom/grants_profile/js/profile_dialog.js b/public/modules/custom/grants_profile/js/profile_dialog.js index 1ea699707..67aeeef57 100644 --- a/public/modules/custom/grants_profile/js/profile_dialog.js +++ b/public/modules/custom/grants_profile/js/profile_dialog.js @@ -29,28 +29,28 @@ let containingElement = document.querySelector('form'); if ((unset_name) && !containingElement.contains(event.target)) { event.preventDefault(); - return Drupal.dialogFunctions.createDialog( - Drupal.t('You need to have a name for your unregistered community or group. Please add a name and save or cancel them.', {}, { context: 'grants_profile' }), - '', - Drupal.t('Back to profile', {}, { context: 'grants_profile' }), - Drupal.t('Close', {}, { context: 'grants_profile' }), - ); + return Drupal.dialogFunctions.createDialog({ + dialogContent: Drupal.t('You need to have a name for your unregistered community or group. Please add a name and save or cancel them.', {}, { context: 'grants_profile' }), + actionButtonText: '', + backButtonText: Drupal.t('Back to profile', {}, { context: 'grants_profile' }), + closeButtonText: Drupal.t('Close', {}, { context: 'grants_profile' }), + }); } else if ((current_name !== initial_name) && !containingElement.contains(event.target)) { event.preventDefault(); - return Drupal.dialogFunctions.createDialog( - Drupal.t('You have unsaved changes in your profile. Please save or cancel them.', {}, { context: 'grants_profile' }), - '', - Drupal.t('Back to profile', {}, { context: 'grants_profile' }), - Drupal.t('Close', {}, { context: 'grants_profile' }), - ); + return Drupal.dialogFunctions.createDialog({ + dialogContent: Drupal.t('You have unsaved changes in your profile. Please save or cancel them.', {}, { context: 'grants_profile' }), + actionButtonText: '', + backButtonText: Drupal.t('Back to profile', {}, { context: 'grants_profile' }), + closeButtonText: Drupal.t('Close', {}, { context: 'grants_profile' }), + }); } else if (($('[data-drupal-selector="edit-isnewprofile"]').val() === 'initialSave') && !containingElement.contains(event.target)) { event.preventDefault(); - return Drupal.dialogFunctions.createDialog( - Drupal.t('You have not saved your profile. Please save your profile before leaving the form.', {}, { context: 'grants_profile' }), - '', - Drupal.t('Back to profile', {}, { context: 'grants_profile' }), - Drupal.t('Close', {}, { context: 'grants_profile' }), - ); + return Drupal.dialogFunctions.createDialog({ + dialogContent: Drupal.t('You have not saved your profile. Please save your profile before leaving the form.', {}, { context: 'grants_profile' }), + actionButtonText: '', + backButtonText: Drupal.t('Back to profile', {}, { context: 'grants_profile' }), + closeButtonText: Drupal.t('Close', {}, { context: 'grants_profile' }), + }); } is_element_click = false; diff --git a/public/themes/custom/hdbt_subtheme/dist/js/matomo.min.js b/public/themes/custom/hdbt_subtheme/dist/js/matomo.min.js index 52425e03f..97c4ae16e 100644 --- a/public/themes/custom/hdbt_subtheme/dist/js/matomo.min.js +++ b/public/themes/custom/hdbt_subtheme/dist/js/matomo.min.js @@ -1 +1 @@ -!function(e,Drupal){function c(){if(void 0!==Drupal.eu_cookie_compliance&&Drupal.eu_cookie_compliance.hasAgreed("statistics")){const e=window._paq=window._paq||[];e.push(["trackPageView"]),e.push(["enableLinkTracking"]),function(){const c="//webanalytics.digiaiiris.com/js/";e.push(["setTrackerUrl",`${c}tracker.php`]),e.push(["setSiteId","1219"]);const i=document,n=i.createElement("script"),t=i.getElementsByTagName("script")[0];n.async=!0,n.src=`${c}piwik.min.js`,t.parentNode.insertBefore(n,t)}()}}e(document).on("eu_cookie_compliance.changeStatus",c()),e(document).ready(c)}(jQuery,Drupal); \ No newline at end of file +!function(e,Drupal){const t=()=>{if(Drupal.cookieConsent.getConsentStatus(["statistics"])){const e=window._paq=window._paq||[];e.push(["trackPageView"]),e.push(["enableLinkTracking"]),function(){const t="//webanalytics.digiaiiris.com/js/";e.push(["setTrackerUrl",`${t}tracker.php`]),e.push(["setSiteId","1219"]);const n=document,i=n.createElement("script"),s=n.getElementsByTagName("script")[0];i.async=!0,i.src=`${t}piwik.min.js`,s.parentNode.insertBefore(i,s)}()}};Drupal.cookieConsent.initialized()?t():Drupal.cookieConsent.loadFunction(t)}(jQuery,Drupal); \ No newline at end of file diff --git a/public/themes/custom/hdbt_subtheme/src/js/matomo.js b/public/themes/custom/hdbt_subtheme/src/js/matomo.js index 7a68ba381..6e730bc24 100644 --- a/public/themes/custom/hdbt_subtheme/src/js/matomo.js +++ b/public/themes/custom/hdbt_subtheme/src/js/matomo.js @@ -1,12 +1,9 @@ // eslint-disable-next-line func-names (function ($, Drupal) { - function loadMatomoAnalytics() { - if (typeof Drupal.eu_cookie_compliance === "undefined") { - return; - } + const loadMatomoAnalytics = () => { // Load Matomo only if statistics cookies are allowed. - if (Drupal.eu_cookie_compliance.hasAgreed("statistics")) { + if (Drupal.cookieConsent.getConsentStatus(['statistics'])) { // Matomo Tag Manager // eslint-disable-next-line no-multi-assign const _paq = (window._paq = window._paq || []); @@ -25,11 +22,12 @@ s.parentNode.insertBefore(g, s); })(); } - } + }; // Load when cookie settings are changed. - $(document).on("eu_cookie_compliance.changeStatus", loadMatomoAnalytics()); - - // Load on page load. - $(document).ready(loadMatomoAnalytics); + if (Drupal.cookieConsent.initialized()) { + loadMatomoAnalytics(); + } else { + Drupal.cookieConsent.loadFunction(loadMatomoAnalytics); + } })(jQuery, Drupal); diff --git a/public/themes/custom/hdbt_subtheme/templates/webform/application-copy-dialog-content.html.twig b/public/themes/custom/hdbt_subtheme/templates/webform/application-copy-dialog-content.html.twig new file mode 100644 index 000000000..11fdb8af1 --- /dev/null +++ b/public/themes/custom/hdbt_subtheme/templates/webform/application-copy-dialog-content.html.twig @@ -0,0 +1,2 @@ +

Kopioitava hakemus: {{ webformTitle }}

+

Hakemustunnus: {{ applicationNumber }}

diff --git a/public/themes/custom/hdbt_subtheme/templates/webform/webform-submission-information.html.twig b/public/themes/custom/hdbt_subtheme/templates/webform/webform-submission-information.html.twig index 58cc39d90..daa38f316 100644 --- a/public/themes/custom/hdbt_subtheme/templates/webform/webform-submission-information.html.twig +++ b/public/themes/custom/hdbt_subtheme/templates/webform/webform-submission-information.html.twig @@ -33,43 +33,51 @@

{{ 'Application info'|t }}

{{ 'Summary about your application and processing details.'|t }}

-
-

- {{ webform.text }} -

- +
+

+ {{ webform.text }} +

+ -
- -
-
{{ "Application number"|t }}
- {{ applicationNumber }} -
{{ 'Sent date'|t }}
- {{ applicationSent }} -
{{ 'Handler information'|t }}
- {{ handler }} -
-
-
{{ "Application statuses"|t }}
- {{ history }} +
+
+
+
{{ "Application number"|t }}
+ {{ applicationNumber }} +
{{ 'Sent date'|t }}
+ {{ applicationSent }} +
{{ 'Handler information'|t }}
+ {{ handler }} +
+
+
{{ "Application statuses"|t }}
+ {{ history }} -
-
-
{{ "Application attachments"|t }}
+
+
+
{{ "Application attachments"|t }}
- {{ attachments }} + {{ attachments }} -
+
+
+ {% if isEditable and not isEditPage %} +
+ {{ editApplicationLink }}
- {% if isEditable and not isEditPage %} -
- {{ editApplicationLink }} -
- {% endif %} + {% endif %}