add files inc

This commit is contained in:
Profile Profile
2026-03-09 20:51:08 +03:00
parent 83ca6c638a
commit ed4d79b706
23 changed files with 3728 additions and 4 deletions

807
inc/admin/lock-terms.php Normal file
View File

@@ -0,0 +1,807 @@
<?php
add_action('admin_enqueue_scripts', function($hook) {
if ($hook !== 'post.php' && $hook !== 'post-new.php') return;
wp_enqueue_script('select2');
wp_enqueue_style('select2');
});
add_action('admin_footer', function () {
global $post;
// Массив с настройками для каждой таксономии
$taxonomies_settings = [
'banned' => [
'background' => '#9e5a63', // фон для banned
'color' => '#ffffff', // белый цвет для рамки
'placeholder_color' => '#ffffff', // цвет placeholder для banned
'allow_new_tags' => false // запрещаем новые теги для banned
],
'keys' => [
'background' => '#e8f2ff', // синий фон для keys
'color' => '#2271b1', // синий цвет для рамки
'placeholder_color' => '#2271b1', // цвет placeholder для keys
'allow_new_tags' => true // разрешаем новые теги для keys
],
'post_tag' => [
'background' => '#31708e', // светлый фон для post_tag
'color' => '#ffffff', // серый цвет для рамки
'placeholder_color' => '#ffffff', // цвет placeholder для post_tag
'allow_new_tags' => false // запрещаем новые теги для post_tag
]
];
?>
<script>
jQuery(function ($) {
const taxonomiesSettings = <?php echo json_encode($taxonomies_settings); ?>;
Object.keys(taxonomiesSettings).forEach(tax => {
const settings = taxonomiesSettings[tax];
// Для post_tag используется другой ID контейнера
let box;
if (tax === 'post_tag') {
box = $('#tagsdiv-post_tag');
// Если не нашли по стандартному ID, пробуем найти по классу
if (!box.length) {
box = $('.tagsdiv-post_tag');
}
} else {
box = $('#tagsdiv-' + tax);
}
if (!box.length) {
console.log('Container not found for taxonomy:', tax);
return;
}
const originalInput = box.find('.taghint').parent().find('input');
const tagList = box.find('.tagchecklist');
const tagCloudLink = box.find('.tagcloud-link');
// Получаем классы с оригинального input
const originalClasses = originalInput.attr('class') || '';
// Скрываем стандартные элементы
originalInput.hide();
box.find('.ajaxtag').hide();
tagList.hide();
// Создаем новый элемент select с классами оригинала
const select = $('<select multiple="multiple" class="' + originalClasses + '"></select>');
select.insertBefore(tagList);
// Создаем скрытое поле для хранения ID терминов
const hiddenIdsInput = $('<input type="hidden" name="' + tax + '_ids" />');
hiddenIdsInput.insertAfter(select);
// Переменная для отслеживания последнего введенного текста
let lastInputText = '';
// Функция для обновления скрытых inputs
const updateHiddenInputs = function() {
let selectedNames = [];
let selectedIds = [];
// Получаем все выбранные значения из Select2
const selectedData = select.select2('data');
selectedData.forEach(item => {
const value = item.id;
const text = item.text;
selectedNames.push(text);
// Для новых тегов используем название вместо ID
if (value.toString().startsWith('NEW_')) {
// Сохраняем название тега (без префикса NEW_)
selectedIds.push(text);
} else {
// Существующий ID термина
selectedIds.push(value);
}
});
// Для совместимости со стандартным input WordPress
originalInput.val(selectedNames.join(','));
// Для нашего обработчика сохранения
hiddenIdsInput.val(selectedIds.join(','));
console.log('Updated hidden inputs for', tax, ':', {
names: selectedNames.join(','),
ids: selectedIds.join(',')
});
};
// Функция для принудительного добавления тега
const forceAddTag = function(tagText) {
if (!tagText || tagText.trim() === '') return false;
const trimmedText = tagText.trim();
const newTagId = 'NEW_' + trimmedText;
// Проверяем, нет ли уже такого тега в выбранных
const existingOptions = select.find('option');
let alreadyExists = false;
existingOptions.each(function() {
if ($(this).val() === newTagId || $(this).text().toLowerCase() === trimmedText.toLowerCase()) {
alreadyExists = true;
return false;
}
});
if (!alreadyExists && settings.allow_new_tags) {
const option = new Option(trimmedText, newTagId, true, true);
select.append(option);
select.trigger('change');
console.log('Force added tag:', trimmedText);
return true;
}
return false;
};
// Настройки Select2 в зависимости от таксономии
const select2Options = {
ajax: {
url: ajaxurl,
dataType: 'json',
delay: 250,
data: (params) => ({
action: 'get_existing_terms',
taxonomy: tax,
search: params.term || ''
}),
processResults: function (data) {
return {
results: data
};
}
},
placeholder: '',
allowClear: true,
width: '100%',
minimumInputLength: 1, // Минимальная длина для поиска
matcher: function(params, data) {
// Если нет поискового запроса, показываем все результаты
if ($.trim(params.term) === '') {
return data;
}
// Проверяем, содержит ли текст данные поискового запроса
if (data.text.toLowerCase().indexOf(params.term.toLowerCase()) > -1) {
return data;
}
// Если не нашли совпадение, скрываем результат
return null;
}
};
// Для banned и post_tag - только выбор из существующих
// Для keys - разрешаем создание новых тегов
if (settings.allow_new_tags) {
select2Options.tags = true;
select2Options.createTag = function (params) {
var term = $.trim(params.term);
if (term === '') {
return null;
}
// Проверяем, есть ли уже такой термин в результатах поиска
// Если есть - не создаем новый тег
return {
id: 'NEW_' + term,
text: term,
newTag: true
};
};
// Добавляем валидацию для новых тегов
select2Options.insertTag = function (data, tag) {
// Проверяем, не существует ли уже такого тега в данных
const existingTag = data.find(item =>
item.text.toLowerCase() === tag.text.toLowerCase()
);
if (existingTag) {
// Если тег уже существует, не добавляем новый
return data;
}
// Добавляем новый тег в начало списка
data.unshift(tag);
return data;
};
} else {
select2Options.tags = false;
// Для таксономий без создания новых тегов запрещаем любые пользовательские вводы
select2Options.createTag = function (params) {
return null;
};
}
// Инициализация select2
select.select2(select2Options);
// Обработчики для гарантированного добавления тегов
if (settings.allow_new_tags) {
// Обработчик ввода - сохраняем последний введенный текст
select.on('input', '.select2-search__field', function(e) {
lastInputText = $(this).val();
});
// Обработчик keydown на самом select элементе
select.on('keydown', function(e) {
if (e.which === 13) { // Enter
const select2Container = select.next('.select2-container');
const searchInput = select2Container.find('.select2-search__field');
const currentText = searchInput.val().trim();
if (currentText !== '') {
e.preventDefault();
e.stopImmediatePropagation();
// Даем немного времени Select2 на обработку
setTimeout(() => {
if (forceAddTag(currentText)) {
searchInput.val('');
updateHiddenInputs();
}
}, 50);
}
}
});
// Дополнительный обработчик на document для перехвата всех Enter
$(document).on('keydown', function(e) {
if (e.which === 13) {
const activeElement = document.activeElement;
if ($(activeElement).hasClass('select2-search__field')) {
const select2Container = $(activeElement).closest('.select2-container');
const relatedSelect = $('select').filter(function() {
return $(this).next('.select2-container').is(select2Container);
});
if (relatedSelect.length && settings.allow_new_tags) {
const currentText = $(activeElement).val().trim();
if (currentText !== '') {
e.preventDefault();
e.stopImmediatePropagation();
setTimeout(() => {
if (forceAddTag(currentText)) {
$(activeElement).val('');
updateHiddenInputs();
}
}, 50);
}
}
}
}
});
// Обработчик для случаев, когда Select2 не успевает обработать тег
select.on('select2:closing', function(e) {
const select2Container = select.next('.select2-container');
const searchInput = select2Container.find('.select2-search__field');
const currentText = searchInput.val().trim();
if (currentText !== '') {
setTimeout(() => {
if (forceAddTag(currentText)) {
searchInput.val('');
updateHiddenInputs();
}
}, 100);
}
});
}
// Обработчики событий Select2
select.on('change', function () {
console.log('Select2 changed for', tax, 'selected values:', $(this).val());
updateHiddenInputs();
});
select.on('select2:select', function (e) {
console.log('Select2 select for', tax, 'selected item:', e.params.data);
updateHiddenInputs();
});
select.on('select2:unselect', function (e) {
console.log('Select2 unselect for', tax, 'unselected item:', e.params.data);
updateHiddenInputs();
});
// Загружаем выбранные термы в select2
$.ajax({
url: ajaxurl,
type: 'POST',
dataType: 'json',
data: {
action: 'get_post_terms',
post_id: <?php echo (int)$post->ID; ?>,
taxonomy: tax
}
}).done(function (terms) {
terms.forEach(t => {
let option = new Option(t.text, t.id, true, true);
select.append(option);
});
select.trigger('change');
// Обновляем скрытые inputs
updateHiddenInputs();
});
// Переменная для хранения текущего облака
let currentTagCloud = null;
// Обработчик клика по ссылке "Выбрать из часто используемых меток"
tagCloudLink.off('click').on('click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
// Если облако уже открыто - закрываем его
if (currentTagCloud && currentTagCloud.is(':visible')) {
currentTagCloud.remove();
currentTagCloud = null;
return;
}
// Удаляем предыдущее облако если есть
if (currentTagCloud) {
currentTagCloud.remove();
}
// Загружаем популярные теги через AJAX
$.ajax({
url: ajaxurl,
type: 'GET',
dataType: 'json',
data: {
action: 'get_popular_terms',
taxonomy: tax
}
}).done(function(terms) {
// Создаем свое облако тегов в стандартном стиле WordPress
if (terms.length > 0) {
currentTagCloud = $('<div class="the-tagcloud" style="margin-top: 10px;"></div>');
// Находим минимальное и максимальное количество использования
let minCount = Math.min(...terms.map(term => term.count));
let maxCount = Math.max(...terms.map(term => term.count));
terms.forEach(term => {
const tagLink = $('<a href="#" class="tag-cloud-link"></a>');
tagLink.text(term.name);
tagLink.attr('data-id', term.id);
// Рассчитываем размер шрифта на основе популярности
const fontSize = calculateFontSize(term.count, minCount, maxCount);
tagLink.css('font-size', fontSize + 'px');
currentTagCloud.append(tagLink);
currentTagCloud.append(' ');
});
box.append(currentTagCloud);
// Обработчик клика по тегам в нашем облаке
currentTagCloud.on('click', 'a', function(e) {
e.preventDefault();
const tagName = $(this).text();
const tagId = $(this).data('id');
// Добавляем тег в Select2
if (!select.find('option[value="' + tagId + '"]').length) {
let option = new Option(tagName, tagId, true, true);
select.append(option);
select.trigger('change');
updateHiddenInputs();
}
// Закрываем облако после выбора
currentTagCloud.remove();
currentTagCloud = null;
});
}
});
});
// Функция для расчета размера шрифта на основе популярности
function calculateFontSize(count, minCount, maxCount) {
// Минимальный и максимальный размер шрифта в px
const minSize = 11;
const maxSize = 18;
// Если все теги имеют одинаковую популярность
if (minCount === maxCount) {
return (minSize + maxSize) / 2;
}
// Рассчитываем размер пропорционально популярности
const scale = (count - minCount) / (maxCount - minCount);
return minSize + (scale * (maxSize - minSize));
}
// Закрываем облако при клике вне его
$(document).on('click', function(e) {
if (currentTagCloud && !$(e.target).closest('.the-tagcloud').length && !$(e.target).closest('.tagcloud-link').length) {
currentTagCloud.remove();
currentTagCloud = null;
}
});
});
$('.tagcloud-link').filter(function() {
return $(this).text().trim() === 'Выбрать из часто используемых меток';
}).text('Выбрать из часто используемых');
});
</script>
<!-- Стили с фоном select, цветными рамками и placeholder -->
<style>
.select2-container {
margin-bottom: 8px;
width: 100% !important;
}
.select2-selection {
min-height: 30px;
}
/* Стили для banned */
#tagsdiv-banned .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
}
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
color: #ffffff !important;
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-banned .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
/* Placeholder для banned */
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['banned']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для banned */
#tagsdiv-banned .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['banned']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-banned .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['banned']['background']; ?> !important;
color: #ffffff !important;
border-color: <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
/* Стили для keys */
#tagsdiv-keys .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
color: #2271b1 !important;
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
/* Placeholder для keys */
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['keys']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для keys */
#tagsdiv-keys .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['keys']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['keys']['background']; ?> !important;
color: #2271b1 !important;
border-color: <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
/* Стили для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
} !impor
#tagsdiv-post_tag .select2-container--default.select2-container--focus .select2-selection--multiple,
.tagsdiv-post_tag .select2-container--default.select2-container--focus .select2-selection--multiple {
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
/* Placeholder для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__placeholder,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
color: <?php echo $taxonomies_settings['post_tag']['placeholder_color']; ?> !important;
opacity: 0.8;
}
/* Курсор для post_tag */
#tagsdiv-post_tag .select2-container--default .select2-search--inline .select2-search__field,
.tagsdiv-post_tag .select2-container--default .select2-search--inline .select2-search__field {
color: <?php echo $taxonomies_settings['post_tag']['placeholder_color']; ?> !important;
caret-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice,
.tagsdiv-post_tag .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: <?php echo $taxonomies_settings['post_tag']['background']; ?> !important;
color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
border-color: <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
/* Общие стили для кнопок удаления */
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: inherit !important;
opacity: 0.7;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
opacity: 1;
color: inherit !important;
}
/* Стили для текста в поле ввода (для всех таксономий) */
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent !important;
margin-top: 0 !important;
padding: 0 !important;
}
/* Стили для облака тегов в стандартном стиле WordPress */
.the-tagcloud {
background: #fff;
border: 1px solid #ccd0d4;
padding: 10px;
border-radius: 4px;
line-height: 2;
}
.the-tagcloud a {
color: #2271b1 !important;
text-decoration: none;
line-height: 1.4;
display: inline-block;
margin: 2px 5px 2px 0;
}
.the-tagcloud a:hover {
color: #135e96 !important;
text-decoration: underline;
}
/* Стили для обводки в выпадающем списке */
#tagsdiv-banned .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['banned']['color']; ?> !important;
outline-offset: 0px !important;
}
#tagsdiv-keys .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['keys']['color']; ?> !important;
outline-offset: 0px !important;
}
#tagsdiv-post_tag .select2-container--default .select2-search__field:focus,
.tagsdiv-post_tag .select2-container--default .select2-search__field:focus {
outline: 1px solid <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
outline-offset: 0px !important;
}
/* Дополнительно для браузеров которые используют box-shadow для фокуса */
#tagsdiv-banned .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['banned']['color']; ?> !important;
}
#tagsdiv-keys .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['keys']['color']; ?> !important;
}
#tagsdiv-post_tag .select2-container--default .select2-search__field:focus,
.tagsdiv-post_tag .select2-container--default .select2-search__field:focus {
box-shadow: 0 0 0 1px <?php echo $taxonomies_settings['post_tag']['color']; ?> !important;
}
.tagsdiv .howto {
display: none !important;
}
</style>
<?php
});
// AJAX обработчик для получения существующих терминов с сортировкой по релевантности
add_action('wp_ajax_get_existing_terms', function () {
$taxonomy = sanitize_key($_GET['taxonomy']);
$search = sanitize_text_field($_GET['search'] ?? '');
$args = [
'taxonomy' => $taxonomy,
'hide_empty' => false,
'number' => 100,
];
// Если есть поисковый запрос, добавляем поиск и сортировку по релевантности
if (!empty($search)) {
$args['search'] = $search;
$args['orderby'] = 'name';
$args['order'] = 'ASC';
}
$terms = get_terms($args);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'text' => $term->name
];
}
// Сортируем по релевантности, если есть поисковый запрос
if (!empty($search)) {
usort($result, function($a, $b) use ($search) {
$a_text = $a['text'];
$b_text = $b['text'];
$search_lower = strtolower($search);
// Приоритет: точное совпадение
if (strtolower($a_text) === $search_lower && strtolower($b_text) !== $search_lower) {
return -1;
}
if (strtolower($b_text) === $search_lower && strtolower($a_text) !== $search_lower) {
return 1;
}
// Приоритет: начинается с поискового запроса
$a_starts = stripos($a_text, $search) === 0;
$b_starts = stripos($b_text, $search) === 0;
if ($a_starts && !$b_starts) {
return -1;
}
if ($b_starts && !$a_starts) {
return 1;
}
// Приоритет: содержит поисковый запрос
$a_contains = stripos($a_text, $search) !== false;
$b_contains = stripos($b_text, $search) !== false;
if ($a_contains && !$b_contains) {
return -1;
}
if ($b_contains && !$a_contains) {
return 1;
}
// Если одинаковый приоритет - сортируем по алфавиту
return strcasecmp($a_text, $b_text);
});
}
wp_send_json($result);
});
// AJAX обработчик для получения терминов поста
add_action('wp_ajax_get_post_terms', function () {
$taxonomy = sanitize_key($_POST['taxonomy']);
$post_id = (int)$_POST['post_id'];
$terms = wp_get_post_terms($post_id, $taxonomy);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'text' => $term->name
];
}
wp_send_json($result);
});
// AJAX обработчик для получения популярных тегов
add_action('wp_ajax_get_popular_terms', function () {
$taxonomy = sanitize_key($_GET['taxonomy']);
// Получаем популярные теги (с наибольшим количеством постов)
$terms = get_terms([
'taxonomy' => $taxonomy,
'orderby' => 'count',
'order' => 'DESC',
'number' => 20,
'hide_empty' => false,
]);
$result = [];
foreach ($terms as $term) {
$result[] = [
'id' => $term->term_id,
'name' => $term->name,
'count' => $term->count // Добавляем количество использований
];
}
wp_send_json($result);
});
// Обработчик сохранения с исправленной логикой для новых тегов
add_action('save_post', function($post_id) {
// Проверяем права пользователя
if (!current_user_can('edit_post', $post_id)) return;
// Убираем автосохранение
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
// Отладочная информация
error_log('Save post hook called for post: ' . $post_id);
error_log('POST data for keys: ' . ($_POST['keys_ids'] ?? 'not set'));
error_log('POST data for banned: ' . ($_POST['banned_ids'] ?? 'not set'));
error_log('POST data for post_tag: ' . ($_POST['post_tag_ids'] ?? 'not set'));
$taxonomies = ['banned', 'keys', 'post_tag'];
foreach ($taxonomies as $taxonomy) {
if (isset($_POST[$taxonomy . '_ids'])) {
$values = explode(',', $_POST[$taxonomy . '_ids']);
$term_ids = [];
foreach ($values as $value) {
$value = trim($value);
if (empty($value)) continue;
// Если значение НЕ число, это новый тег (только для keys)
if (!is_numeric($value) && $taxonomy === 'keys') {
// Это название нового тега
$term_name = $value;
if (!empty($term_name)) {
// Создаем новый терм
$new_term = wp_insert_term($term_name, $taxonomy);
if (!is_wp_error($new_term)) {
$term_ids[] = $new_term['term_id'];
error_log('Created new term: ' . $term_name . ' with ID: ' . $new_term['term_id']);
} else if ($new_term->get_error_code() === 'term_exists') {
// Если терм уже существует, получаем его ID
$existing_term = get_term_by('name', $term_name, $taxonomy);
if ($existing_term) {
$term_ids[] = $existing_term->term_id;
error_log('Term already exists: ' . $term_name . ' with ID: ' . $existing_term->term_id);
}
} else {
error_log('Error creating term: ' . $term_name . ' - ' . $new_term->get_error_message());
}
}
} else {
// Существующий ID термина
$term_ids[] = intval($value);
}
}
$term_ids = array_filter($term_ids);
if (!empty($term_ids)) {
wp_set_object_terms($post_id, $term_ids, $taxonomy, false);
error_log('Set terms for ' . $taxonomy . ': ' . implode(', ', $term_ids));
} else {
// Если нет терминов, очищаем
wp_set_object_terms($post_id, [], $taxonomy, false);
error_log('Cleared terms for ' . $taxonomy);
}
}
}
});