WP Residence Help WP Residence Help

  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API
Home / WPEstate Translate Plugin / Meta Sync Across Language Variants — Developer Guide

Meta Sync Across Language Variants — Developer Guide

180 views 0

This article covers the meta synchronization pipeline in WPEstate Translate 1.0.9 (text domain wpr-translate): how rules are loaded and normalized, which hooks drive synchronization and which safeguards prevent loops and stale writes. It is the companion to the user-facing Custom Field Rules article and part of the wider multi-language real estate website architecture.

CONTENT

  • Files Involved
  • Behavior Keywords
  • Rule Source of Truth
  • First Load on Fresh Installs
  • Normalization & Grouping
  • Options Used
  • Runtime Hooks
  • Sibling Resolution
  • Full-Sweep Sync: wpr_sync_meta_fields()
  • Meta Cloning on Translation Create
  • Re-Entry Guards
  • Extension Points
  • Admin Page Wiring
  • Legacy Import
  • Related Reading

Files Involved

File Role
includes/meta-sync.php Runtime synchronization on save_post, meta change, delete and untrash hooks.
includes/custom-field-rules.php Rule loader, first-run import, override storage, reload, state reporting.
includes/custom-field-rules-storage.php Normalization, grouping and legacy rule conversion.
includes/custom-fields-sync.php JSON config path, hash tracking, wpr_cf_preferences cache.
includes/admin/post-list-meta.php Meta cloning when a translation is created (wpr_translation_clone_meta_fields()), copy-once markers.
includes/admin/views/settings-custom-fields.php Custom Field Rules admin view.

Behavior Keywords

Four keywords are accepted, normalized in wpr_translate_normalize_meta_field_behavior_value():

Keyword Effect
copy Copied when a translation is created and kept in sync on every save / meta change of any family member.
copy-once Copied only when the translation is created; tracked with the _wpr_cf_copied_once marker.
translate Never propagated; an empty placeholder is seeded on the new translation.
ignore Never touched.

The legacy alias copy_once is rewritten to copy-once. Unknown values are dropped.

Rule Source of Truth

Defaults come from wpr/custom-fields-config.json, located by wpr_translate_locate_theme_wpr_file(): the child theme copy first, then the parent theme. wpr_translate_get_custom_field_rules_file_path() passes the result through the wpr_translate_custom_field_rules_file filter. wpr_translate_read_custom_field_rules_file() accepts a top-level array or an object with a custom_fields key. Each rule can carry:

{ "id": "property_price", "label": "Price", "description": "Property asking price", "action": "copy", "post": "estate_property" }

The key value is accepted as an alias for action.

First Load on Fresh Installs

When no defaults are stored yet, wpr_translate_get_custom_field_rules() reads the JSON file and persists it through wpr_translate_import_custom_field_rules(). Plugin activation calls the same getter, so a new site has the theme rules without pressing Reload from JSON File. The automatic import keeps existing overrides; only the Reload button resets them. If the file is missing nothing is written.

Normalization & Grouping

wpr_translate_normalize_custom_field_rules() sanitizes every entry, builds a unique uid per post type via wpr_translate_build_unique_rule_id(), and defaults the action to copy when missing. wpr_translate_group_custom_field_rules_by_post() groups rules by post type with estate_property first.

Options Used

Option Contents
wpr_cf_rules_defaults Normalized default rules + file hash, path and last-loaded timestamp.
wpr_cf_rule_overrides Admin-changed behaviors keyed by rule uid.
wpr_cf_file_state Status / error payload for the JSON loader shown on the admin page.
wpr_cf_preferences Secondary cache maintained by custom-fields-sync.php (hash + raw rules).

Overrides are merged on top of defaults in wpr_translate_get_custom_field_rules(). Each merged rule carries source (file or override) and the original file_action.

Runtime Hooks

All registered in plugin-bootstrap.php:

Hook Callback Purpose
save_post (20) wpr_translate_handle_save_post_meta_sync() Full sweep of every copy key from the saved post to its family.
added_post_meta / updated_post_meta / deleted_post_meta (20) wpr_translate_handle_post_meta_change_sync() Single-key sync (wpr_sync_single_meta_field()) for code that writes meta without saving the post.
before_delete_post (1) / deleted_post (999) wpr_translate_meta_sync_mark_post_deleting() / _unmark_post_deleting() Meta rows removed by wp_delete_post() are not treated as a sync source, so deleting one translation cannot empty copy fields on the others.
untrash_post (1) / untrashed_post (20) wpr_translate_meta_sync_mark_post_untrashing() / wpr_translate_handle_untrashed_post_meta_sync() The save_post fired by wp_untrash_post() is skipped; after restore, the post is synced from an available sibling so stale values are not pushed to the family.

Both sync callbacks skip autosaves, revisions, auto-drafts and unregistered post types. The single-key handler also skips _wpr_language and _wpr_translations and only acts on copy keys. Both respect the opt-out filter:

apply_filters( 'wpr_translate_should_sync_post_meta', true, $post_id, $post_type, $post );

Sibling Resolution

wpr_translate_resolve_meta_sync_translation_post_ids( $post_id, $post_type ) returns the members of wpr_translate_relationship_get_post_family( $post_id ) (the shared relationship layer, cached per request). On a WP_Error it returns only the current post, so nothing is synced. Do not query siblings through a language-filtered WP_Query; use the family helper.

