Skip to content Skip to sidebar

This is the technical support forum for WPML - the multilingual WordPress plugin.

Everyone can read, but only WPML clients can post here. WPML team is replying on the forum 6 days per week, 22 hours per day.

Sun Mon Tue Wed Thu Fri Sat
- 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 9:00 – 13:00 -
- 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 14:00 – 18:00 -

Supporter timezone: America/Argentina/Buenos_Aires (GMT-03:00)

This topic contains 6 replies, has 1 voice.

Last updated by Otto 5 months, 2 weeks ago.

Assisted by: Otto.

Author Posts
October 31, 2025 at 4:15 pm #17536454

josephN-9

Background of the issue:
I'm trying to update my secondary language posts using an API call, but after updating the posts, they lose their existing category and are set to the default category. I called the API from the master dashboard, and it updated successfully but lost the existing category. My child site code is provided in the message. Here is an example URL where the issue can be seen: hidden link. I have tried to save the existing category and set them before the update, but it did not work for me. Here is the code I used:

<code>
<?php
// k-update-post.php
// Secure post updater for Master Dashboard (Full control version)
// WPML-friendly + category-safe version

// ---------------------------
// 1️⃣ Configuration
// ---------------------------
$site_secret_key = '#';
$master_key = '#';

// ---------------------------
// 2️⃣ Security Checks
// ---------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die('Invalid request method');
}

$data = json_decode(file_get_contents('hidden link'), true);
if (!$data) die('Invalid JSON data');

if (empty($data['site_key']) || $data['site_key'] !== $site_secret_key) {
die('Unauthorized: site key');
}

if (empty($data['master_key']) || $data['master_key'] !== $master_key) {
die('Unauthorized: master key');
}

if (empty($data['slug'])) {
die('Missing slug');
}

// ---------------------------
// 3️⃣ Load WordPress
// ---------------------------
require_once __DIR__ . '/wp-load.php';

// ---------------------------
// 4️⃣ Get the post by slug
// ---------------------------
$slug = sanitize_text_field($data['slug']);

$post = get_posts([
'name' => $slug,
'post_type' => 'post',
'post_status' => ['publish'],
'numberposts' => 1,
]);

if (!$post) die('Post not found');

$post_id = $post[0]->ID;

// ---------------------------
// 5️⃣ Backup old taxonomies (to preserve WPML categories)
// ---------------------------
$old_cats = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']);
$old_tags = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'names']);

// ---------------------------
// 6️⃣ Update post fields safely
// ---------------------------
$update = ['ID' => $post_id];

if (!empty($data['new_title'])) {
$update['post_title'] = sanitize_text_field($data['new_title']);
}

if (!empty($data['new_slug'])) {
$update['post_name'] = sanitize_title($data['new_slug']);
}

if (!empty($data['content'])) {
$update['post_content'] = wp_kses_post($data['content']);
}

if (!empty($data['excerpt'])) {
$update['post_excerpt'] = sanitize_text_field($data['excerpt']);
}

wp_update_post($update);

// ---------------------------
// 7️⃣ Restore categories to prevent loss
// ---------------------------
if (!empty($old_cats)) {
wp_set_post_terms($post_id, $old_cats, 'category', false);
}

// ---------------------------
// 8️⃣ Update tags (replace old with new)
// ---------------------------
if (!empty($data['tags']) && is_array($data['tags'])) {
$new_tags = array_map('sanitize_text_field', $data['tags']);
wp_set_post_terms($post_id, $new_tags, 'post_tag', false);
} else {
// if no tags provided, keep existing ones
if (!empty($old_tags)) {
wp_set_post_terms($post_id, $old_tags, 'post_tag', false);
}
}
// ---------------------------
// 9️⃣ Handle Featured Image
// ---------------------------
if (!empty($data['featured_image'])) {
$image_url = esc_url_raw($data['featured_image']);
$caption = !empty($data['caption']) ? sanitize_text_field($data['caption']) : '';

require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';

$image_id = media_sideload_image($image_url, $post_id, $caption, 'id');

if (!is_wp_error($image_id)) {
set_post_thumbnail($post_id, $image_id);

if ($caption) {
wp_update_post([
'ID' => $image_id,
'post_excerpt' => $caption,
]);
}
}
}

// ---------------------------
// ✅ Done
// ---------------------------
echo "✅ Post ID {$post_id} updated successfully (title, slug, content, tags, categories, image)";

</code>

Symptoms:
After updating secondary language posts via API, the posts lose their existing categories and are set to the default category.

