Question

I have a post type the uses post_save to take the address from the post-meta and retrieve the lat/lng coordinates from the Google API. I need a way of notifying the user if there was an issue with retrieving the coordintes. I tried using admin_notices, but nothing displayed:

public static function update_notice() {
  echo "<div class='error'><p>Failed to retrieve coordinates. Please check key and address.<p></div>";
  remove_action('admin_notices', 'update_notice');
}

add_action('admin_notices', array('GeoPost', 'update_notice'));

I'm not sure if I'm using it incorrectly or in the wrong context. To be clear, in the actual code the add_action is in another function in the same class. That's working fine.

Was it helpful?

Solution

The reason this doesn't work is because there is a redirection happening after the save_post action. One way you can acheive want you want is by implementing a quick work around using query vars.

Here is a sample class to demonstrate:

class My_Awesome_Plugin {
  public function __construct(){
   add_action( 'save_post', array( $this, 'save_post' ) );
   add_action( 'admin_notices', array( $this, 'admin_notices' ) );
  }

  public function save_post( $post_id, $post, $update ) {
   // Do you stuff here
   // ...

   // Add your query var if the coordinates are not retreive correctly.
   add_filter( 'redirect_post_location', array( $this, 'add_notice_query_var' ), 99 );
  }

  public function add_notice_query_var( $location ) {
   remove_filter( 'redirect_post_location', array( $this, 'add_notice_query_var' ), 99 );
   return add_query_arg( array( 'YOUR_QUERY_VAR' => 'ID' ), $location );
  }

  public function admin_notices() {
   if ( ! isset( $_GET['YOUR_QUERY_VAR'] ) ) {
     return;
   }
   ?>
   <div class="updated">
      <p><?php esc_html_e( 'YOUR MESSAGE', 'text-domain' ); ?></p>
   </div>
   <?php
  }
}

Hope this helps you a little bit. Cheers

OTHER TIPS

Made a wrapper class for this kind of scenario. Actually the class can be used in any scenario involving displaying notices. I use the PSR standards, so the naming is atypical of Wordpress code.

class AdminNotice
{
    const NOTICE_FIELD = 'my_admin_notice_message';

    public function displayAdminNotice()
    {
        $option      = get_option(self::NOTICE_FIELD);
        $message     = isset($option['message']) ? $option['message'] : false;
        $noticeLevel = ! empty($option['notice-level']) ? $option['notice-level'] : 'notice-error';

        if ($message) {
            echo "<div class='notice {$noticeLevel} is-dismissible'><p>{$message}</p></div>";
            delete_option(self::NOTICE_FIELD);
        }
    }

    public static function displayError($message)
    {
        self::updateOption($message, 'notice-error');
    }

    public static function displayWarning($message)
    {
        self::updateOption($message, 'notice-warning');
    }

    public static function displayInfo($message)
    {
        self::updateOption($message, 'notice-info');
    }

    public static function displaySuccess($message)
    {
        self::updateOption($message, 'notice-success');
    }

    protected static function updateOption($message, $noticeLevel) {
        update_option(self::NOTICE_FIELD, [
            'message' => $message,
            'notice-level' => $noticeLevel
        ]);
    }
}

Usage:

add_action('admin_notices', [new AdminNotice(), 'displayAdminNotice']);
AdminNotice::displayError(__('An error occurred, check logs.'));

The notice is displayed once.

In addition to @jonathanbardo's answer which is great and functions well, if you want to remove the query argument after the new page is loaded, you can use the removable_query_args filter. You get an array of argument names to which you can append your own argument. Then WP will take care of removing all of the arguments in the list from the URL.

public function __construct() {
    ...
    add_filter('removable_query_args', array($this, 'add_removable_arg'));
}

public function add_removable_arg($args) {
    array_push($args, 'my-query-arg');
    return $args;
}

Something like:

'...post.php?post=1&my-query-arg=10'

Will become:

'...post.php?post=1'

Simple, elegant, based on get_settings_errors().

function wpse152033_set_admin_notice($id, $message, $status = 'success') {
    set_transient('wpse152033' . '_' . $id, [
        'message' => $message,
        'status' => $status
    ], 30);
}

function wpse152033_get_admin_notice($id) {
    $transient = get_transient( 'wpse152033' . '_' . $id );
    if ( isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] && $transient ) {
        delete_transient( 'wpse152033' . '_' . $id );
    }
    return $transient;
}

Usage

In your post request handler:

wpse152033_set_admin_notice(get_current_user_id(), 'Hello world', 'error');
wp_redirect(add_query_arg('settings-updated', 'true',  wp_get_referer()));

Where you want to use the admin notice, usually in the admin_notices hook.

$notice = $this->get_admin_notice(get_current_user_id());
if (!empty($notice) && is_array($notice)) {
    $status = array_key_exists('status', $notice) ? $notice['status'] : 'success';
    $message = array_key_exists('message', $notice) ? $notice['message'] : '';
    print '<div class="notice notice-'.$status.' is-dismissible">'.$message.'</div>';
}

You can achive this by doing a redirect and passing along query arguments with the filter redirect_post_location. There is also redirect_term_location which will work for taxonomies/terms.

First add the admin_notices action which will always be active, but will only show the notice under certain conditions.

add_action( 'admin_notices', 'general_admin_notice' );

function general_admin_notice(){
  global $pagenow;

  if ( 'post.php' === $pagenow && isset($_GET['post']) && 'custom_post_type' === get_post_type( $_GET['post'] ) ){

    if ( isset($_GET['empty'])) {
      
      // Turn string into array, so we can loop trough it.
      $terms_id = explode( ',', $_GET['empty'] );

      echo '<div class="notice notice-error is-dismissible">
                <p>';
                foreach ( $terms_id as $term_id ) {
                  $term = get_term( $term_id, 'custom_taxonomy' );
                  echo '<a href="'.get_term_link( $term ).'">'.$term->name.'</a>, ';
                }
              echo 'nutrients are empty.</p>
            </div>';
      }
    }
}

Then you need to redirect the page after save and pass a query argument with add_query_arg. The way I have done it here you can have a dynamic input which is shown in the admin notice.

        if ( !empty($empty_error) ) {
            add_filter('redirect_post_location', function($loc) use ($empty_error) {
                trigger_error( $empty_error);
                return add_query_arg( 'empty', implode(',', $empty_error), $loc );
            }); 
        }

In my case I do an array_push on the variable $empty_error with a term id. The admin notice will then show all the terms which has an error with an link to the respective term.

You can also use removable_query_args to remove the added query args, so the url looks cleaner. The admin notice will then go away if you reload the page.

add_filter('removable_query_args', 'add_removable_arg');

function add_removable_arg($args) {
    array_push($args, 'empty');
    return $args;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top