apply_filters() slices away needed arguments

I’m trying to add a button (its function is beyond the scope of this question, suffice it to say that it needs the refund id as an argument) to each refund line in a WooCommerce order. I found that those lines are created in woocommerce\includes\admin\meta-boxes\views\html-order-refund.php which can’t be overridden. However there is an action:

do_action( 'woocommerce_admin_order_item_values', null, $refund, $refund->get_id() );

This seemed perfect for my purpose, so I tried adding the following code to functions.php to test if it could work:

add_filter('woocommerce_admin_order_item_values', 'test_refund_id');
function test_refund_id($nullvar, $refund, $refund_id) {
    if ( !empty($refund->refunded_by) ){
        echo '<td>Refund ID: '.$refund_id.'</td>';  
    }
}

I used if ( !empty($refund->refunded_by) ) because woocommerce_admin_order_item_values also gets called in html-order-shipping.php, html-order-item.php and html-order-fee.php, with an “order item” object (instead of a “refund” object) as the second argument, so there’s no refunded_by property and my instruction should be ignored.

Much to my disappointment, though, I get the following error:

Fatal error: Uncaught ArgumentCountError: Too few arguments to
function test_refund_id(), 1 passed in \wp-includes\class-wp-hook.php
on line 290 and exactly 3 expected in
\wp-content\themes\mytheme\functions.php:1087

I know that, as a matter of fact, three arguments are passed in the line I quoted above (namely a null, the refund object and the refund id), but I went and checked the script the error was referring to (\wp-includes\class-wp-hook.php) around line 290, and I found this in the apply_filters() function:

        // Avoid the array_slice if possible.
        if ( $the_['accepted_args'] == 0 ) {
            $value = call_user_func( $the_['function'] );
        } elseif ( $the_['accepted_args'] >= $num_args ) {
            $value = call_user_func_array( $the_['function'], $args );
        } else {
            $value = call_user_func_array( $the_['function'], array_slice( $args, 0, (int) $the_['accepted_args'] ) );
        }

Just for testing purposes I tried to temporarily change the last “else” clause, replacing the “sliced” arguments array with the original one, thereby forcing the script to keep all the arguments anyway, so array_slice( $args, 0, (int) $the_['accepted_args'] ) became simply $args. Surprise, surprise, now everything works smoothly!

enter image description here

Now, I’m well aware that’s of course not a solution since I can’t edit a core WP script. I would just want to understand WHY apply_filters() thinks that the accepted arguments are less than the passed ones and therefore it trims the argument array down to just the first one.

Thanks a lot in advance to anyone who’d like to shed some light on the matter.

2 Answers
2

Two inherent problems with your script as posted. I’m not certain this will solve your problem, but it’s too big to address in the comments.

First, you need to use add_action(), not add_filter(). That by itself is not a huge deal because add_action() is just a wrapper for add_filter(), but you should still use the correct one.

The other problem may be your passed arguments.

I know that, as a matter of fact, three arguments are passed in the
line I quoted above (namely a null, the refund object and the refund
id),

Actually, the way you have it, you’re not passing three arguments, you’re only passing one. But your function as written is expecting three.

Your add_action() uses the required $tag and $function_to_add values, but leaves off the optional $priority and $accepted_args. The default of $accepted_args is “1”, so anytime you’re passing more than a single value with add_action() or add_filter(), you must define the $accepted_args value.

Try it as follows, noting the final value of “3” – that’s the $accepted_args value that tells it to pass more than 1 argument:

add_action('woocommerce_admin_order_item_values', 'test_refund_id', 10, 3 );
function test_refund_id($nullvar, $refund, $refund_id) {
    if ( !empty($refund->refunded_by) ){
        echo '<td>Refund ID: '.$refund_id.'</td>';  
    }
}

Leave a Comment