Tworzenie przyjaznego dla użytkownika filtra URL

This tutorial shows how to create a user-friendly URL filter for any WordPress post type. Follow our detailed steps to enhance filtering with structured and clean URLs.

Tworzenie przyjaznego dla użytkownika filtra URL

Tworzymy filtr oparty na taksonomiach i polach meta dla dowolnego typu wpisu. Ten kod i podejście są uniwersalne i można je zastosować do wszystkich typów wpisów oraz taksonomii — wystarczy podstawić własne dane.

Przykład tworzenia adresu URL:
/{post_type_name}/filter/{taxonomy_name}-in-{term_slug}-or-{second_term_slug}-and-{meta_name}-in-{meta_value}

Separators:

  • -and- — separator pierwszego poziomu
  • -in- — separator drugiego poziomu
  • -or- — separator trzeciego poziomu

Najpierw musimy nadpisać reguły permalinków. W tym celu używamy add_action(‘init’, ‘rewrite_rules’), gdzie bots to mój dedykowany typ wpisu.

/**
 * Rewrite rules filter.
 *
 * @return void
 */
function rewrite_rules(): void {
add_rewrite_rule(
'bots/filter/([-_a-zA-Z0-9+%.:]+)/page/(\d+)/?$',
'index.php?post_type=bots&filter=$matches[1]&paged=$matches[2]',
'top'
); // rule for pagination support

add_rewrite_rule(
    'bots/filter/([-_a-zA-Z0-9+%.:]+)/?$',
    'index.php?post_type=bots&filter=$matches[1]',
    'top'
); // main rule for overriding

}
add_action('init', 'rewrite_rules');

Teraz musimy dodać naszą zmienną do query_vars. Jest to konieczne, aby przechowywać nasze dane w zmiennej globalnej i umożliwić dostęp do nich.

/**
 * Add Query vars filter
 *
 * @param array $vars Vars Query.
 *
 * @return array
 */
function add_filter_vars( array $vars ): array {
    $vars[] = 'filter';

    return $vars;
}

add_filter( 'query_vars', 'add_filter_vars'  );

Następnie tworzymy formularz filtra. Poniżej znajduje się kod, którego użyłem w jednym z moich projektów.

<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="post" class="filter-form">
    <!-- Your HTML form code here -->
    <?php wp_nonce_field(TBL_FILTER_ACTION_NAME, 'filter_nonce'); ?>
    <input type="hidden" name="action" value="<?php echo esc_attr(TBL_FILTER_ACTION_NAME); ?>">
    <input type="hidden" id="sort_by" name="tbl_filter[sort_by]" value="">
    <input type="hidden" id="price_sort" name="tbl_filter[price_sort]" value="">
    <input class="button" type="submit" value="<?php esc_html_e('Filtered', 'telegram-bots-listing'); ?>">
</form>

Do przetwarzania danych z formularza używam admin-post, który pozwala przetwarzać dane formularza i generować na ich podstawie adres URL, działając na podobnej zasadzie jak standardowy handler żądań AJAX.

/**
 * Filter bots handler.
 *
 * @return void
 */
public function filter_bot_handler(): void {
    $filter_nonce = !empty($_POST['filter_nonce']) ? filter_var(wp_unslash($_POST['filter_nonce']), FILTER_SANITIZE_SPECIAL_CHARS) : null; // retrieve and clean data with the nonce code
    if (!wp_verify_nonce($filter_nonce, Main::TBL_FILTER_ACTION_NAME)) {
        wp_safe_redirect(filter_input(INPUT_POST, '_wp_http_referer', FILTER_SANITIZE_STRING));
        die();
    } // check the nonce, and if it's not valid, redirect the user back

    $filter_args = [
        'category'   => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FORCE_ARRAY],
        'language'   => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FORCE_ARRAY],
        'price'      => FILTER_SANITIZE_SPECIAL_CHARS,
        'sort_by'    => FILTER_SANITIZE_SPECIAL_CHARS,
        'price_sort' => FILTER_SANITIZE_SPECIAL_CHARS,
    ]; // array for cleaning and validating form data

    // phpcs:disable
    $filter_request = !empty($_POST['tbl_filter']) ? filter_var_array(wp_unslash($_POST['tbl_filter']), $filter_args) : [];
    $rating = !empty($_POST['review_rating']) ? filter_var(wp_unslash($_POST['review_rating']), FILTER_SANITIZE_NUMBER_INT) : null;
    // phpcs:enable

    if (!empty($rating)) {
        $filter_request['rating'] = $rating;
    }

    $param_url = generate_filter_url($filter_request); // pass the array to a function that will generate our URL
    wp_safe_redirect($param_url . '/', 301); // redirect the user to our URL
    die();
}

add_action('admin_post_nopriv_' . YOUR_ACTION_NAME, 'filter_bot_handler');
add_action('admin_post_' . YOUR_ACTION_NAME, 'filter_bot_handler');

Tablica zamienników znaków specjalnych:

