При эксплуатации WordPress на одном из сайтов был обнаружен неприятный дефект – некоторые автоматически сгенерированные ярлыки (“slug”) для категорий имели покоцанные последние символы, вследствие чего перейти по ссылкам на такие категории не представлялось возможным. Однако при ручном их указании все сохранялось корректно.
Данная проблема явно не относится к тем, с которыми можно часто столкнуться: она возникает при наличии, к примеру, многоуровневой иерархии категорий с повторяющимися названиями узлов. Предположим следующую гипотетическую структуру категорий:
Причина проблемы состоит в том, что WordPress не допускает существование одинаковых ярлыков, даже если они находятся в разных ветвях иерархии. Поэтому, если при создании данной структуры не заполнять вручную ярлыки (“slugs”), автоматически получим такую структуру ярлыков:
Стандартный алгоритм пытается автозаполнить ярлыки категорий, исходя из их названий. Когда названия повторяются – WordPress пытается приписать к ярлыку название его родительской категории, создав тем самым более уникальное имя.
Почему сгенерированные названия получаются нерабочими? – Дело в том, что в базе данных на длину поля ярлыка выделено 200 символов. В принципе, это довольно большое число, которого должно было хватить с запасом в том числе и на имя родительской категории. Однако, как выяснилось, это верно лишь при использовании латинских символов. В базе данных ярлык хранится уже в URL-encoded форме, что при учете использования UTF-8 приводит к тому, что одна русская буква заменяется на последовательность из 6 символов. В результате, фактически в этом поле умещается название, в котором не более 33 русских букв. И, как выяснилось, никакой проверки этого факта в WordPress не предусмотрено: при записи слишком длинного ярлыка в базу просто пишется та его часть, которая уложилась в 200 символов. А при учете, что на одну русскую букву в кодированной форме приходится 6 символов, получаем, что одна из букв оказывается разорвана пополам. Как итог – битая нерабочая ссылка.
Для исправления этой ситуации можно пропатчить файл wp-includes/taxonomy.php:
--- a/taxonomy.php 2014-02-24 13:14:30.649795666 +0400 +++ b/taxonomy.php 2014-02-25 11:23:51.443231774 +0400 @@ -2436,6 +2436,15 @@ * @param object $term The term object that the $slug will belong too * @return string Will return a true unique slug. */ +function truncate_slug_to_200_chars($slug, $limit = 200) { + while (strlen($slug) >= $limit) + { + $slug = rawurldecode($slug); + $slug = mb_substr($slug,0,-1); + $slug = rawurlencode($slug); + } + return $slug; +} function wp_unique_term_slug($slug, $term) { global $wpdb; @@ -2451,6 +2460,10 @@ if ( is_wp_error($parent_term) || empty($parent_term) ) break; $slug .= '-' . $parent_term->slug; + // [kreon] slugs are url-encoded and limited to 200 chars + // if this limit is exceeded then non-ascii urlencoded data will probably break + if ( strlen( $slug ) >= 200 ) + $slug = truncate_slug_to_200_chars( $slug ); if ( ! term_exists( $slug ) ) return $slug; @@ -2468,6 +2481,10 @@ if ( $wpdb->get_var( $query ) ) { $num = 2; + // [kreon] same thing here. if result exceeds 200 chars, then things may break + // make sure we have free space for 3 additional chars to hold '-num' parameter + if ( strlen( $slug ) >= 197 ) + $slug = truncate_slug_to_200_chars ( $slug, 197 ); do { $alt_slug = $slug . "-$num"; $num++;
Этот патч не позволяет автозаполнению сгенерировать слишком длинный ярлык, сокращая побуквенно длину декодированного ярлыка до тех пор, пока его кодированное представление не станет менее 200 символов.
Патч не исправит уже имеющиеся поломанные ярлыки – их необходимо пересохранить повторно вручную.