From 2d55ea78f101a2a7f50a0389abc75a9f67f02d5c Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Fri, 13 Jun 2025 11:14:11 +0100 Subject: [PATCH] Add CTAs to daily emails Add a daily email CTA node type and reference field to attach a CTA to a daily email. Also update the RSS feed view to include the referenced body field value. --- ..._override.node.daily_email_cta.promote.yml | 22 +++++ ..._form_display.node.daily_email.default.yml | 11 +++ ...m_display.node.daily_email_cta.default.yml | 87 ++++++++++++++++++ ..._view_display.node.daily_email.default.yml | 2 + ...tity_view_display.node.daily_email.rss.yml | 2 + ...y_view_display.node.daily_email.teaser.yml | 2 + ...w_display.node.daily_email_cta.default.yml | 28 ++++++ ...ew_display.node.daily_email_cta.teaser.yml | 30 ++++++ ...node.daily_email.field_daily_email_cta.yml | 29 ++++++ .../field.field.node.daily_email_cta.body.yml | 24 +++++ ...eld.storage.node.field_daily_email_cta.yml | 19 ++++ config/sync/node.type.daily_email_cta.yml | 17 ++++ .../sync/views.view.daily_email_rss_feed.yml | 77 +++++++++++++++- content/meta/index.json | 18 ++++ ....20cde1b4-efdc-46a4-a6a4-4fd2264f617e.json | 92 +++++++++++++++++++ ....2a6bc8bd-a1e0-4f62-8112-47c3107020c5.json | 92 +++++++++++++++++++ ....3074e1e9-c691-4f73-a71c-cfe5920f884e.json | 92 +++++++++++++++++++ ....9b4c39a3-702f-486c-a79b-4d7b96a4f3f6.json | 92 +++++++++++++++++++ ....c74de3cf-5362-4d08-935a-a9d0d22fcb94.json | 92 +++++++++++++++++++ ....e3f6c728-7855-4804-8614-e2a0c08c368f.json | 92 +++++++++++++++++++ .../opd_daily_emails/opd_daily_emails.module | 46 ++++++++++ modules/opd_daily_emails/src/Ctas.php | 36 ++++++++ modules/opd_daily_emails/src/DailyEmail.php | 14 +++ .../tests/src/Functional/CallToActionTest.php | 45 +++++++++ 24 files changed, 1058 insertions(+), 3 deletions(-) create mode 100644 config/sync/core.base_field_override.node.daily_email_cta.promote.yml create mode 100644 config/sync/core.entity_form_display.node.daily_email_cta.default.yml create mode 100644 config/sync/core.entity_view_display.node.daily_email_cta.default.yml create mode 100644 config/sync/core.entity_view_display.node.daily_email_cta.teaser.yml create mode 100644 config/sync/field.field.node.daily_email.field_daily_email_cta.yml create mode 100644 config/sync/field.field.node.daily_email_cta.body.yml create mode 100644 config/sync/field.storage.node.field_daily_email_cta.yml create mode 100644 config/sync/node.type.daily_email_cta.yml create mode 100644 content/node.20cde1b4-efdc-46a4-a6a4-4fd2264f617e.json create mode 100644 content/node.2a6bc8bd-a1e0-4f62-8112-47c3107020c5.json create mode 100644 content/node.3074e1e9-c691-4f73-a71c-cfe5920f884e.json create mode 100644 content/node.9b4c39a3-702f-486c-a79b-4d7b96a4f3f6.json create mode 100644 content/node.c74de3cf-5362-4d08-935a-a9d0d22fcb94.json create mode 100644 content/node.e3f6c728-7855-4804-8614-e2a0c08c368f.json create mode 100644 modules/opd_daily_emails/src/Ctas.php create mode 100644 modules/opd_daily_emails/src/DailyEmail.php create mode 100644 modules/opd_daily_emails/tests/src/Functional/CallToActionTest.php diff --git a/config/sync/core.base_field_override.node.daily_email_cta.promote.yml b/config/sync/core.base_field_override.node.daily_email_cta.promote.yml new file mode 100644 index 000000000..ec7fbe594 --- /dev/null +++ b/config/sync/core.base_field_override.node.daily_email_cta.promote.yml @@ -0,0 +1,22 @@ +uuid: 125295f6-32ba-473d-a114-64b2f30a74a2 +langcode: en +status: true +dependencies: + config: + - node.type.daily_email_cta +id: node.daily_email_cta.promote +field_name: promote +entity_type: node +bundle: daily_email_cta +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/config/sync/core.entity_form_display.node.daily_email.default.yml b/config/sync/core.entity_form_display.node.daily_email.default.yml index 5bf61cee9..093623b3c 100644 --- a/config/sync/core.entity_form_display.node.daily_email.default.yml +++ b/config/sync/core.entity_form_display.node.daily_email.default.yml @@ -4,6 +4,7 @@ status: true dependencies: config: - field.field.node.daily_email.body + - field.field.node.daily_email.field_daily_email_cta - node.type.daily_email module: - path @@ -29,6 +30,16 @@ content: region: content settings: { } third_party_settings: { } + field_daily_email_cta: + type: entity_reference_autocomplete + weight: 121 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } path: type: path weight: 30 diff --git a/config/sync/core.entity_form_display.node.daily_email_cta.default.yml b/config/sync/core.entity_form_display.node.daily_email_cta.default.yml new file mode 100644 index 000000000..8da683c6c --- /dev/null +++ b/config/sync/core.entity_form_display.node.daily_email_cta.default.yml @@ -0,0 +1,87 @@ +uuid: 66add161-1d42-41b4-9eb3-adf47de3b200 +langcode: en +status: true +dependencies: + config: + - field.field.node.daily_email_cta.body + - node.type.daily_email_cta + module: + - path + - text +id: node.daily_email_cta.default +targetEntityType: node +bundle: daily_email_cta +mode: default +content: + body: + type: text_textarea_with_summary + weight: 2 + region: content + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + show_summary: false + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + weight: 15 + region: content + settings: + display_label: true + third_party_settings: { } + simple_sitemap: + weight: 10 + region: content + settings: { } + third_party_settings: { } + status: + type: boolean_checkbox + weight: 120 + region: content + settings: + display_label: true + third_party_settings: { } + sticky: + type: boolean_checkbox + weight: 16 + region: content + settings: + display_label: true + third_party_settings: { } + title: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + region: content + settings: + match_operator: CONTAINS + match_limit: 10 + size: 60 + placeholder: '' + third_party_settings: { } + url_redirects: + weight: 50 + region: content + settings: { } + third_party_settings: { } +hidden: { } diff --git a/config/sync/core.entity_view_display.node.daily_email.default.yml b/config/sync/core.entity_view_display.node.daily_email.default.yml index 2206eb916..af7514181 100644 --- a/config/sync/core.entity_view_display.node.daily_email.default.yml +++ b/config/sync/core.entity_view_display.node.daily_email.default.yml @@ -4,6 +4,7 @@ status: true dependencies: config: - field.field.node.daily_email.body + - field.field.node.daily_email.field_daily_email_cta - node.type.daily_email module: - text @@ -21,4 +22,5 @@ content: weight: 0 region: content hidden: + field_daily_email_cta: true links: true diff --git a/config/sync/core.entity_view_display.node.daily_email.rss.yml b/config/sync/core.entity_view_display.node.daily_email.rss.yml index cdf82fa8a..bbce5f764 100644 --- a/config/sync/core.entity_view_display.node.daily_email.rss.yml +++ b/config/sync/core.entity_view_display.node.daily_email.rss.yml @@ -5,6 +5,7 @@ dependencies: config: - core.entity_view_mode.node.rss - field.field.node.daily_email.body + - field.field.node.daily_email.field_daily_email_cta - node.type.daily_email module: - layout_builder @@ -27,4 +28,5 @@ content: weight: 0 region: content hidden: + field_daily_email_cta: true links: true diff --git a/config/sync/core.entity_view_display.node.daily_email.teaser.yml b/config/sync/core.entity_view_display.node.daily_email.teaser.yml index 2958442a3..58c24c706 100644 --- a/config/sync/core.entity_view_display.node.daily_email.teaser.yml +++ b/config/sync/core.entity_view_display.node.daily_email.teaser.yml @@ -5,6 +5,7 @@ dependencies: config: - core.entity_view_mode.node.teaser - field.field.node.daily_email.body + - field.field.node.daily_email.field_daily_email_cta - node.type.daily_email module: - text @@ -23,4 +24,5 @@ content: weight: 0 region: content hidden: + field_daily_email_cta: true links: true diff --git a/config/sync/core.entity_view_display.node.daily_email_cta.default.yml b/config/sync/core.entity_view_display.node.daily_email_cta.default.yml new file mode 100644 index 000000000..f02529ea4 --- /dev/null +++ b/config/sync/core.entity_view_display.node.daily_email_cta.default.yml @@ -0,0 +1,28 @@ +uuid: a09e31bc-1142-4441-a82c-aa658d1e45fa +langcode: en +status: true +dependencies: + config: + - field.field.node.daily_email_cta.body + - node.type.daily_email_cta + module: + - text + - user +id: node.daily_email_cta.default +targetEntityType: node +bundle: daily_email_cta +mode: default +content: + body: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 0 + region: content + links: + settings: { } + third_party_settings: { } + weight: 100 + region: content +hidden: { } diff --git a/config/sync/core.entity_view_display.node.daily_email_cta.teaser.yml b/config/sync/core.entity_view_display.node.daily_email_cta.teaser.yml new file mode 100644 index 000000000..0314e02dd --- /dev/null +++ b/config/sync/core.entity_view_display.node.daily_email_cta.teaser.yml @@ -0,0 +1,30 @@ +uuid: 26873112-e7a7-4730-8b8a-abe73e29cc9b +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.daily_email_cta.body + - node.type.daily_email_cta + module: + - text + - user +id: node.daily_email_cta.teaser +targetEntityType: node +bundle: daily_email_cta +mode: teaser +content: + body: + type: text_summary_or_trimmed + label: hidden + settings: + trim_length: 600 + third_party_settings: { } + weight: 0 + region: content + links: + settings: { } + third_party_settings: { } + weight: 100 + region: content +hidden: { } diff --git a/config/sync/field.field.node.daily_email.field_daily_email_cta.yml b/config/sync/field.field.node.daily_email.field_daily_email_cta.yml new file mode 100644 index 000000000..54494c83d --- /dev/null +++ b/config/sync/field.field.node.daily_email.field_daily_email_cta.yml @@ -0,0 +1,29 @@ +uuid: 115fb0c0-03eb-43b1-8b89-3740b2645160 +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_daily_email_cta + - node.type.daily_email + - node.type.daily_email_cta +id: node.daily_email.field_daily_email_cta +field_name: field_daily_email_cta +entity_type: node +bundle: daily_email +label: CTA +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:node' + handler_settings: + target_bundles: + daily_email_cta: daily_email_cta + sort: + field: title + direction: ASC + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/sync/field.field.node.daily_email_cta.body.yml b/config/sync/field.field.node.daily_email_cta.body.yml new file mode 100644 index 000000000..0ab8d8044 --- /dev/null +++ b/config/sync/field.field.node.daily_email_cta.body.yml @@ -0,0 +1,24 @@ +uuid: 4bfa25ff-4011-4978-9566-299540a4fc92 +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.daily_email_cta + module: + - text +id: node.daily_email_cta.body +field_name: body +entity_type: node +bundle: daily_email_cta +label: Body +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: + display_summary: true + required_summary: false + allowed_formats: { } +field_type: text_with_summary diff --git a/config/sync/field.storage.node.field_daily_email_cta.yml b/config/sync/field.storage.node.field_daily_email_cta.yml new file mode 100644 index 000000000..1fa53b3f9 --- /dev/null +++ b/config/sync/field.storage.node.field_daily_email_cta.yml @@ -0,0 +1,19 @@ +uuid: 1df230c0-e769-4208-88a7-19c756975886 +langcode: en +status: true +dependencies: + module: + - node +id: node.field_daily_email_cta +field_name: field_daily_email_cta +entity_type: node +type: entity_reference +settings: + target_type: node +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/sync/node.type.daily_email_cta.yml b/config/sync/node.type.daily_email_cta.yml new file mode 100644 index 000000000..a58c3a404 --- /dev/null +++ b/config/sync/node.type.daily_email_cta.yml @@ -0,0 +1,17 @@ +uuid: aafaaf8f-2670-4202-bc20-2bc31312859b +langcode: en +status: true +dependencies: + module: + - menu_ui +third_party_settings: + menu_ui: + available_menus: { } + parent: '' +name: 'Daily email CTA' +type: daily_email_cta +description: 'A call to action to attach to a daily email.' +help: null +new_revision: true +preview_mode: 1 +display_submitted: false diff --git a/config/sync/views.view.daily_email_rss_feed.yml b/config/sync/views.view.daily_email_rss_feed.yml index bb2f4b416..6364bd1bc 100644 --- a/config/sync/views.view.daily_email_rss_feed.yml +++ b/config/sync/views.view.daily_email_rss_feed.yml @@ -23,6 +23,68 @@ display: position: 0 display_options: fields: + body_1: + id: body_1 + table: node__body + field: body + relationship: field_daily_email_cta + group_type: group + admin_label: '' + plugin_id: field + 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: text_default + 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 title: id: title table: node_field_data @@ -99,8 +161,8 @@ display: label: '' exclude: false alter: - alter_text: false - text: '' + alter_text: true + text: "{{ body }}\r\n{{ body_1 }}" make_link: false path: '' absolute: false @@ -494,7 +556,16 @@ display: distinct: false replica: false query_tags: { } - relationships: { } + relationships: + field_daily_email_cta: + id: field_daily_email_cta + table: node__field_daily_email_cta + field: field_daily_email_cta + relationship: none + group_type: group + admin_label: 'field_daily_email_cta: Content' + plugin_id: standard + required: false header: { } footer: { } display_extenders: { } diff --git a/content/meta/index.json b/content/meta/index.json index 88f4a2a7c..c7954ed3b 100644 --- a/content/meta/index.json +++ b/content/meta/index.json @@ -6438,5 +6438,23 @@ ], "path_alias.b9c6a52b-65d7-4122-b132-7b51164a8203": [ "node.0d970bea-36e2-4f0c-b448-4be60af220ef" + ], + "node.3074e1e9-c691-4f73-a71c-cfe5920f884e": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" + ], + "node.9b4c39a3-702f-486c-a79b-4d7b96a4f3f6": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" + ], + "node.20cde1b4-efdc-46a4-a6a4-4fd2264f617e": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" + ], + "node.2a6bc8bd-a1e0-4f62-8112-47c3107020c5": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" + ], + "node.e3f6c728-7855-4804-8614-e2a0c08c368f": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" + ], + "node.c74de3cf-5362-4d08-935a-a9d0d22fcb94": [ + "user.b8966985-d4b2-42a7-a319-2e94ccfbb849" ] } \ No newline at end of file diff --git a/content/node.20cde1b4-efdc-46a4-a6a4-4fd2264f617e.json b/content/node.20cde1b4-efdc-46a4-a6a4-4fd2264f617e.json new file mode 100644 index 000000000..901c52f52 --- /dev/null +++ b/content/node.20cde1b4-efdc-46a4-a6a4-4fd2264f617e.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "20cde1b4-efdc-46a4-a6a4-4fd2264f617e" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T22:11:07+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Open source sponsorship" + } + ], + "created": [ + { + "value": "2025-06-13T22:10:36+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T22:11:07+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

