Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Woocommerce admin_notices does not work when woocommerce_order_status_changed hook fires

I'm new to WordPress development and I'm currently encountering a dead-end.

I want an admin notice to be displayed in a WooCommerce order after the order's status has been changed.

With the following code, the notice doesn't appear:

<?php

class TestNotice {
    public function testTheNotice() {
        add_action('woocommerce_order_status_changed', [$this, 'test'], 10, 4);
    }

    public function test(int $id, string $statusFrom, string $statusTo, WC_Order $order)
    {
        add_action('admin_notices', [$this, 'notice']);
    }

    public function notice()
    {
        ?>
        <div class="notice notice-error is-dismissible">
            <p>This notice appears on the order page.</p>
        </div>
        <?php
    }

}

$testNotice = new TestNotice();
$testNotice->testTheNotice();

I have tried setting the "priority" parameter of the "admin_notices" action to 20, without success (I think it would have been useful if the nested action was the same as the one first called).

However, when I call "admin_notices" action directly in testTheNotice() method (and thus not calling "woocommerce_order_status_changed" action), it works (on every admin page, which is not what I want).

I thought it was because the notice() was somehow not recognised, but it actually is: the code below displays "This notice appears on the order page." on a blank page (which is not what I want and only for test purpose).

<?php

class TestNotice {
    public function testTheNotice() {
        add_action('woocommerce_order_status_changed', [$this, 'test'], 10, 4);
    }

    public function test(int $id, string $statusFrom, string $statusTo, WC_Order $order)
    {
        call_user_func([$this, 'notice']); die();
    }

    public function notice()
    {
        ?>
        <div class="notice notice-error is-dismissible">
            <p>This notice appears on the order page.</p>
        </div>
        <?php
    }

}

$testNotice = new TestNotice();
$testNotice->testTheNotice();

I'm aware there's a special class and method for WooCommerce admin notices, and writing below code in notice() displays a notice, but with a purple border (because of the "update" css class, which I haven't found how to change) instead of a red border (which would be possible thanks to the "error" css class, which I don't know how to apply).

$adminNotice = new WC_Admin_Notices();
$adminNotice->add_custom_notice("Test",'<p>This notice appears on the order page.</p>');
$adminNotice->output_custom_notices();
like image 329
Chloé Avatar asked Nov 05 '21 18:11

Chloé


Video Answer


1 Answers

Good question. It got me curious and made me dig into this WC_Admin_Notices class. And here's what I found out!

Well, before I talk about WC_Admin_Notices class, first let's talk about your first question!

"the notice doesn't appear"

Because when the woocommerce_order_status_changed hook fires there is no screen associated with it and it's not just notices, for example if you try to do a print_r and/or an echo they won't show anything either because there is no screen associated with that hook. The only way you could find out that you hit that hook is by using die function. In order to test this, you could do this:

add_action('woocommerce_order_status_changed', 'test', 99, 4);

function test($order_id, $old_status, $new_status, $order_object)
{
  die('You hit the right hook!');
}

But if you replace the die('You hit the right hook!') function with echo or print_r, they won't show anything no matter how high you set the priority of the hook.


It gets interesting when you use WC_Admin_Notices class. If there is no screen associated with the woocommerce_order_status_changed hook, then how does this class work? Well, here's how:

class-wc-admin-notices.phpClass

  1. It has a property called $notices which is a simple empty array.
  2. When you call add_custom_notice method, it'll store the values you gave it, into the database and into the "options" table. The key would be "woocommerce_admin_notice_{the name you gave for example test}", and the value would be the message/notice you defined. That's all it does! It stores your message/notice into the database.
  3. When you call output_custom_notices method, it'll check the $notices array, and check the database for any key value pairs stored in the options table with the format I just mentioned in number 2! Then, it uses a template called html-notice-custom.php in the following path:

yourwebsite.com > wp-content > plugins > woocommerce > includes > admin > views > html-notice-custom.php
html-notice-custom.phpTemplate

The html-notice-custom.php file is responsible for outputting the html markup for custom notices and it'll give them a class called updated which will trigger a css rule that has a light purple color.


So the overall workflow of the WC_Admin_Notices class is:
  • Stores your custom message into either an array or in the database
  • Retrieves the stored key and value pairs when the screen loads!

Technically, that's all it does, well, in the simplest terms!


Since we don't want that custom light purple template, we could use the workflow that WC_Admin_Notices uses and write our own solution without using WC_Admin_Notices class.

We could use one of the following approaches:

  • Use the combination of $_SESSION + admin_notices hook
  • Use the combination of cookie or local storage + admin_notices hook
  • Use the combination of database + admin_notices hook

I think, if we use $_SESSION + admin_notices hook approach, it'd be both safe and fast without even query the database for a simple string of text. Here's the solution I can think of at this moment:

Code goes into the functions.php file of your active theme.

add_action('woocommerce_order_status_changed', 'test', 99, 4);

function test($order_id, $old_status, $new_status, $order_object)
{

  if ( $order_object && ($old_status != $new_status) ) 
  {

    $notice = 'This notice appears on the order page.';

    session_start();

    $_SESSION['your_custom_message_name'] = $notice;

  }
}

add_action('admin_notices', 'your_them_displaying_custom_admin_notice');

function your_them_displaying_custom_admin_notice()
{
  session_start();
  if (isset($_SESSION['your_custom_message_name'])) {

?>
    <div class='notice notice-error is-dismissible'>
      <p><?php echo $_SESSION['your_custom_message_name'] ?></p>
    </div>
<?php
    unset($_SESSION['your_custom_message_name']);
  }
}

Note:

  • This works only if there is an order AND the $old_status is not equal to the $new_status.
  • In the $notice variable, I put raw text, not html, since we put a p tag in the html section/template of the admin_notices hook.
  • I've used notice-error class for the div tag which shows the red color for the notice. You could replace it with notice-warning, or notice-info or notice-success.

This answers has been fully tested on woocommerce 5.7 and works fine.

like image 90
Ruvee Avatar answered Oct 09 '22 20:10

Ruvee