Remove action from a plugin class, forced to use global instance

I have been trying desperately to find a solution to this problem. There are instances of this issue all over this site (and others) but none of the answers help in my case. If you want to skip the background and get to my question, it is at then end after the code snippets.

How I got here:
I need to remove an action that was added by a plugin within a class that was not instantiated. Meaning, in the class in question there was no global instantiation like this(example):

global $Class_name;
$Class_name = new This_Class;

Within the plugin (Woocommerce Services), the action (that must be removed) was added like so:

add_action( ‘woocommerce_email_after_order_table’, array( $this, ‘add_tracking_info_to_emails’ ), 10, 3 );

I have seen and read numerous articles on how to successfully remove an action. They are all helpful by suggesting the following:

1) Ensure timing is correct. You can only remove an action after the action has been added in the first place.

2) You must remove the action by using the same priority in which the action was added.

However, when it comes to removing an action that was added in a class with no instance… they have fallen short. Most suggest removing the action this way by passing in the class name itself(in this case ‘WC_Connect_Loader’):

remove_action( ‘woocommerce_email_after_order_table’, array( ‘WC_Connect_Loader’, ‘add_tracking_info_to_emails’ ), 10 );

Some have claimed the above to work for them so I tried and tried to make this work for me with no luck. I made sure I was following guidance from my research.
1) I confirmed that I was attempting to remove the action after it was added (Check).
2) I confirmed I was using the same priority to remove the action as when it was added, in this case 10 (Check).
I was at a loss.

After much debugging and experimenting, it became clear that I will not be able to remove the action simply by passing in the class name. I wound up having to modify the plugin itself (which I hate) to create a global instance of the class ‘WC_Connect_Loader’ and then pass in the instance when attempting to remove the action.

For the sake of this post/question, I have included the debug version of my code to show how I was able to confirm which was working and which was not. I’m sure there a better methods than wp_mail() but this was the easiest for me. I have WP send me an email that allows me to determine success/failure.

In the two cases below, there is only slight changes. I have commented the lines that are changed.

This does NOT work:

//woocommerce-services.php:

public function attach_hooks() {

add_action( ‘woocommerce_email_after_order_table’, array( $this, ‘add_tracking_info_to_emails’ ), 10, 3 );

}

if ( ! defined( ‘WC_UNIT_TESTING’ ) ) {
new WC_Connect_Loader();
}

//functions.php

add_action(‘woocommerce_init’,’remove_add_tracking_info_to_emails’,999);
function remove_add_tracking_info_to_emails() {
if (remove_action(‘woocommerce_email_after_order_table’,array(‘WC_Connect_Loader’,’add_tracking_info_to_emails’),10)){
wp_mail(‘myemail@xyz.com’,’Successfully removed the hook’,’Message’);
}
else {
wp_mail(‘myemail@xyz.com’,’Failed to remove the hook’,’Message’);
}
}

This DOES work:

//woocommerce-services.php

public function attach_hooks() {

add_action( ‘woocommerce_email_after_order_table’, array( $this, ‘add_tracking_info_to_emails’ ), 10, 3 );

}

if ( ! defined( ‘WC_UNIT_TESTING’ ) ) {
global $WC_Connect; // created global
$WC_Connect = new WC_Connect_Loader(); // Class ‘WC_Connect_Loader’ now has a global instance
}

// functions.php

add_action(‘woocommerce_init’,’remove_add_tracking_info_to_emails’,999);
function remove_add_tracking_info_to_emails() {
global $WC_Connect; // Global instance of class
if (remove_action(‘woocommerce_email_after_order_table’,array($WC_Connect,’add_tracking_info_to_emails’),10)){ // Pass in global instance here
wp_mail(‘myemail@xyz.com’,’Successfully removed the hook’,’Message’);
}
else {
wp_mail(‘myemail@xyz.com’,’Failed to remove the hook’,’Message’);
}
}

My question:
Why is it that I cannot pass in the class name as so many have suggested and claimed to work? Why must I pass in the class instance in order for this to work?

I can’t seem to find anything that helps me understand this. I’m hoping for 1 of 2 outcomes by posting this question:

1) Someone can explain this limitation as simply unavoidable and the plugin should have been written correctly to be extensible. Hopefully someone else will benefit from the time I have spent working on this and those that respond.

2) I am missing something and am not as clever as I think. In which case, I’m willing to bet I’m not the first person to run into this issue and more than myself will benefit from this question.

Read more here:: Remove action from a plugin class, forced to use global instance

Leave a Reply

Your email address will not be published. Required fields are marked *