admin管理员组

文章数量:1415684

I have two custom plugins that use the WP Logging class. It creates a custom post type called Logs, which (at least in my case) is only visible inside the admin, for example at /wp-admin/edit.php?s&post_status=all&post_type=wp_log.

To make it easier to tell which plugin is creating the logs, I've added a few hooks. Both plugins use them identically. What they do is:

  1. Add a sortable Type column to the manage posts screen, and add the type field (which the WP Logging library adds) to that screen.
  2. Add a dropdown to the top of the screen (next to the date filter) and allow the posts to be filtered by which type value they use.

Here's the code I'm using:

// add a sortable Type column to the posts admin
add_filter( 'manage_edit-wp_log_columns', array( $this, 'type_column' ), 10, 1 );
add_filter( 'manage_edit-wp_log_sortable_columns', array( $this, 'sortable_columns' ), 10, 1 );
add_action( 'manage_wp_log_posts_custom_column', array( $this, 'type_column_content' ), 10, 2 );

// filter the log posts admin by log type
add_filter( 'parse_query', array( $this, 'posts_filter' ), 10, 1 );
add_action( 'restrict_manage_posts', array( $this, 'restrict_log_posts' ) );

/**
 * Add a Type column to the posts admin for this post type
 *
 * @param array $columns
 * @return array $columns
 */
public function type_column( $columns ) {
    $columns['type'] = __( 'Type', 'form-processor-mailchimp' );
    return $columns;
}

/**
 * Make the Type column in the posts admin for this post type sortable
 *
 * @param array $columns
 * @return array $columns
 */
public function sortable_columns( $columns ) {
    $columns['type'] = 'type';
    return $columns;
}

/**
 * Add the content for the Type column in the posts admin for this post type
 *
 * @param string $column_name
 * @param int $post_id
 */
public function type_column_content( $column_name, $post_id ) {
    if ( 'type' != $column_name ) {
        return;
    }
    // get wp_log_type
    $terms = wp_get_post_terms(
        $post_id,
        'wp_log_type',
        array(
            'fields' => 'names',
        )
    );
    if ( is_array( $terms ) ) {
        echo esc_attr( $terms[0] );
    }
}

/**
 * Filter log posts by the taxonomy from the dropdown when a value is present
 *
 * @param object $query
 * @return object $query
 */
public function posts_filter( $query ) {
    global $pagenow;
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    if ( is_admin() && 'edit.php' === $pagenow ) {
        if ( isset( $_GET['post_type'] ) && esc_attr( $_GET['post_type'] ) === $type ) {
            if ( isset( $_GET[ $taxonomy ] ) && '' !== $_GET[ $taxonomy ] ) {
                $query->post_type = $type;
                $query->tax_query = array(
                    array(
                        'taxonomy' => $taxonomy,
                        'field'    => 'slug',
                        'terms'    => esc_attr( $_GET[ $taxonomy ] ),
                    ),
                );
            }
        }
    }
}

/**
 * Add a filter form for the log admin so we can filter by wp_log_type taxonomy values
 *
 * @param object $query
 * @return object $query
 */
public function restrict_log_posts() {
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    // only add filter to post type you want
    if ( isset( $_GET['post_type'] ) && esc_attr( $_GET['post_type'] ) === $type ) {
        // get wp_log_type
        $terms = get_terms(
            [
                'taxonomy'   => $taxonomy,
                'hide_empty' => true,
            ]
        );
        ?>
        <select name="wp_log_type">
            <option value=""><?php _e( 'All log types ', 'form-processor-mailchimp' ); ?></option>
            <?php
            $current_log_type = isset( $_GET[ $taxonomy ] ) ? esc_attr( $_GET[ $taxonomy ] ) : '';
            foreach ( $terms as $key => $term ) {
                printf(
                    '<option value="%s"%s>%s</option>',
                    $term->slug,
                    $term->slug == $current_log_type ? ' selected="selected"' : '',
                    $term->name
                );
            }
            ?>
        </select>
        <?php
    }
}

The code is identical between the two plugins in this case. Of course the problem, if it's enabled in both plugins, is that it duplicates the display.

Is there any way I can check to see if these additions have already been made before making them?

I thought this was worth a try (on all the filters, and using doing_action on all the actions):

if ( ! doing_filter( 'manage_edit-wp_log_columns' ) ) {
    add_filter( 'manage_edit-wp_log_columns', array( $this, 'type_column' ), 10, 1 );
}

But it didn't change anything. Is there anything else I can try on this?

I have two custom plugins that use the WP Logging class. It creates a custom post type called Logs, which (at least in my case) is only visible inside the admin, for example at /wp-admin/edit.php?s&post_status=all&post_type=wp_log.

To make it easier to tell which plugin is creating the logs, I've added a few hooks. Both plugins use them identically. What they do is:

  1. Add a sortable Type column to the manage posts screen, and add the type field (which the WP Logging library adds) to that screen.
  2. Add a dropdown to the top of the screen (next to the date filter) and allow the posts to be filtered by which type value they use.

