Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 60 additions & 19 deletions includes/emails/class-emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,43 @@ public static function can_send_email( $type ) {
return true;
}

/**
* Default values for email config fields shared across all providers.
*/
const EMAIL_CONFIG_DEFAULTS = [
'trigger_description' => '',
'recipient' => 'reader',
'recommended' => true,
'chip' => 'auth-account',
];

/**
* Fill in default values for any email config field a provider omitted.
*
* @param array $config Single email config entry as registered via the
* `newspack_email_configs` filter.
* @return array Config with shared defaults applied for missing fields.
*/
public static function apply_config_defaults( array $config ): array {
return array_merge( self::EMAIL_CONFIG_DEFAULTS, $config );
}

/**
* Get all email configs.
*
* Returns the merged config set from the `newspack_email_configs`
* filter with shared defaults applied to each entry. Public so
* downstream consumers (e.g. the wizard response builder) can read
* the unified set without re-running the filter.
*
* @return array Configs keyed by type, each merged with the shared defaults.
*/
private static function get_email_configs() {
return apply_filters( 'newspack_email_configs', [] );
public static function get_email_configs() {
$configs = apply_filters( 'newspack_email_configs', [] );
foreach ( $configs as $type => $config ) {
$configs[ $type ] = self::apply_config_defaults( $config );
}
return $configs;
}

/**
Expand All @@ -399,11 +431,15 @@ private static function serialize_email( $type = null, $post_id = 0 ) {
}
$email_config = $configs[ $type ];
} else {
$email_config = [
'label' => '',
'description' => '',
'category' => '',
];
// Fallback config for the null-type branch. Apply the shared
// defaults so the serialized output shape stays uniform.
$email_config = self::apply_config_defaults(
[
'label' => '',
'description' => '',
'category' => '',
]
);
}
$html_payload = get_post_meta( $post_id, \Newspack_Newsletters::EMAIL_HTML_META, true );
if ( ! $html_payload || empty( $html_payload ) ) {
Expand All @@ -416,18 +452,23 @@ private static function serialize_email( $type = null, $post_id = 0 ) {
$edit_link = str_replace( site_url(), '', $post_link );
}
$serialized_email = [
'type' => $type,
'category' => $email_config['category'],
'label' => $email_config['label'],
'description' => $email_config['description'],
'post_id' => $post_id,
'edit_link' => $edit_link,
'subject' => get_the_title( $post_id ),
'from_name' => isset( $email_config['from_name'] ) ? $email_config['from_name'] : self::get_from_name(),
'from_email' => isset( $email_config['from_email'] ) ? $email_config['from_email'] : self::get_from_email(),
'reply_to_email' => isset( $email_config['reply_to_email'] ) ? $email_config['reply_to_email'] : self::get_reply_to_email(),
'status' => get_post_status( $post_id ),
'html_payload' => $html_payload,
'type' => $type,
'category' => $email_config['category'],
'label' => $email_config['label'],
'description' => $email_config['description'],
'post_id' => $post_id,
'edit_link' => $edit_link,
'subject' => get_the_title( $post_id ),
'from_name' => isset( $email_config['from_name'] ) ? $email_config['from_name'] : self::get_from_name(),
'from_email' => isset( $email_config['from_email'] ) ? $email_config['from_email'] : self::get_from_email(),
'reply_to_email' => isset( $email_config['reply_to_email'] ) ? $email_config['reply_to_email'] : self::get_reply_to_email(),
'status' => get_post_status( $post_id ),
'html_payload' => $html_payload,
'trigger_description' => $email_config['trigger_description'],
'recipient' => $email_config['recipient'],
'recommended' => $email_config['recommended'],
'chip' => $email_config['chip'],
'source' => isset( $email_config['source'] ) ? $email_config['source'] : 'newspack',
];

return $serialized_email;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public static function add_email_config( $configs ) {
'description' => __( 'Email sent to invite a reader to join a group subscription.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/group-subscription-invite.php',
'editor_notice' => __( 'This email will be sent when a reader is invited to join a group subscription.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to invite a reader to join a group subscription.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => false,
'chip' => 'reader-revenue',
'available_placeholders' => [
[
'label' => __( 'the site title', 'newspack-plugin' ),
Expand Down
149 changes: 149 additions & 0 deletions includes/plugins/woocommerce/class-woocommerce-emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,161 @@ class WooCommerce_Emails {
*/
const WOOCOMMERCE_EMAILS_UPDATED_OPTION = 'newspack_woocommerce_block_editor_emails_updated_to_latest';

/**
* Curated metadata for WooCommerce emails surfaced in the unified
* Emails UI, keyed by WC_Email->id. Unrecognized WC emails (those
* WC()->mailer()->get_emails() returns but this table doesn't list)
* are silently skipped at registration time, as are entries whose
* `plugin_dependency` isn't active.
*
* `plugin_dependency` is the plugin slug; the full file path is built
* at check time as "{slug}/{slug}.php". Use null for emails available
* from core WooCommerce.
*
* Note: this method returns the table (rather than a const) so the
* `label` and `trigger_description` strings can use __() — PHP < 8.2
* does not allow function calls in const arrays.
*
* @return array Metadata table keyed by WC_Email->id.
*/
private static function get_surfaced_wc_emails() {
return [
// WooCommerce core — available whenever WC is active.
'customer_new_account' => [
'chip' => 'auth-account',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => null,
'label' => __( 'New account', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a customer creates a new account.', 'newspack-plugin' ),
],
'customer_refunded_order' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => false,
'plugin_dependency' => null,
'label' => __( 'Order refund', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when an order is refunded.', 'newspack-plugin' ),
],
'new_order' => [
'chip' => 'reader-revenue',
'recipient' => 'admin',
'recommended' => false,
'plugin_dependency' => null,
'label' => __( 'New order', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to the admin when a new order is placed.', 'newspack-plugin' ),
],
// WooCommerce Subscriptions.
'customer_notification_auto_renewal' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'Renewal reminder', 'newspack-plugin' ),
'trigger_description' => __( 'Sent before automatic renewal (timing depends on WooCommerce Subscriptions settings).', 'newspack-plugin' ),
],
'customer_payment_retry' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'Failed order retry', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a renewal payment fails, before the retry attempt.', 'newspack-plugin' ),
],
'expired_subscription' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'Subscription expired', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a subscription reaches its expiration date.', 'newspack-plugin' ),
],
'customer_completed_switch_order' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'Subscription switch complete', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader switches their subscription.', 'newspack-plugin' ),
],
// WooCommerce Subscriptions Gifting (bundled into WC Subs core).
'WCSG_Email_Customer_New_Account' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'New giftee account', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to the giftee when a gift subscription creates their account.', 'newspack-plugin' ),
],
'recipient_completed_order' => [
'chip' => 'reader-revenue',
'recipient' => 'reader',
'recommended' => true,
'plugin_dependency' => 'woocommerce-subscriptions',
'label' => __( 'New gift order', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to the giftee to notify them of a gift subscription.', 'newspack-plugin' ),
],
];
}