Questions:
Why are the existing categories being lost after updating posts via API?
How can I ensure that the existing categories are preserved during the update?

October 31, 2025 at 4:24 pm #17536485

josephN-9

But I works perfectly on default language, this issue occur only while update secondary language

November 3, 2025 at 2:57 pm #17541978

josephN-9

I have also tested with below version code.

Hi,
I have a custom PHP script that updates posts on my WPML site using wp_update_post().

Before the update, my script takes the post’s current categories (with wp_get_post_terms()), then after the update, it sets them again using wp_set_post_terms() — to make sure categories are not lost.

Everything works fine without WPML, but when WPML is active, the categories of the translated post get mixed up:

After the update, the post’s categories switch to the default language version.

In the WordPress dashboard → when I edit the translated post, the Category box is empty (no category selected).

In the database, the post is now linked to the default-language category IDs, not the translated ones.

So WPML seems to ignore language when wp_set_post_terms() runs.

I think WPML needs to remap the category IDs to the correct language when a post is updated programmatically.

Can you please tell me:
👉 How to properly restore a post’s categories in the same language as the post when updating it with PHP?
For example — how can I get the correct translated category IDs for a specific post language?

<?php
/*
Plugin Name: Master Control Dashboard (Full Sender)
Description: Secure post updater — WPML-friendly, preserves translated categories, tags, featured image, etc.
Version: 2.1
*/

// ---------------------------
// 1️⃣ Configuration
// ---------------------------
$site_secret_key = 'RERasean@!34AsDfGhJkLzX000';
$master_key      = 'MASTER_SECRET_';

// ---------------------------
// 2️⃣ Security Checks
// ---------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    die('Invalid request method');
}

$data = json_decode(file_get_contents('<em><u>hidden link</u></em>'), true);
if (!$data) die('Invalid JSON data');

if (empty($data['site_key']) || $data['site_key'] !== $site_secret_key) {
    die('Unauthorized: site key');
}

if (empty($data['master_key']) || $data['master_key'] !== $master_key) {
    die('Unauthorized: master key');
}

if (empty($data['slug'])) {
    die('Missing slug');
}

// ---------------------------
// 3️⃣ Load WordPress
// ---------------------------
require_once __DIR__ . '/wp-load.php';

// ---------------------------
// 4️⃣ Get the post by slug
// ---------------------------
$slug = sanitize_text_field($data['slug']);

$post = get_posts([
    'name'        => $slug,
    'post_type'   => 'post',
    'post_status' => ['publish', 'draft'],
    'numberposts' => 1,
]);

if (!$post) die('Post not found');

$post_id = $post[0]->ID;

// ---------------------------
// 5️⃣ BACKUP OLD TAXONOMIES (WPML-AWARE)
// ---------------------------
$old_tags = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'names']);
$old_cats = [];

$categories = wp_get_post_terms($post_id, 'category');
foreach ($categories as $term) {
    if (defined('ICL_SITEPRESS_VERSION') && function_exists('apply_filters')) {
        $term_lang = apply_filters('wpml_element_language_code', null, [
            'element_id'   => $term->term_id,
            'element_type' => 'tax_category',
        ]);
    } else {
        $term_lang = ''; // no WPML
    }

    $old_cats[] = [
        'id'   => $term->term_id,
        'lang' => $term_lang,
    ];
}

// ---------------------------
// 6️⃣ UPDATE POST CONTENT
// ---------------------------
$update = ['ID' => $post_id];

if (!empty($data['new_title'])) {
    $update['post_title'] = sanitize_text_field($data['new_title']);
}

if (!empty($data['new_slug'])) {
    $update['post_name'] = sanitize_title($data['new_slug']);
}

if (!empty($data['content'])) {
    $update['post_content'] = wp_kses_post($data['content']);
}

if (!empty($data['excerpt'])) {
    $update['post_excerpt'] = sanitize_text_field($data['excerpt']);
}

wp_update_post($update);

// ---------------------------
// 7️⃣ RESTORE CATEGORIES (LANGUAGE-AWARE)
// ---------------------------
if (!empty($old_cats)) {
    $translated_cats = [];

    if (defined('ICL_SITEPRESS_VERSION') && function_exists('apply_filters')) {
        // get post language
        $post_lang = apply_filters('wpml_element_language_code', null, [
            'element_id'   => $post_id,
            'element_type' => 'post_post',
        ]);

        foreach ($old_cats as $cat) {
            // find equivalent category in current language
            $mapped_id = apply_filters('wpml_object_id', $cat['id'], 'category', false, $post_lang);
            if ($mapped_id) {
                $translated_cats[] = (int) $mapped_id;
            }
        }
    } else {
        // no WPML installed
        foreach ($old_cats as $cat) {
            $translated_cats[] = (int) $cat['id'];
        }
    }

    if (!empty($translated_cats)) {
        wp_set_post_terms($post_id, $translated_cats, 'category', false);
    }
}