P.S. Do you want to contribute more to open source but don't have the time? Sponsor me to do it for you<\/a>!<\/p>", + "format": "basic_html", + "processed": "

P.S. Do you want to contribute more to open source but don't have the time? Sponsor me to do it for you<\/a>!<\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/content/node.2a6bc8bd-a1e0-4f62-8112-47c3107020c5.json b/content/node.2a6bc8bd-a1e0-4f62-8112-47c3107020c5.json new file mode 100644 index 000000000..c9a5d3d7a --- /dev/null +++ b/content/node.2a6bc8bd-a1e0-4f62-8112-47c3107020c5.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "2a6bc8bd-a1e0-4f62-8112-47c3107020c5" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T23:03:11+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Subscription" + } + ], + "created": [ + { + "value": "2025-06-13T23:02:52+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T23:03:11+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

Do you need immediate access to an expert Drupal Developer? With my Drupal development subscription<\/a>, make unlimited requests for a fixed monthly price in less time than posting to a job board!<\/p>", + "format": "basic_html", + "processed": "

Do you need immediate access to an expert Drupal Developer? With my Drupal development subscription<\/a>, make unlimited requests for a fixed monthly price in less time than posting to a job board!<\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/content/node.3074e1e9-c691-4f73-a71c-cfe5920f884e.json b/content/node.3074e1e9-c691-4f73-a71c-cfe5920f884e.json new file mode 100644 index 000000000..3145a683d --- /dev/null +++ b/content/node.3074e1e9-c691-4f73-a71c-cfe5920f884e.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "3074e1e9-c691-4f73-a71c-cfe5920f884e" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T22:10:23+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Call" + } + ], + "created": [ + { + "value": "2025-06-13T22:07:32+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T22:10:23+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

