web-dev-qa-db-ja.com

既存のmeta_queryと競合することなく、pre_get_postsでmeta_queryをフィルター処理する

私は現在のユーザロールをチェックし、ユーザが許容できるユーザロールを持っている場合にのみその投稿を返すように設計されているすべての投稿にmeta_keyを持っています。私が遭遇している問題は、私のpre_get_postsで宣言された私のmeta_queryが私がサイトを通してセットアップした既存のメタクエリと衝突しているということです。私は何か間違ったことをしているのでしょうか、これは不可能なのでしょうか、それとも私の質問に対する別の解決策がありますか?

add_action('pre_get_posts', function ($query) {

    // dont filter admin posts
    if(is_admin()) return $query;

    // get required globals
    global $current_user;

    // create our not in array
    $not_in = array (
        'public'    => array ('media', 'sponsor', 'super'),
        'media'     => array ('sponsor', 'super'),
        'sponsor'   => array ('super'),
        'super'     => array (),
    );

    // determine the users role
    $role = $current_user->roles;
    $role = array_key_exists(0, $role) ? $role[0] : 'public';
    if(current_user_can('edit_posts')) $role = 'super';

    // determine the users post visibility
    switch($role) {
        case 'media':
        case 'sponsor':
            $posts_visibility = $role;
            break;

        case 'super':
        case 'editor':
        case 'administrator':
            $posts_visibility = 'super';
            break;

        default:
            $posts_visibility = 'public';
            break;
    }

    // get existing meta query
    $meta_query = $query->get('meta_query');

    // add our new meta_query data
    $meta_query[] = array (
        'relation'  => 'OR',
        array (
            'key'       => 'restricted_visibility',
            'value'     => $not_in[$posts_visibility],
            'compare'   => 'NOT IN',
        ),
        array (
            'key'       => 'restricted_visibility',
            'compare'   => 'NOT EXISTS',
        ),
    );


    // update to our new meta query
    $query->set('meta_query', $meta_query);

    // return our query
    return $query;

});

今私はメタクエリはそれ自身で機能することを知っていますが、それは他のメタクエリと衝突するか、または既存のクエリと新しいクエリを一緒に結合しようとするとまったく機能しないようです。

それで、AND/OR条件を混在させることはできないと言われたので、posts_whereフィルタを使うように変更しました。私がメタクエリを設定するまでは、コードは上記とまったく同じです。それから私はこれを使う -

add_filter('posts_where', function ($where) {

    // dont filter admin posts
        if(is_admin()) return $where;

    // get required globals
    global $current_user;

    // create our not in array
    $not_in = array (
        'public'    => array ('media', 'sponsor', 'super'),
        'media'     => array ('sponsor', 'super'),
        'sponsor'   => array ('super'),
        'super'     => array (),
    );

    // determine the users role
    $role = $current_user->roles;
    $role = array_key_exists(0, $role) ? $role[0] : 'public';
    if(current_user_can('edit_posts')) $role = 'super';

    // determine the users post visibility
    switch($role) {
        case 'media':
        case 'sponsor':
            $posts_visibility = $role;
            break;

        case 'super':
        case 'editor':
        case 'administrator':
            $posts_visibility = 'super';
            break;

        default:
            $posts_visibility = 'public';
            break;
    }

    if(count($not_in[$posts_visibility]) > 0) {
        // join postmeta so we can query it
        $this->filter('posts_join', function ($join) {

            // get our global
            global $wpdb;

            // create our join and return it
            $join .= sprintf(' LEFT JOIN %1$s ON %2$s.ID = %1$s.post_id ', $wpdb->postmeta, $wpdb->posts);

            return $join;
        });

        // build our query
        $where .= sprintf(
            ' AND (( NOT EXISTS (SELECT * FROM %1$s WHERE (%1$s.post_id = %2$s.ID) AND %1$s.meta_key = "restricted_visibility")' .
            ' OR ( %1$s.meta_key = "restricted_visibility" AND %1$s.meta_value NOT IN (%3$s) ))) GROUP BY %2$s.ID',
            $wpdb->postmeta,
            $wpdb->posts,
            '\'' . implode('\', \'', $not_in[$posts_visibility]) . '\''
        );
    }

    // return our query
    return $where;
});

上記のスニペットで私が抱えている問題は、それが多くの予期しない問題を引き起こすように思われるということです。クエリに時間がかかるなど、その他多くの予期しない問題があります。照会できるように、postmetaテーブルに参加する必要がありました。

クリス、あなたの考えを楽しみにしています。

1
Chris

OK、

多くの試行錯誤を繰り返し、周りを尋ねることで、ついに実用的な解決策を得ることができました。将来同様の問題を解決しようとしている人のためにここに投稿することにしました。

フィルター1:posts_joinは、postmetaテーブルをリンクするためにすべての照会に対して左結合を作成します。注意すべき重要な点として、postmetaテーブルはcpm1というエイリアスで作成されています。これはすべてを順調に進めるためのバインド全体であるためです。そうでなければ、私たちのカスタムクエリは他のメタクエリと相反するでしょう。

// create our posts join filter
add_filter('posts_join', function ($join) {

    // get our global
    global $wpdb;

    // create our join complete with alias and return it
    return $join . sprintf(" LEFT JOIN %1\$s AS cpm1 ON (%2\$s.ID = cpm1.post_id AND cpm1.meta_key = 'META_KEY_HERE') ", $wpdb->postmeta, $wpdb->posts);

});

これでジョインができました。投稿を照会するときに、クエリのカスタム部分を追加できます。クエリはpostmetaテーブルを直接参照するのではなく、左結合テーブル(cpm1)を参照することに注意してください。

// create our posts_where filter to make use of our join and exclude certain posts
add_filter('posts_where', function ($where) {

    // dont filter admin posts
    if(is_admin()) return $where;

    // get required globals
    global
        $current_user;

    // create our not in array
    $not_in = array (
        'public'    => array ('media', 'sponsor', 'super'),
        'media'     => array ('sponsor', 'super'),
        'sponsor'   => array ('super'),
        'super'     => array (),
    );

    // determine the users role
    $role = $current_user->roles;
    $role = array_key_exists(0, $role) ? $role[0] : 'public';
    if(current_user_can('edit_posts')) $role = 'super';


    // determine the users post visibility
    switch($role) {
        case 'media':
        case 'sponsor':
            $posts_visibility = $role;
            break;

        case 'super':
        case 'editor':
        case 'administrator':
            $posts_visibility = 'super';
            break;

        default:
            $posts_visibility = 'public';
            break;
    }

    // only apply our custom query if we have legitimate conditions to be met
    if(count($not_in[$posts_visibility]) > 0) {

        // build our query
        $where .= sprintf(
            " AND (( cpm1.meta_key = 'META_KEY_HERE' AND cpm1.meta_value NOT IN (%1\$s)) OR cpm1.meta_id IS NULL ) ",
            "'" . implode("', '", $not_in[$posts_visibility]) . "'"
        );
    }

    // return our query
    return $where;

});
0
Chris