const TBL_CODE_MATCH = [
    '"', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '{', '}', '|', ':', '"', '<', '>', '?', '[', ']', ';', "'", '', '.', '/', '', '~', '`', '='
];

Funkcja generująca adres URL filtra:

/**
 * Generate filter URL.
 *
 * @param array $params Params.
 * @return string
 */
function generate_filter_url(array $params): string {
    $params_array = [];
    $redirect = get_bloginfo('url') . '/bots/filter/';
    foreach ($params as $key => $item) {
        $params_array[$key] = [];
        if (is_array($item)) {
            foreach ($item as $value) {
                $params_array[$key][] = rawurlencode(str_replace(self::TBL_CODE_MATCH, '-', $value)); // clean special characters in the data and replace them with '-'
            }
        } else {
            $params_array[$key][] = rawurlencode(str_replace(self::TBL_CODE_MATCH, '-', $item)); // clean special characters in the data and replace them with '-'
        }
    }
    $i = 0;
    foreach ($params_array as $key => $param) {
        if (0 === $i && !empty($param[0])) {
            $redirect .= $key . '-in-' . implode('-or-', (array) $param);
            $i++;
            continue;
        }
        foreach ($param as $index => $item) {
            if (!empty($item)) {
                $addon = (0 === $index) ? '-and-' . $key . '-in-' : '-or-';
                $redirect .= $addon . $item;
            }
        }
    }
    return $redirect;
}

Ostatnim krokiem jest sparsowanie tego adresu URL i zmodyfikowanie głównego zapytania za pomocą filtra z wykorzystaniem następującej akcji: add_action(‘pre_get_posts’, ‘add_query_filter’).

/**
 * Filter query.
 *
 * @param WP_Query $query WP Query.
 * @return WP_Query
 */
function add_query_filter(WP_Query $query) {
    if (
        !is_admin() && $query->is_main_query() &&
        is_post_type_archive('bots') &&
        !empty($query->query_vars['filter'])
    ) {
        // check for main_query and if data is present in query_vars['filter']
        $args = [];
        $filter_params = parse_url_query($query->query_vars['filter']); // parse our URL
        if (empty($filter_params)) {
            return $query;
        }
        foreach ($filter_params as $key => $value) {
            switch ($key) {
                case 'category':
                    $args['tax_query'][] = [
                        'taxonomy' => 'bot-category',
                        'field'    => 'slug',
                        'terms'    => $value,
                        'operator' => 'IN'
                    ];
                    break;
                case 'language':
                    $args['tax_query']['relation'] = 'AND';
                    $args['tax_query'][] = [
                        'taxonomy' => 'bot-languages',
                        'field'    => 'slug',
                        'terms'    => $value,
                        'operator' => 'IN'
                    ];
                    break;
                // additional cases...
            }
        }
        foreach ($args as $key => $arg) {
            if (!empty($arg)) {
                $query->set($key, $arg);
            }
        }
        return $query;
    }
    return $query;
}

add_action('pre_get_posts', 'add_query_filter');

Funkcja do parsowania adresu URL:

/**
 * Parse string URL
 *
 * @param string $url_query URL Query.
 * @return array|false
 */
function parse_url_query(string $url_query) {
    $param_string = $url_query;
    $query_arg = [];
    if (!empty($param_string)) {
        $params = explode('-and-', $param_string);
        foreach ($params as $param) {
            $items = explode('-in-', urldecode($param));
            $param_name = $items[0];
            unset($items[0]);
            if (preg_match('-or-', $items[1])) {
                $query_arg[$param_name] = explode('-or-', $items[1]);
            } else {
                $query_arg[$param_name] = $items;
            }
        }
        return array_filter($query_arg);
    } else {
        return false;
    }
}

Po dodaniu tego kodu filtr powinien działać w szablonie archive_{post_type}, a przefiltrowane dane będą dostępne w standardowej pętli wyświetlającej wyniki.

Powiązane wpisy

Wskazówki i spostrzeżenia z mojej drogi zawodowej

Jak korzystać z autoloadingu Composera w WordPressie

Jak korzystać z autoloadingu Composera w WordPressie

  • 02.10.2026
  • 14
Przeniesienie strony ze środowiska lokalnego na serwer za pomocą dostępu SSH

Przeniesienie strony ze środowiska lokalnego na serwer za pomocą dostępu SSH

  • 02.10.2026
  • 13
Dlaczego WordPress 5.5.3 z PHP 8 zwraca błąd 404 na każdej stronie witryny?

Dlaczego WordPress 5.5.3 z PHP 8 zwraca błąd 404 na każdej stronie witryny?

  • 02.10.2026
  • 13
Wszystkie wpisy

Gotowy, aby wynieść swój projekt
na wyższy poziom?

Wprowadźmy Twoją wizję w życie dzięki profesjonalnemu tworzeniu stron i dedykowanym rozwiązaniom.
Skontaktuj się z nami już teraz i rozpocznijmy pracę nad przekształceniem Twoich pomysłów w rzeczywistość!