P.S. Are you still using Drupal 7 and don\u2019t know what\u2019s involved to upgrade to Drupal 10? Book a Drupal 7 upgrade consultation call<\/a> or an upgrade roadmap<\/a>.<\/p>", + "format": "basic_html", + "processed": "

P.S. Are you still using Drupal 7 and don\u2019t know what\u2019s involved to upgrade to Drupal 10? Book a Drupal 7 upgrade consultation call<\/a> or an upgrade roadmap<\/a>.<\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/content/node.9b4c39a3-702f-486c-a79b-4d7b96a4f3f6.json b/content/node.9b4c39a3-702f-486c-a79b-4d7b96a4f3f6.json new file mode 100644 index 000000000..133f3e068 --- /dev/null +++ b/content/node.9b4c39a3-702f-486c-a79b-4d7b96a4f3f6.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "9b4c39a3-702f-486c-a79b-4d7b96a4f3f6" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T22:09:58+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Drupal 7 EOL" + } + ], + "created": [ + { + "value": "2025-06-13T22:08:54+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T22:09:58+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

P.S. Are you stuck on Drupal 7? Plan your upgrade to Drupal 10 now!<\/a><\/p>", + "format": "basic_html", + "processed": "

P.S. Are you stuck on Drupal 7? Plan your upgrade to Drupal 10 now!<\/a><\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/content/node.c74de3cf-5362-4d08-935a-a9d0d22fcb94.json b/content/node.c74de3cf-5362-4d08-935a-a9d0d22fcb94.json new file mode 100644 index 000000000..a4353b0ce --- /dev/null +++ b/content/node.c74de3cf-5362-4d08-935a-a9d0d22fcb94.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "c74de3cf-5362-4d08-935a-a9d0d22fcb94" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T23:04:29+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Pair programming" + } + ], + "created": [ + { + "value": "2025-06-13T23:04:05+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T23:04:29+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