/**
* Initialize hooks.
*/
public static function init() {
add_filter( 'option_woocommerce_feature_block_email_editor_enabled', [ __CLASS__, 'override_woocommerce_email_editor_option' ], 10, 2 );
add_action( 'admin_init', [ __CLASS__, 'update_woocommerce_emails_to_latest' ] );
add_filter( 'newspack_email_configs', [ __CLASS__, 'get_email_configs' ] );
}

/**
* Inject surfaced WooCommerce emails into the unified email config set.
*
* Discovery-based: iterates WC()->mailer()->get_emails() and looks up
* each instance's id in self::SURFACED_WC_EMAILS. Unrecognized WC
* emails are silently skipped; that's the contract — only emails we
* explicitly curate appear in the unified UI. Entries whose
* plugin_dependency isn't active are also skipped.
*
* Each registered config carries the live WC_Email instance as
* `wc_email_instance` so downstream code (toggle endpoint, first-run
* auto-enable, response builder) can call instance methods directly
* instead of re-resolving via WC()->mailer().
*
* @param array $configs Existing email configs from upstream providers.
* @return array Configs with surfaced WC emails added.
*/
public static function get_email_configs( $configs ) {
if ( ! class_exists( 'WooCommerce' ) || ! function_exists( 'WC' ) ) {
return $configs;
}
$surfaced = self::get_surfaced_wc_emails();
$wc_emails = \WC()->mailer()->get_emails();
foreach ( $wc_emails as $wc_email ) {
if ( ! isset( $surfaced[ $wc_email->id ] ) ) {
continue;
}
$meta = $surfaced[ $wc_email->id ];
if ( ! empty( $meta['plugin_dependency'] ) ) {
$plugin_file = $meta['plugin_dependency'] . '/' . $meta['plugin_dependency'] . '.php';
if ( ! \Newspack\is_plugin_active( $plugin_file ) ) {
continue;
}
}
$configs[ $wc_email->id ] = [
'name' => $wc_email->id,
'category' => 'woocommerce',
'source' => 'woocommerce',
'label' => $meta['label'],
'description' => $meta['trigger_description'],
'trigger_description' => $meta['trigger_description'],
'recipient' => $meta['recipient'],
'recommended' => $meta['recommended'],
'chip' => $meta['chip'],
'plugin_dependency' => $meta['plugin_dependency'],
'wc_email_instance' => $wc_email,
];
}
return $configs;
}