Full-Sweep Sync: wpr_sync_meta_fields()

$behaviors = wpr_translate_get_meta_field_behaviors();
$copy_keys = keys where behavior === 'copy';
$siblings  = wpr_translate_resolve_meta_sync_translation_post_ids( $post_id, $post_type );
// stop if fewer than 2 members or the source is not available

foreach ( $siblings as $target ) {            // skip source and unavailable targets
    foreach ( $copy_keys as $meta_key ) {
        $source = get_post_meta( $source_post_id, $meta_key, false );
        $current = get_post_meta( $target, $meta_key, false );
        if ( array_map( 'maybe_serialize', $source ) !== array_map( 'maybe_serialize', $current ) ) {
            wpr_translate_meta_sync_enter_internal_write();
            delete_post_meta( $target, $meta_key );
            foreach ( $source as $v ) add_post_meta( $target, $meta_key, wp_slash( $v ) );
            wpr_translate_meta_sync_exit_internal_write();
        }
    }
}

Values from get_post_meta() are already decoded and are not passed through maybe_unserialize() again, so a string that only looks serialized stays a string.

Meta Cloning on Translation Create

wpr_translation_clone_meta_fields( $source_post_id, $new_post_id, $excluded_meta_keys ) applies the rules when a translation is created:

  • Always skipped: _edit_lock, _edit_last, _wp_old_slug, _wp_desired_post_slug, _wp_trash_meta_*, _wpr_cf_copied_once, _wpr_language, _wpr_translations. Copying old-slug meta used to let wp_old_slug_redirect() send a default-language URL to the translation.
  • Builder payloads (_elementor_data, _elementor_edit_mode, _elementor_version, _elementor_template_type, WPBakery keys) are always copied.
  • Elementor caches (_elementor_css, _elementor_page_assets, _elementor_element_cache) are deleted on the new post so Elementor regenerates them.
  • Attachment fields are skipped when Media Synchronization is off.

Re-Entry Guards

  • Per-request static guard keyed by post_type:post_id in wpr_sync_meta_fields() and wpr_sync_single_meta_field().
  • Internal-write depth counter via wpr_translate_meta_sync_enter_internal_write() / _exit_internal_write(); the single-key handler bails when wpr_translate_meta_sync_is_internal_write() is true.
  • Delete / untrash flags in $GLOBALS (see Runtime Hooks).
  • Availability check via wpr_translation_is_translation_post_available() – unavailable translations are skipped.

Extension Points

  • apply_filters( 'wpr_translate_should_sync_post_meta', $should, $post_id, $post_type, $post ) – veto sync per post or post type.
  • apply_filters( 'wpr_translate_meta_field_behaviors', $map ) – last chance to change the behavior map.
  • apply_filters( 'wpr_translate_custom_field_rules_file', $path ) – override the JSON defaults file path.

Admin Page Wiring

The view receives $data['custom_field_rules'] (merged map) and $data['custom_field_state'] (reload status). The Reload button posts wpr_cf_action=reload with nonce wpr_cf_reload_nonce (action wpr_cf_reload); the handler calls wpr_translate_reload_custom_field_rules_from_file(), which deletes wpr_cf_rule_overrides and re-imports the file.

Dropdown changes are saved through wpr_translate_save_custom_field_overrides(); actions are validated against copy, translate, copy-once, ignore.

Legacy Import

wpr_translate_convert_legacy_custom_fields_to_rules() converts the WPResidence wp_estate_custom_fields theme option (indexed arrays, [0] name, [1] label, [2] type, [4] choices) into rules. wpr_translate_import_custom_field_rules() merges them in on every import, so theme-defined custom fields get defaults too.

Related Reading

  • Translation Linking (trid system) – how sibling posts are linked.
  • WP_Query Language Filtering – why sibling lookups must not go through language-filtered queries.
  • Database Schema – the translation table used for legacy content.

For product context, visit the multi-language real estate website landing page.

WPEstate Translate Plugin

Related Articles

  • String Scanner — Developer Guide
  • The String Scanner
  • Gettext Pipeline & MO Files — Developer Guide
  • Gettext & MO Files — Making Translations Appear on the Front End
MLSImportWordPress IDX Plugin for MLS ListingsImport MLS listings into your WordPress website, customize your property pages, and keep your inventory updated.Start free 30-day trial →

Help Categories

  • 51 Getting Started
  • 72 Installation & Setup
  • 243 Installation FAQ
  • 18Agent, Agency & Developers
  • 5Blog Posts & Blog Lists
  • 39Elementor Shortcodes Built-In
  • 56FAQ
  • 15Footer
  • 37Header
  • 2IDX & MLSImport
  • 23Maps & Location Settings
  • 21Multi-Language - Third Party Plugins
  • 7Other Third party Plugins
  • 21Pages
  • 4Payments & Monetization
  • 20Property Lists, Categories & Archive
  • 37Property Pages & Layouts
  • 32Search & Filtering
  • 162Technical how to | Custom Code
  • 8Technical: Actions and filters
  • 7Technical: Child Theme
  • 86Theme Options & Global Settings
  • 6Translations & Languages
  • 16WPBakery Shortcodes
  • 50WPEstate Translate Plugin
  • 51WPResidence / WPEstate CRM
  • 50WPResidence 5.0 Documentation
  • 9WPResidence Elementor Studio

Join Us On

Powered by WP Estate - All Rights Reserved
  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API