Need help or want another pair of eyes on your code? Book a 1-on-1 consulting call<\/a> or an online pair programming session<\/a> with a 100% money-back guarantee.,<\/p>", + "format": "basic_html", + "processed": "

Need help or want another pair of eyes on your code? Book a 1-on-1 consulting call<\/a> or an online pair programming session<\/a> with a 100% money-back guarantee.,<\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/content/node.e3f6c728-7855-4804-8614-e2a0c08c368f.json b/content/node.e3f6c728-7855-4804-8614-e2a0c08c368f.json new file mode 100644 index 000000000..ed95f00ae --- /dev/null +++ b/content/node.e3f6c728-7855-4804-8614-e2a0c08c368f.json @@ -0,0 +1,92 @@ +{ + "uuid": [ + { + "value": "e3f6c728-7855-4804-8614-e2a0c08c368f" + } + ], + "langcode": [ + { + "value": "en" + } + ], + "type": [ + { + "target_id": "daily_email_cta", + "target_type": "node_type", + "target_uuid": "aafaaf8f-2670-4202-bc20-2bc31312859b" + } + ], + "revision_timestamp": [ + { + "value": "2025-06-13T23:03:45+00:00" + } + ], + "revision_uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "revision_log": [], + "status": [ + { + "value": true + } + ], + "uid": [ + { + "target_type": "user", + "target_uuid": "b8966985-d4b2-42a7-a319-2e94ccfbb849" + } + ], + "title": [ + { + "value": "Drupal testing course" + } + ], + "created": [ + { + "value": "2025-06-13T23:03:26+00:00" + } + ], + "changed": [ + { + "value": "2025-06-13T23:03:45+00:00" + } + ], + "promote": [ + { + "value": false + } + ], + "sticky": [ + { + "value": false + } + ], + "default_langcode": [ + { + "value": true + } + ], + "revision_translation_affected": [ + { + "value": true + } + ], + "path": [ + { + "alias": "", + "pid": null, + "langcode": "en" + } + ], + "body": [ + { + "value": "

Do you want to learn about automated testing in Drupal? Take my free 10-day email course<\/a> and get daily lessons straight to your inbox.<\/p>", + "format": "basic_html", + "processed": "

Do you want to learn about automated testing in Drupal? Take my free 10-day email course<\/a> and get daily lessons straight to your inbox.<\/p>", + "summary": "" + } + ] +} \ No newline at end of file diff --git a/modules/opd_daily_emails/opd_daily_emails.module b/modules/opd_daily_emails/opd_daily_emails.module index 68b433bf2..682a98a7f 100644 --- a/modules/opd_daily_emails/opd_daily_emails.module +++ b/modules/opd_daily_emails/opd_daily_emails.module @@ -4,8 +4,54 @@ declare(strict_types=1); use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\node\NodeInterface; +use Drupal\opd_daily_emails\Ctas; +use Drupal\opd_daily_emails\DailyEmail; use Drupal\opd_daily_emails\DailyEmailNodeRepository; +/** + * Implements hook_entity_bundle_info_alter(). + * + * @param array $bundles + */ +function opd_daily_emails_entity_bundle_info_alter(array &$bundles): void { + if (isset($bundles['node'])) { + $bundles['node'][DailyEmail::NODE_TYPE]['class'] = DailyEmail::class; + } +} + +/** + * Implements hook_entity_presave(). + */ +function opd_daily_emails_entity_presave(Drupal\Core\Entity\EntityInterface $entity): void { + if (!$entity instanceof DailyEmail) { + return; + } + + if (!$entity->isNew()) { + return; + } + + if ($entity->get('field_daily_email_cta')->getValue() !== []) { + return; + } + + $nodeStorage = \Drupal::entityTypeManager()->getStorage('node'); + $query = $nodeStorage->getQuery(); + $query->condition('status', NodeInterface::PUBLISHED); + $query->condition('type', 'daily_email_cta'); + $query->accessCheck(); + $ctaNodes = $nodeStorage->loadMultiple($query->execute()); + + $ctas = Ctas::fromNodes($ctaNodes); + + if ($ctas->isEmpty()) { + return; + } + + $entity->set('field_daily_email_cta', $ctas->getRandomCta()); +} + /** * Implements hook_token_info(). * diff --git a/modules/opd_daily_emails/src/Ctas.php b/modules/opd_daily_emails/src/Ctas.php new file mode 100644 index 000000000..16cb8e788 --- /dev/null +++ b/modules/opd_daily_emails/src/Ctas.php @@ -0,0 +1,36 @@ +ctas); + } + + public function getRandomCta(): NodeInterface { + return $this->ctas[array_rand($this->ctas)]; + } + + /** + * @param list $nodes + */ + public static function fromNodes(array $nodes): self { + return new self($nodes); + } + + /** + * @param list $ctas + */ + private function __construct( + private array $ctas, + ) { + Assert::allIsInstanceOf($ctas, NodeInterface::class); + } + +} diff --git a/modules/opd_daily_emails/src/DailyEmail.php b/modules/opd_daily_emails/src/DailyEmail.php new file mode 100644 index 000000000..86f1bf2d3 --- /dev/null +++ b/modules/opd_daily_emails/src/DailyEmail.php @@ -0,0 +1,14 @@ +createNode([ + 'type' => 'daily_email_cta', + ]); + + $email = $this->createNode([ + 'field_daily_email_cta' => NULL, + 'type' => 'daily_email', + ]); + + $this->assertNotEmpty($email->get('field_daily_email_cta')->getValue()); + + // TODO: assert the returned text. + } + + public function test_saving_an_email_node_with_a_cta_will_keep_the_same_cta(): void { + $cta = $this->createNode([ + 'type' => 'daily_email_cta', + ]); + + $email = $this->createNode([ + 'field_daily_email_cta' => $cta, + 'type' => 'daily_email', + ]); + + $email->set('title', 'Updated'); + $email->save(); + + $this->assertSame( + actual: $email->get('field_daily_email_cta')->getValue()[0]['target_id'], + expected: $cta->id(), + ); + } + +}