Динамическое добавление правила переписывания

Вопрос или проблема

Я пытаюсь динамически добавить правило перезаписи, которое позволяет получить доступ к любой странице с альтернативной структурой постоянной ссылки по моему выбору.

Например, если альтернативная структура постоянной ссылки включает слаг foo/ в первой позиции, я хочу добавить правило перезаписи таким образом, чтобы:

example.com/foo/my-page переписывалось в example.com/my-page

example.com/foo/my-cool/blog/post переписывалось в example.com/my-cool/blog/post

Таким образом, неважно, является ли запрос к странице, посту или любому другому типу пользовательского контента, я хочу, чтобы этот контент был доступен под слагом /foo/, а также с оригинальной структурой URL без foo.

Я написал рабочую функцию, которая делает это следующим образом:

  1. чтение URI
  2. удаление слага из URI
  3. перебор всех существующих правил перезаписи
  4. сопоставление URI с удаленным слагом существующему правилу перезаписи
  5. извлечение соответствующих групп для совпавшего правила перезаписи
  6. составление нового правила перезаписи с URI со слагом в качестве источника и совпавшей конечной точкой правила переписывания с замененными значениями для соответствующих групп в качестве назначения.

Вот код:

// functions.php

add_filter('rewrite_rules_array', function($rules) {

    $slug = 'foo';

    // получить URI запроса
    $uri = $_SERVER['REQUEST_URI'];
    
    // определить, содержит ли URI слаг в первой позиции
    if(preg_match('/^(\/' . $slug . ')/', $uri)){

        // получить базовый URI, удалив слаг
        $base_uri = str_replace("https://wordpress.stackexchange.com/".$slug,'',$uri);

        // перебрать существующие правила перезаписи
        foreach($rules as $src => $dest){

            // найти правило перезаписи, для которого базовый URI бы совпал
            $regex_to_match = "https://wordpress.stackexchange.com/" . str_replace("https://wordpress.stackexchange.com/",'\/',$src) . "https://wordpress.stackexchange.com/";
            preg_match_all($regex_to_match, $base_uri, $matches, PREG_SET_ORDER);

            if(count($matches) > 0){

                // получить конкретные совпадающие группы
                $matches = $matches[0]; 

                // составить допустимый regex из URI со слагом для создания нового источника перезаписи
                $new_src = ($uri[0] == "https://wordpress.stackexchange.com/" ? substr($uri, 1) : $uri) . '?$';
                
                // заменить переменные соответствия их строковыми значениями для создания нового назначения перезаписи
                for($i=1; $i<count($matches)+1; $i++){
                    $replacement = isset($matches[$i]) ? $matches[$i] : '';
                    $dest = str_replace('$matches[' . $i . ']', $replacement, $dest);
                }
                $new_dest = $dest;

                // добавить новое правило перезаписи в массив правил $wp_rewrite
                $rules[$new_src] = $new_dest;

                // добавить правило перезаписи
                add_rewrite_rule($new_src,$new_dest,'top');

                break;
            }
        }
    }

    return $rules;

});

flush_rewrite_rules();

Это работает, но только для URL с несколькими слагами.

example.com/foo/my-page/123 переписывается правильно!

example.com/foo/abc/defg/bar переписывается правильно!

example.com/foo/abc НЕ переписывается правильно. Вместо этого он перенаправляет на example.com/abc

то же для example.com/foo/my-page — оно перенаправляется на example.com/my-page

Проблема с вашим обновленным кодом заключается в том, что вы используете add_rewrite_rule() внутри фильтра, и она не может правильно добавить его в начало списка. Вы можете увидеть это, если выведете массив $rules. Поскольку он добавляется в конец списка $rules, WordPress предварительно вмешивается, чтобы сопоставить с слагом в одиночку, используя некоторую магию в фоновом режиме.

Чтобы обойти этот процесс, вы должны добавить его в начало списка вручную и вернуть это из фильтра.

В частности, вам нужно изменить вызов add_rewrite_rule() на следующий:

$rules = [$new_src => $new_dest] + $rules;

Я включил полный обновленный код ниже.

