• Resolved magnificentjake

    (@magnificentjake)


    Hi folks,

    I’m in the middle of developing a custom plugin for integrating my store with a third-party. As part of that integration I’m sending a new email, which (after some kind help on these forums!) is sending fine. The trouble is, the email in question is sending twice and I’m not sure why. I’m sure it’s something simple, but I can’t spot what it is.

    Can any of you kind folks spot where I’m going wrong in my email class below? I *think* this is where the problem ought to be.

    <?php
    
    if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
    
    if ( ! class_exists( 'Foundry_Code_Email', false ) ) :
    
    	class Foundry_Code_Email extends WC_Email {
    
    		/**
    		* Set email defaults
    		*/
    
    		public function __construct() {
    		
    
    			$this->id = 'foundry_code_email'; // Unique ID for custom email
    			$this->customer_email = true; // Is a customer email
    			$this->title = __( 'Foundry Premium Content Email', 'woocommerce' ); // Title field in WooCommerce Email settings
    			$this->description = __( 'Foundry email is sent when customer purchases Foundry Premium Content', 'woocommerce' ); // Description field in WooCommerce email settings
    			$this->template_base	= WP_PLUGIN_DIR . '/foundry-premium-content/templates/';
    			$this->template_html = 'emails/foundry-code-email.php';
    			$this->template_plain = 'emails/plain/foundry-code-email.php';
    			// $this->template_html = 'emails/customer-refunded-order.php';
    			$this->placeholders = array(
    				'{site_title}'	=> $this->get_blogname(),
    				'{order_date}'	=> '',
    				'{order_number}' => '',
    			);
    
    			// Trigger email when woocommerce_order_status_completed_notification is called when payment is complete
    			add_action( 'woocommerce_order_status_completed_notification', array( $this, 'trigger' ), 10, 2 );
    
    			// Call parent constructor to load any other defaults not explicitly defined here.
    			parent::__construct();
    		}
    
    		/**
    		* Prepares email content and triggers the email
    		*
    		* @param int $order_id
    		*/
    		public function trigger( $order_id, $order = false ) {
    			$this->setup_locale();	
    
    			if ( $order_id && ! is_a( $order, 'WC_Order') ) {
    				$order = wc_get_order( $order_id );
    			}
    
    			if ( is_a( $order, 'WC_Order' ) ) {
    				$this->object							= $order;
    				$this->recipient						= $this->object->get_billing_email();
    				$this->placeholders['{order_date}']		= wc_format_datetime( $this->object->get_date_created() );
    				$this->placeholders['{order_number}']	= $this->object->get_order_number();
    
    				// Maybe include an additional check to make sure that stuff happened
    			}
    
    			if ( $this->is_enabled() && $this->get_recipient() ) {
    				// All well, send the email
    				$this->send ( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
    			}
    
    			$this->restore_locale();
    
    			// Add order note about the same
    			$this->object->add_order_note( sprintf(__('%s email sent to the customer.', 'woocommerce'), $this->get_title() ) );
    
    			// Set order meta to indicate that the welcome email was sent
    			update_post_meta( $order_id, 'foundry_code_email_sent', 1);
    		} 
    
    		/**
    		* Get email subject.
    		*
    		* @since 3.1.0
    		* @return string
    		*/
    		public function get_default_subject() {
    			return __( 'Foundry Premium Content Codes', 'woocommerce' );
    		}
    
    		/**
    		* Get email heading
    		*
    		* @since 3.1.0
    		* @return string
    		*/
    		public function get_default_heading() {
    			return __('Your Foundry Premium Content Codes:', 'woocommerce' );
    		}
    
    		/**
    		* get_content_html function
    		*
    		* @return string
    		*/
    		public function get_content_html() {
    			return wc_get_template_html(
    					$this->template_html,
    					array(
    						'order'              => $this->object,
    						'email_heading'      => $this->get_heading(),
    						'additional_content' => $this->get_additional_content(),
    						'sent_to_admin'      => false,
    						'plain_text'         => false,
    						'email'              => $this,
    					), '', $this->template_base
    				);
    		}
    
    		public function get_content_plain() {
    			return wc_get_template_html(
    					$this->template_plain,
    					array(
    						'order'              => $this->object,
    						'email_heading'      => $this->get_heading(),
    						'additional_content' => $this->get_additional_content(),
    						'sent_to_admin'      => false,
    						'plain_text'         => true,
    						'email'              => $this,
    					), '', $this->template_base
    				);
    		} 
    
    		public function get_default_additional_content() {
    			return __( 'Thanks for shopping with us.', 'woocommerce' );
    		}
    
    		/*
    		* Initialise settings form fields
    		*/
    		public function init_form_fields() {
    			$this->form_fields = array(
    				'enabled'	=> array(
    						'title'		=> __( 'Enable/Disable', 'woocommerce' ),
    						'type'		=> 'checkbox',
    						'label'		=> 'Enable this email notification',
    						'default'	=> 'yes'
    				),
    				'subject'	=> array(
    						'title'		=> __( 'Subject', 'woocommerce' ),
    						'type'		=> 'text',
    						'desc_tip'	=> true,
    						'description'	=> sprintf( 'This controls the email subject line. Leave blank to use the default subject: <code>%s</code>.', $this->get_subject() ),
    						'placeholder'	=> $this->get_default_subject(),
    						'default'	=> ''
    				),
    				'heading'	=> array(
    						'title'		=> __( 'Email Heading', 'woocommerce' ),
    						'type'		=> 'text',
    						'desc_tip'	=> true,
    						'description'	=> sprintf( __( 'This controls the main heading contained within the email notification. Leave blank to use the default heading: <code>%s</code>.' ), $this->get_heading() ),
    						'placeholder'	=> $this->get_default_heading(),
    						'default'	=> ''
    				),
    				'email_type'	=> array(
    						'title'		=> __( 'Email type', 'woocommerce'),
    						'type'		=> 'select',
    						'description'	=> __( 'Choose which format of email to send.', 'woocommerce' ),
    						'default'	=> 'html',
    						'class'		=> 'email_type wc-enhanced-select',
    						'options'	=> $this->get_email_type_options(),
    						'desc_tip'	=> true,
    				)
    			);
    		} 
    	}
    
    endif;
    
    return new Foundry_Code_Email();
    
    ?>
Viewing 7 replies - 1 through 7 (of 7 total)
  • Moderator bcworkz

    (@bcworkz)

    The action that triggers the sending of email is from WooCommerce. You could ask in their dedicated support forum why it’s triggering twice.

    Generally speaking though, it’s not uncommon for actions to unexpectedly trigger more than once per request. While redundant, it’s often of no consequence when callback code executes more than once. Except when it results in observable results like counting more than once, or sending multiple messages. To prevent this, the added callback can remove itself from the action hook stack so it cannot be called again until the next request no matter how many more times the action fires.

    You might think it’d more logical to check the related ‘foundry_code_email_sent’ meta value before sending, but doing so would be unreliable because writing to the DB creates a race condition where the next action iteration is likely to execute before the value can be written to the DB.

    https://developer.wordpress.org/reference/functions/remove_action/

    Thread Starter magnificentjake

    (@magnificentjake)

    Sure thing, thanks very much for pointing me in that direction; I’m trying to test that approach at the moment.

    Am I right in saying I need to add this somewhere in the trigger function then?

    remove_action( 'woocommerce_order_status_completed_notification', 'trigger', 10 );

    If I’m right in that approach (probably unlikely!) it doesn’t appear to have fixed the issue unfortunately so perhaps some more sleuthing required.

    Thread Starter magnificentjake

    (@magnificentjake)

    Ok, so I actually managed to fix it myself, but I’m not sure if my fix is problematic? Removing the return new Foundry_Code_Email(); clause at the end of the php file appears to have sorted it, but I notice that all the WooCommerce core templates have a return clause at the end.

    Is this going to cause problems?

    Moderator bcworkz

    (@bcworkz)

    Yeah, no need to return from a class declaration, but don’t you still need to instantiate your class with new? Or is it done elsewhere? I’m not aware of any WooCommerce class declarations with return statements. Methods within a class might return values, but not the declaration itself.

    Thread Starter magnificentjake

    (@magnificentjake)

    I’d been basing a lot of my class file on the files in woocommerce/includes/emails , most (all?) of which have a return new WC_Email_Customer_Completed_Order(); or return new WC_Email_New_Order(); (and so forth) line right at the end of the file. Omitting my equivalent of that line nipped the double email in the bud, but I’m just worried something elsewhere might break without it.

    Thread Starter magnificentjake

    (@magnificentjake)

    It turns out I had another return clause in my functions file where I was doing the inclusion statement. All fixed now – thanks for the help!

    Moderator bcworkz

    (@bcworkz)

    FYI, the return statements after email class declarations are so the related include statements elsewhere are able to assign the new object to a variable. Normally include simply returns true. The return statement in the included file overrides this. Unless you are doing similarly, the return statement serves no purpose except to create an extra instance you don’t want or need added to the ‘woocommerce_order_status_completed_notification’ action stack.

    Because two instances are involved, my earlier remove action suggestion does not work. That technique solves a problem that is not happening in this case.

Viewing 7 replies - 1 through 7 (of 7 total)

The topic ‘Custom Email Triggering Twice’ is closed to new replies.