Here's the code I'm using:

// add a sortable Type column to the posts admin
add_filter( 'manage_edit-wp_log_columns', array( $this, 'type_column' ), 10, 1 );
add_filter( 'manage_edit-wp_log_sortable_columns', array( $this, 'sortable_columns' ), 10, 1 );
add_action( 'manage_wp_log_posts_custom_column', array( $this, 'type_column_content' ), 10, 2 );

// filter the log posts admin by log type
add_filter( 'parse_query', array( $this, 'posts_filter' ), 10, 1 );
add_action( 'restrict_manage_posts', array( $this, 'restrict_log_posts' ) );

/**
 * Add a Type column to the posts admin for this post type
 *
 * @param array $columns
 * @return array $columns
 */
public function type_column( $columns ) {
    $columns['type'] = __( 'Type', 'form-processor-mailchimp' );
    return $columns;
}

/**
 * Make the Type column in the posts admin for this post type sortable
 *
 * @param array $columns
 * @return array $columns
 */
public function sortable_columns( $columns ) {
    $columns['type'] = 'type';
    return $columns;
}

/**
 * Add the content for the Type column in the posts admin for this post type
 *
 * @param string $column_name
 * @param int $post_id
 */
public function type_column_content( $column_name, $post_id ) {
    if ( 'type' != $column_name ) {
        return;
    }
    // get wp_log_type
    $terms = wp_get_post_terms(
        $post_id,
        'wp_log_type',
        array(
            'fields' => 'names',
        )
    );
    if ( is_array( $terms ) ) {
        echo esc_attr( $terms[0] );
    }
}

/**
 * Filter log posts by the taxonomy from the dropdown when a value is present
 *
 * @param object $query
 * @return object $query
 */
public function posts_filter( $query ) {
    global $pagenow;
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    if ( is_admin() && 'edit.php' === $pagenow ) {
        if ( isset( $_GET['post_type'] ) && esc_attr( $_GET['post_type'] ) === $type ) {
            if ( isset( $_GET[ $taxonomy ] ) && '' !== $_GET[ $taxonomy ] ) {
                $query->post_type = $type;
                $query->tax_query = array(
                    array(
                        'taxonomy' => $taxonomy,
                        'field'    => 'slug',
                        'terms'    => esc_attr( $_GET[ $taxonomy ] ),
                    ),
                );
            }
        }
    }
}

/**
 * Add a filter form for the log admin so we can filter by wp_log_type taxonomy values
 *
 * @param object $query
 * @return object $query
 */
public function restrict_log_posts() {
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    // only add filter to post type you want
    if ( isset( $_GET['post_type'] ) && esc_attr( $_GET['post_type'] ) === $type ) {
        // get wp_log_type
        $terms = get_terms(
            [
                'taxonomy'   => $taxonomy,
                'hide_empty' => true,
            ]
        );
        ?>
        <select name="wp_log_type">
            <option value=""><?php _e( 'All log types ', 'form-processor-mailchimp' ); ?></option>
            <?php
            $current_log_type = isset( $_GET[ $taxonomy ] ) ? esc_attr( $_GET[ $taxonomy ] ) : '';
            foreach ( $terms as $key => $term ) {
                printf(
                    '<option value="%s"%s>%s</option>',
                    $term->slug,
                    $term->slug == $current_log_type ? ' selected="selected"' : '',
                    $term->name
                );
            }
            ?>
        </select>
        <?php
    }
}

The code is identical between the two plugins in this case. Of course the problem, if it's enabled in both plugins, is that it duplicates the display.

Is there any way I can check to see if these additions have already been made before making them?

I thought this was worth a try (on all the filters, and using doing_action on all the actions):

if ( ! doing_filter( 'manage_edit-wp_log_columns' ) ) {
    add_filter( 'manage_edit-wp_log_columns', array( $this, 'type_column' ), 10, 1 );
}

But it didn't change anything. Is there anything else I can try on this?

Share Improve this question asked Aug 28, 2019 at 14:07 Jonathan StegallJonathan Stegall 2692 silver badges13 bronze badges 4
  • Have you considered not loading the functionality if it's already defined, then having a taxonomy that identifies the source of the log file? Loading 2 implementations of the same thing should have caused PHP fatal errors, to rename the functions so they still load just gives you new problems – Tom J Nowell Commented Aug 28, 2019 at 15:48
  • @TomJNowell I'm not sure I understand the question. The logging library, in this case, is used to log different things by the two plugins. So the library itself is only loaded once, but then both plugins create a different type of log (it's a taxonomy), so it is helpful (at least for me) to be able to filter it. Currently only one of the plugins is used by more users than just me, but that's not necessarily always going to be the case. – Jonathan Stegall Commented Aug 28, 2019 at 17:30
  • when you go to load/define the logging library, check if it's already loaded, e.g. something like if ( class_exists( 'logging library' ) ) { return; }. If it's already loaded, then don't load it again. The same goes for your code that adds the filter for the roles. You don't need to load both copies – Tom J Nowell Commented Aug 28, 2019 at 21:34
  • @TomJNowell this would work, but the way the logging library works is that the calling plugin has to extend it. So the hooks I'm calling aren't part of the logging library. If I run a check to see if that class is loaded, the additions and settings are never run. It could be a less than ideal library in that case, but that's how it works. – Jonathan Stegall Commented Aug 29, 2019 at 16:01