add_filter('rewrite_rules_array', function($rules) {

    $slug = 'foo';

    // получить URI запроса
    $uri = $_SERVER['REQUEST_URI'];
    
    // определить, содержит ли URI слаг в первой позиции
    if(preg_match('/^(\/' . $slug . ')/', $uri)){

        // получить базовый URI, удалив слаг
        $base_uri = str_replace("https://wordpress.stackexchange.com/".$slug,'',$uri);

        // перебрать существующие правила перезаписи
        foreach($rules as $src => $dest){

            // найти правило перезаписи, для которого базовый URI бы совпал
            $regex_to_match = "https://wordpress.stackexchange.com/" . str_replace("https://wordpress.stackexchange.com/",'\/',$src) . "https://wordpress.stackexchange.com/";
            
            preg_match_all($regex_to_match, $base_uri, $matches, PREG_SET_ORDER);

            if(count($matches) > 0){

                // получить конкретные совпадающие группы
                $matches = $matches[0]; 

                // составить допустимый regex из URI со слагом для создания нового источника перезаписи
                $new_src = ($uri[0] == "https://wordpress.stackexchange.com/" ? substr($uri, 1) : $uri) . '?$';
                
                // заменить переменные соответствия их строковыми значениями для создания нового назначения перезаписи
                for($i=1; $i<count($matches)+1; $i++){
                    $replacement = isset($matches[$i]) ? $matches[$i] : '';
                    $replacement = strpos($replacement, "https://wordpress.stackexchange.com/") === 0 ? substr($replacement, 1) : $replacement;
                    $dest = str_replace('$matches[' . $i . ']', $replacement, $dest);
                }
                $new_dest = $dest;

                // добавить новое правило перезаписи в массив правил $wp_rewrite
                $rules = [$new_src => $new_dest] + $rules;

                break;
            }
        }
    }

    return $rules;

});

Это все еще кажется странным способом сделать это, и я уверен, что есть лучший, но без лучшего понимания вашего случая использования я не могу предложить альтернативное предложение. Самая большая проблема для меня здесь в том, что вы очищаете правила перезаписи при каждом запросе, что может быть затратной операцией.

Оригинальный ответ

Я бы не стал подключаться к generate_rewrite_rules для этого. Вы можете просто использовать обычный API правил перезаписи.

Это протестировано ниже, и оно работает для перенаправления http://example.com/foo/test/123 на https://example.com/test/123. Можно настроить это под свои нужды. Поместите это в functions.php или плагин функциональности.

add_action( 'init',  function() {
    add_rewrite_rule( 'foo/([a-z0-9-/]+)[/]?$', 'index.php?foo_redirect_to=$matches[1]', 'top' );
} );

add_filter( 'query_vars', function( $query_vars ) {
    $query_vars[] = 'foo_redirect_to';
    return $query_vars;
} );

add_action('template_redirect', function() {
    if ( ( $redirect_to = get_query_var( 'foo_redirect_to' ) ) == false ) {
        return;
    }

    wp_redirect("https://example.com/$redirect_to");
});

Ответ или решение

Теория

В области веб-разработки динамическое добавление правил перезаписи URL (rewrite rules) является важной задачей, особенно в системах управления контентом, таких как WordPress. Это позволяет адаптировать структуру URL на сайте без изменения его фактического содержания, что может быть полезным для SEO или для реализации специальных маршрутов. Задача, представленная в вопросе, связана с созданием правил перезаписи, которые позволяют любому контенту на сайте быть доступным через альтернативную структуру постоянных ссылок со slug foo в самом начале URL.

Пример

На практике это означает, что URL вида example.com/foo/my-page должен перенаправляться на example.com/my-page. Такие перезаписи должны поддерживаться для различных типов контента, включая страницы, посты или кастомные посты. Исходный код, предложенный пользователем, решает данную задачу, но с определенными ограничениями. Особенно, он корректно работает для URL с несколькими сегментами, но испытывает проблемы с одноуровневыми URL.

Также важно отметить, что исходное решение добавляет правила в конец существующего списка, что приводит к преобладанию других, ранее добавленных правил. Это ограничение устраняется изменением добавления нового правила в начало списка.

Применение

Рекомендуемое решение состоит в модификации существующего кода таким образом, чтобы новые правила перезаписи добавлялись в начало массива правил, обеспечивая их первоочередное выполнение:

add_filter('rewrite_rules_array', function($rules) {
    $slug = 'foo';
    // Получение URI запроса
    $uri = $_SERVER['REQUEST_URI'];

    if(preg_match('/^(\/' . $slug . ')/', $uri)){
        $base_uri = str_replace("/" . $slug,'',$uri);

        foreach($rules as $src => $dest){
            $regex_to_match = '/^' . str_replace('/', '\/', $src) . '/';

            if(preg_match_all($regex_to_match, $base_uri, $matches, PREG_SET_ORDER)){
                $matches = $matches[0];
                $new_src = $uri . '?$';

                foreach($matches as $key => $value){
                    $dest = str_replace('$matches[' . ($key + 1) . ']', $value, $dest);
                }
                $new_dest = $dest;
                $rules = [$new_src => $new_dest] + $rules;
                break;
            }
        }
    }

    return $rules;
});

Заключение

При динамическом добавлении правил перезаписи следует тщательно учитывать, как эти правила вписываются в общий механизм маршрутизации сайта. В приведенном решении предложенные корректировки позволяют устранить описанные проблемы и повысить эффективность правил перезаписи. Однако, важно помнить, что повторное выполнение операции flush_rewrite_rules() может быть ресурсоемким, и его следует использовать с осторожностью.

Оцените материал
Добавить комментарий

Капча загружается...