2

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('[email protected]','Successfully removed the hook','Message');
    }
    else {
        wp_mail('[email protected]','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('[email protected]','Successfully removed the hook','Message');
    }
    else {
        wp_mail('[email protected]','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.

1
  • Note that a more reliable mechanism than wp_mail for debugging, is wp_die, or error_log, as emails get caught by spam filters
    – Tom J Nowell
    Commented Aug 1, 2018 at 23:57

1 Answer 1

6

Because these are not the same, and mean different things:

  • array( $this, 'method' ) = $this->method();
  • array( 'classname', 'method' ) = classname::method();

One calls a method on an object, the other calls a static method on a class.

Since what you're trying to unhook is not a static method, you need a reference to the object, hence why your second example works, and the first doesn't.

2
  • 1
    Thank you so much for the fast response. I was really hoping I was just doing something wrong. I would have rather that be the case so I wasn't modifying code inside the plugin. It would be great to know if others have run into this same problem. Is there a different/better way for me to accomplish my goal?
    – rrwebdev
    Commented Aug 1, 2018 at 23:44
  • No, your second example of using global to gain a reference to the object is what you were supposed to do. If it feels hacky or wrong, it's because using global variables is bad practice, and the plugin author should have known better
    – Tom J Nowell
    Commented Aug 1, 2018 at 23:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.