/**
Expand Down
32 changes: 32 additions & 0 deletions includes/reader-activation/class-reader-activation-emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ public static function add_email_configs( $configs ) {
'description' => __( "Email sent to the reader after they've registered.", 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/verification.php',
'editor_notice' => __( 'This email will be sent to a reader after they\'ve registered.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader needs to verify their email address.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => true,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -93,6 +97,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email sent to users with a login link generated by Admin user or when one-time password is disabled.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/magic-link.php',
'editor_notice' => __( 'This email will be sent to a reader when they request a login link.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader requests a magic login link.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => true,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -110,6 +118,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email sent to users with a one-time password and login link.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/otp.php',
'editor_notice' => __( 'This email will be sent to a reader when they request a login link and a one-time password is available.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader logs in with a one-time password.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => true,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -135,6 +147,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email with password reset link.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/password-reset.php',
'editor_notice' => __( 'This email will be sent to a reader when they request a password creation or reset.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader requests a password reset.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => true,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -152,6 +168,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email with account deletion link.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/delete-account.php',
'editor_notice' => __( 'This email will be sent to a reader when they request an account deletion.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a reader requests to delete their account.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => false,
'chip' => 'auth-account',
'available_placeholders' => [
[
'label' => __( 'the account deletion link', 'newspack-plugin' ),
Expand All @@ -167,6 +187,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email sent to the reader to confirm or cancel a request to update their email address.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/change-email.php',
'editor_notice' => __( 'This email will be sent to a reader\'s new email address after they update their email address.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to the new address to confirm an email change.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => false,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -188,6 +212,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email sent to notify the reader of a request to update their email addresses.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/change-email-cancel.php',
'editor_notice' => __( 'This email will be sent to a reader\'s existing email address after they update their email address.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent to the old address when a reader changes their email.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => false,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand All @@ -210,6 +238,10 @@ public static function add_email_configs( $configs ) {
'description' => __( 'Email reminder sent to non-reader accounts instead of reader account-related emails.', 'newspack-plugin' ),
'template' => dirname( NEWSPACK_PLUGIN_FILE ) . '/includes/templates/reader-activation-emails/non-reader.php',
'editor_notice' => __( 'This email will be sent to non-reader WP user accounts as a reminder to use standard WP login flows.', 'newspack-plugin' ),
'trigger_description' => __( 'Sent when a non-reader WordPress user tries to log in as a reader.', 'newspack-plugin' ),
'recipient' => 'reader',
'recommended' => false,
'chip' => 'auth-account',
'available_placeholders' => array_merge(
$available_placeholders,
[
Expand Down
Loading
Loading