// ---------------------------
// 8️⃣ UPDATE TAGS (REPLACE OR RESTORE)
// ---------------------------
if (!empty($data['tags']) && is_array($data['tags'])) {
    $new_tags = array_map('sanitize_text_field', $data['tags']);
    wp_set_post_terms($post_id, $new_tags, 'post_tag', false);
} else {
    if (!empty($old_tags)) {
        wp_set_post_terms($post_id, $old_tags, 'post_tag', false);
    }
}

// ---------------------------
// 9️⃣ HANDLE FEATURED IMAGE
// ---------------------------
if (!empty($data['featured_image'])) {
    $image_url = esc_url_raw($data['featured_image']);
    $caption   = !empty($data['caption']) ? sanitize_text_field($data['caption']) : '';

    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';

    $image_id = media_sideload_image($image_url, $post_id, $caption, 'id');

    if (!is_wp_error($image_id)) {
        set_post_thumbnail($post_id, $image_id);

        if ($caption) {
            wp_update_post([
                'ID'           => $image_id,
                'post_excerpt' => $caption,
            ]);
        }
    }
}

// ---------------------------
// ✅ DONE
// ---------------------------
echo "✅ Post ID {$post_id} updated successfully (WPML-safe: title, slug, content, tags, categories, image)";


can you help me why it still not works for me ?

November 3, 2025 at 4:17 pm #17542458

Otto

Hello,

I apologize for the delay in responding. I will take care of this ticket; the reply time will be shorter now.

Please note that we currently are not able to provide support for custom work within this forum.
I will, however, try to point you to the correct direction.

Let me rephrase the issue, so I am sure I get it right:
When you programmatically update translated posts, their categories are cleared and the post gets the default category. This only happens in secondary languages. WordPress assigns the default-language categories (or none), so the Category meta box in the translated post shows no selection.

I suggest checking the following:

❌ Please make a full website backup before proceeding ❌

1.Switch to the post’s language and map term IDs before setting

Determine the post language:

$post_lang = apply_filters( 'wpml_element_language_code', null, ['element_id' => $post_id, 'element_type' => 'post_post'] );

Switch context:

do_action( 'wpml_switch_language', $post_lang );

Build translated category IDs from the existing ones, using return_original_if_missing = true (4th param):

$translated[] = apply_filters( 'wpml_object_id', $cat_id, 'category', true, $post_lang );

Set them once: wp_set_post_terms( $post_id, array_unique( array_filter( $translated ) ), 'category', false );

(Optional but recommended) After restoring terms, switch back to the original language if your script continues with other tasks.

Why this helps: it guarantees WPML remaps each category to the same language as the post and prevents the “empty → default category” fallback.

2. Post-update sync (safety net)

After batches, run WPML → Support → Troubleshooting → Synchronize posts taxonomies to re-link any out-of-sync term relationships, especially if you updated while another plugin/filter interfered.
WPML

3. Ensure taxonomy settings & data are consistent

Categories should be set to Translatable – only show translated items (or “use translation if available”) in WPML → Settings → Taxonomies translation.

Confirm translated category terms actually exist in the target language (WPML → Taxonomy Translation). Missing translations + false in wpml_object_id → empty array → default category.

Best Regards,
Otto

November 6, 2025 at 7:55 pm #17555281

josephN-9

I understand your support system , But I have tried much from my end after that I asked to you why it not works
Thank you, Let me check this one from end

November 6, 2025 at 8:12 pm #17555313

Otto

Hello,

I understand your frustration, but please note that we currently are not able to provide support for custom work within this forum. I tried to point you to the right direction. But reproducing and debugging this scenario is outside our scope.

I am sorry I can't be of better help.

Best Regards,
Otto

November 6, 2025 at 8:13 pm #17555315

Otto

Hello,

I understand your frustration, but please note that we currently are not able to provide support for custom work within this forum. I tried to point you to the right direction. But reproducing and debugging this scenario is outside our scope.

I am sorry I can't be of better help.

Best Regards,
Otto

The topic ‘[Closed] Need help to set existing category after update secondary languge posts by API call’ is closed to new replies.