Add a comment  | 

2 Answers 2

Reset to default 1

In your code, you add the filter like this:

add_action( 'restrict_manage_posts', array( $this, 'restrict_log_posts' ) );

Which adds a function that starts like this:

public function restrict_log_posts() {
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    // only add filter to post type you want
    if ( isset( $_GET['post_type'] ) && esc_attr( $_GET['post_type'] ) === $type ) {
        // get wp_log_type
        $terms = get_terms(

First, this action passes the post type so it can be simplified by adjusting the add_action and using the first parameter it passes:

public function restrict_log_posts( $post_type ) {
    $type     = 'wp_log';
    $taxonomy = 'wp_log_type';
    // only add filter to post type you want
    if ( 'wp_log' === $post_type ) {
        // get wp_log_type
        $terms = get_terms(

Then we could use did_action to only run it the first time:

public function restrict_log_posts( $post_type ) {
    if ( did_action( 'restrict_manage_posts' ) ) {
        return;
    }
....

But this might not have the desired effect, and it's crude. Instead, this action doesn't belong in this class at all, and should be a standalone action. Then we can define the function only if it isn't already defined:


if ( !function_exists('restrict_logs_by_type') ) {
    add_action( 'restrict_manage_posts', 'restrict_logs_by_type', 10, 1 );
    /**
     * Add a filter form for the log admin so we can filter by wp_log_type taxonomy values
     *
     */
    function restrict_logs_by_type( $post_type ) {
        $type     = 'wp_log';
        $taxonomy = 'wp_log_type';
        // only add filter to post type you want
        if ( $type !== $post_type ) {
            return;
        }
        // get wp_log_type
        $terms = get_terms([
                'taxonomy'   => $taxonomy,
                'hide_empty' => true,
        ]);
        if ( is_wp_error( $terms ) || empty( $terms ) ) {
            // no terms, or the taxonomy doesn't exist, skip
            return;
        }

        ?>
        <select name="wp_log_type">
            <option value=""><?php esc_html_e( 'All log types ', 'form-processor-mailchimp' ); ?></option>
            <?php
            $current_log_type = isset( $_GET[ $taxonomy ] ) ? esc_attr( $_GET[ $taxonomy ] ) : '';
            foreach ( $terms as $key => $term ) {
                printf(
                    '<option value="%s"%s>%s</option>',
                    esc_attr( $term->slug ),
                    selected( $term->slug, $current_log_type, false ),
                    esc_html( $term->name )
                );
            }
            ?>
        </select>
        <?php
    }
}

Note that I made a number of significant improvements:

  • I fixed a major security hole by adding escaping to your option tags
  • I replaced the ugly ternary operator with the selected function provided by WP
  • I replaced the if check with a !==, making it a guard. Now the entire function can be unindented by 1, and is more readable. Additionally both the Cyclomatic and NPath complexity have been reduced
  • The function is now standalone, there was never any need for it to be part of an object or class
  • I renamed the function to describe what it does
  • If no terms are found, or the taxonomy doesn't exist, this version skips displaying the dropdown entirely. No sense having a dropdown if the only item is "All". Without this, the log screen would crash if there were no log types or entries
  • The entire snippet is wrapped in a function_exists call, if the function is loaded by the first plugin, the second plugin won't bother defining and adding it

Now that I've been looking at it, I thought of a possible solution.

// add a filter to check for other plugins that might be filtering the log screen
$are_logs_filtered = apply_filters( 'wp_logging_manage_logs_filtered', false );
add_filter( 'wp_logging_manage_logs_filtered', '__return_true' );

if ( false === $are_logs_filtered ) {
    // add a sortable Type column to the posts admin
    add_filter( 'manage_edit-wp_log_columns', array( $this, 'type_column' ), 10, 1 );
    add_filter( 'manage_edit-wp_log_sortable_columns', array( $this, 'sortable_columns' ), 10, 1 );
    add_action( 'manage_wp_log_posts_custom_column', array( $this, 'type_column_content' ), 10, 2 );

    // filter the log posts admin by log type
    add_filter( 'parse_query', array( $this, 'posts_filter' ), 10, 1 );
    add_action( 'restrict_manage_posts', array( $this, 'restrict_log_posts' ) );
}

Adding a wp_logging_manage_logs_filtered to both plugins, and setting its value to true on both plugins, results in the filtering field, the sorted column, and the content in the column, all only happening once.

I am inclined to think that's the best thing to do, but of course I could be missing something.

本文标签: wp adminHow to avoid manage posts screen duplicates when two plugins use the same library