mailer()->get_emails()`), shared by the * email power-ups. * * @since 1.0.0 */ final class WooEmails { /** * Emails that are about a user account, not an order, and carry a one-time link (set or reset * a password, verify an address or a waitlist sign-up). Their content is never kept, copied * to other recipients, previewed or resent. Use account_emails(), which add-ons can extend. * * @since 1.0.0 */ const USER_EMAILS = array( 'customer_new_account', 'customer_reset_password', 'customer_verify_email', 'customer_stock_notification_verify' ); /** * Account emails: USER_EMAILS plus any an add-on names (for example its own emails with a login, * verification or password link). The filter can only add: the built-in ones always stay. * * @since 1.0.0 * @return string[] Email IDs. */ public static function account_emails() { /** * Filter the emails that carry a private one-time link (password, login or verification). * They are never stored with content in the Email log, never copied to extra recipients, * never previewed, test-sent or resent. Only adds: WooCommerce's own account emails always * stay in the list. * * @since 1.0.0 * @param string[] $ids Email IDs. */ $extra = apply_filters( 'apwc_account_emails', self::USER_EMAILS ); $extra = array_filter( is_array( $extra ) ? $extra : array(), static fn( $id ) => is_string( $id ) && '' !== $id ); return array_values( array_unique( array_merge( self::USER_EMAILS, $extra ) ) ); } /** * Whether an email carries a private one-time link (see account_emails()). * * @since 1.0.0 * @param string $id Email ID. * @return bool */ public static function is_account_email( $id ) { return in_array( (string) $id, self::account_emails(), true ); } /** * All emails by ID. * * @since 1.0.0 * @return array */ public static function all() { if ( ! function_exists( 'WC' ) || ! WC()->mailer() ) { return array(); } $out = array(); foreach ( WC()->mailer()->get_emails() as $email ) { if ( $email instanceof WC_Email && '' !== (string) $email->id ) { $out[ (string) $email->id ] = $email; } } return $out; } /** * One email by ID. * * @since 1.0.0 * @param string $id Email ID, e.g. `customer_completed_order`. * @return WC_Email|null */ public static function get( $id ) { return self::all()[ (string) $id ] ?? null; } /** * Email titles by ID, customer emails first. * * @since 1.0.0 * @param bool $orders_only Leave out account emails (new account, password reset). * @return array */ public static function titles( $orders_only = false ) { $customer = array(); $admin = array(); $accounts = $orders_only ? self::account_emails() : array(); foreach ( self::all() as $id => $email ) { if ( $orders_only && in_array( $id, $accounts, true ) ) { continue; } if ( $email->is_customer_email() ) { $customer[ $id ] = $email->get_title(); } else { $admin[ $id ] = $email->get_title(); } } return $customer + $admin; } /** * Readable title for an email ID, even when that email is gone (log entries). * * @since 1.0.0 * @param string $id Email ID. * @return string */ public static function title( $id ) { $email = self::get( $id ); return $email ? $email->get_title() : ucfirst( str_replace( '_', ' ', (string) $id ) ); } /** * Emails that can be sent again from the order alone (no note, refund or account needed). * * @since 1.0.0 * @return string[] */ public static function resendable() { /** * Filter which WooCommerce emails can be resent for an order (order actions, Email log). * Each is sent with `$email->trigger( $order_id, $order )`. Account emails (new account, * password reset) are always removed: they can't be resent from an order. * * @since 1.0.0 * @param string[] $ids Email IDs. */ $ids = (array) apply_filters( 'apwc_resendable_emails', array( 'new_order', 'cancelled_order', 'failed_order', 'customer_on_hold_order', 'customer_processing_order', 'customer_completed_order', 'customer_invoice', 'customer_failed_order', 'customer_cancelled_order' ) ); return array_values( array_diff( array_map( 'strval', $ids ), self::account_emails() ) ); } /** * Send an order email again, to the recipients WooCommerce would use now. * * @since 1.0.0 * @param string $id Email ID. * @param \WC_Order $order Order. * @return true|\WP_Error */ public static function resend( $id, \WC_Order $order ) { $id = (string) $id; // Account emails carry a login or reset link: never from here, whatever the filter says. if ( self::is_account_email( $id ) ) { return new \WP_Error( 'apwc_not_resendable', __( 'This email can’t be sent again from here.', 'admin-powerups-for-woocommerce' ), array( 'status' => 400 ) ); } $email = self::get( $id ); if ( ! $email || ! in_array( $id, self::resendable(), true ) || ! method_exists( $email, 'trigger' ) ) { return new \WP_Error( 'apwc_not_resendable', __( 'This email can’t be sent again from here.', 'admin-powerups-for-woocommerce' ), array( 'status' => 400 ) ); } // Manual emails (e.g. "Order details") have no on/off setting: is_enabled() is always false for them. if ( ! $email->is_manual() && ! $email->is_enabled() ) { /* translators: %s: email title. */ return new \WP_Error( 'apwc_email_off', sprintf( __( '“%s” is turned off in WooCommerce → Settings → Emails.', 'admin-powerups-for-woocommerce' ), $email->get_title() ), array( 'status' => 409 ) ); } // Payment gateways and shipping add their own email content; load them like WooCommerce does. WC()->payment_gateways(); WC()->shipping(); $allow = static fn() => true; add_filter( 'woocommerce_new_order_email_allows_resend', $allow ); try { if ( 'customer_invoice' === $id ) { $email->trigger( $order ); } else { $email->trigger( $order->get_id(), $order ); } } finally { remove_filter( 'woocommerce_new_order_email_allows_resend', $allow ); } /** This action is documented in woocommerce/includes/admin/meta-boxes/class-wc-meta-box-order-actions.php */ do_action( 'woocommerce_after_resend_order_email', $order, $id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce's own hook, fired like its "Resend" order action does. return true; } }