Перегенерация ЧПУ (Slugs) из заголовков постов
Да, это возможно.
Пример кода, который нужно протестировать и доработать:
// получить все записи
$posts = get_posts( array ( 'numberposts' => -1 ) );
foreach ( $posts as $post )
{
// проверить ярлык и обновить при необходимости
$new_slug = sanitize_title( $post->post_title );
if ( $post->post_name != $new_slug )
{
wp_update_post(
array (
'ID' => $post->ID,
'post_name' => $new_slug
)
);
}
}
Я только что придумал этот код, вероятно, в нём есть некоторые ошибки и неучтённые случаи, но он должен дать вам представление о том, как это можно сделать. Кроме того, это может занять некоторое время, поэтому может быть полезно разбить обновление на более мелкие части.

Хмм... По моему опыту, это не работает.
Аргумент post_name
игнорируется в функции wp_update_post
, по крайней мере в версии ядра 3.9

В настоящее время post_name
игнорируется внутри функции wp_update_post()
, но учитывается при вызове функции wp_insert_post()
при обновлении записи: это означает, что передача нового слага при обновлении фактически изменит его для обновляемой записи.

@fuxia, я пробовал что-то похожее, но это приводит к таймауту. Не могли бы вы предложить подход?

Этот плагин также выполняет задачу: http://www.jerrytravis.com/598/wordpress-plugin-to-generate-post-slugs
Однако, поскольку он работает только с записями, у которых еще нет слага (URL-адреса), если вам нужно перегенерировать слаги, отредактируйте следующую строку в плагине:
if ($post->post_name == "") {
Например, вы можете изменить её на:
if (true) {

Я пробовал метод, предложенный Toscho, который является "инстинктивным", но во многих случаях он не работает (см. код ядра, чтобы понять, что я имею в виду под "многими случаями").
Изучая код, я обнаружил хук фильтра wp_insert_post_data
, вызываемый функцией wp_update_post
непосредственно перед вставкой записи в базу данных.
Используя этот фильтр и изменя значение $data['post_name']
, мне удалось заставить это работать правильно. WordPress классный, но так плохо документирован...
Я отредактировал документацию, чтобы больше людей могли найти это решение, если оно понадобится.

Можешь указать, почему wp_update_post перезаписывает post_name? Единственная причина, которую я вижу для этого — если пользователь, пытающийся изменить post_name, является только участником (или того же уровня), и в этом случае этому пользователю не должно быть разрешено изменять slug. Нашел ли ты какие-либо другие случаи, когда post_name перезаписывается?

Вы можете сделать это напрямую в MySQL, если это необходимо. (Наш сайт WooCommerce содержит сотни тысяч товаров):
update wp_posts set post_name = concat(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(lower(post_title), '"', ''), "'", ''), ",", '-'), " ", '-'), "&", ''), ";", ''), "@", ''), ".", ''), ":", ''), "/", ''), "+", ''), "(", ''), ")", ''), "--", '-'), "---", '-'), "--", '-'), "--", '-'), '-', id) where post_type = 'product';
где post_type = 'product' - это ограничит обновление только товарами WooCommerce; вам следует определить, какие ограничения вы хотите установить для этого запроса.

У нас возникла проблема при миграции и объединении нескольких типов записей. Также у нас было много пустых заголовков. Я написал WP CLI команду, чтобы исправить их все, но вот суть того, что я сделал:
global $wpdb;
$types = [
'page',
// Имена ваших пользовательских типов записей
];
// Правильно форматируем запрос IN
$final = array_map(function($type) {
return "'" . esc_sql($type) . "'";
}, $types);
// Получаем все записи с пустыми заголовками
$query = sprintf("SELECT post_title, ID FROM `%s` WHERE post_type IN (%s) AND post_name = ''",
$wpdb->posts,
implode(",", $final)
);
foreach ($wpdb->get_results($query) as $post) {
$title = sanitize_title_with_dashes( $post->post_title );
$wpdb->update('wp_posts', ['post_name' => $title], ['ID' => $post->ID]);
}
Пришлось делать прямое обновление MySQL, так как wp_update_post не обновляет post_name.
