Thanks Ismael,
You said that you manually cleaned up the code, so when I added it to the live site, I was seeing the same issues as before.
I am now just testing the following, which appears to be working, but did break the nav builder when I first ran it, so filtered this so it would only work on pages and posts.
I will update as to if it works fully.
Thanks
/**
* ============================================================================
* POLYLANG + ENFOLD CUSTOM_CLASS ISSUE FIXES
* ============================================================================
*
* Problem: When Polylang duplicates/translates pages, it incorrectly converts
* custom_class attributes into id="custom_class" or id="custom_class=" in HTML output.
*
* Solution: Multiple filters at different points in the translation/save process
* to catch and fix the issue wherever it occurs.
*/
/**
* Solution 1: Fix content after Polylang processes it (Primary Fix)
* This runs after Polylang has translated the content but before it's saved
*
* CRITICAL FIX: Handles unclosed custom_class=' attributes that break Enfold's parser
* IMPORTANT: Excludes nav_menu_item to prevent menu structure issues
*/
add_filter('pll_filter_translated_post', 'fix_polylang_custom_class_issue', 20, 4);
function fix_polylang_custom_class_issue($tr_post, $source_post, $target_language, $data) {
if (!$tr_post instanceof WP_Post) {
return $tr_post;
}
// Exclude menu items - they don't contain Enfold shortcodes
if ($tr_post->post_type === 'nav_menu_item') {
return $tr_post;
}
$content = $tr_post->post_content;
$original_content = $content;
// Fix 1: Remove malformed id="custom_class=" or id="custom_class" in HTML
$content = preg_replace(
'/\s+id=["\']custom_class(?:=["\'])?/i',
'',
$content
);
// Fix 2: Fix unclosed custom_class=' attributes (THE MAIN ISSUE)
// Pattern: custom_class=' followed by space, closing bracket, or end of shortcode
// Replace with custom_class='' (properly closed empty attribute)
$before_fix2 = $content;
$content = preg_replace(
"/custom_class='(?=\s|'|])/i",
"custom_class=''",
$content
);
$fix2_count = substr_count($before_fix2, "custom_class='") - substr_count($content, "custom_class='");
// Fix 3: Also handle custom_class=" (double quotes, unclosed)
$before_fix3 = $content;
$content = preg_replace(
'/custom_class="(?=\s|"|])/i',
'custom_class=""',
$content
);
$fix3_count = substr_count($before_fix3, 'custom_class="') - substr_count($content, 'custom_class="');
// Fix 4: Remove custom_class='' or custom_class="" if they appear as standalone (no value)
// This cleans up empty attributes that might cause issues
$before_fix4 = $content;
$content = preg_replace(
"/\s+custom_class=['\"]{2}/i",
'',
$content
);
$fix4_count = (substr_count($before_fix4, "custom_class=''") + substr_count($before_fix4, 'custom_class=""')) -
(substr_count($content, "custom_class=''") + substr_count($content, 'custom_class=""'));
// Fix 5: Fix in shortcode attributes - sometimes custom_class becomes id in shortcodes
$before_fix5 = $content;
$content = preg_replace(
'/\[([a-z_]+)([^\]]*?)\s+id=["\']custom_class(?:=["\'])?([^\]]*?)\]/i',
'[$1$2$3]',
$content
);
$fix5_count = substr_count($before_fix5, 'id="custom_class') - substr_count($content, 'id="custom_class');
// Debug logging (only if WP_DEBUG is enabled)
if (defined('WP_DEBUG') && WP_DEBUG && $content !== $original_content) {
error_log(sprintf(
'[POLYLANG FIX] Post ID %d: Fixed %d unclosed custom_class=\' attributes, %d unclosed custom_class=", %d empty custom_class, %d id="custom_class" issues',
$tr_post->ID,
$fix2_count,
$fix3_count,
$fix4_count,
$fix5_count
));
}
$tr_post->post_content = $content;
return $tr_post;
}
/**
* Solution 2: Clean content when post is saved (Backup Fix)
* This catches the issue at save time as a safety net
*
* CRITICAL: Fixes unclosed custom_class=' attributes
* IMPORTANT: Excludes nav_menu_item to prevent menu structure issues
*/
add_filter('wp_insert_post_data', 'clean_custom_class_on_save', 10, 2);
function clean_custom_class_on_save($data, $postarr) {
// Exclude menu items - they don't contain Enfold shortcodes and menu structure is in postmeta
if (!isset($data['post_type']) || $data['post_type'] === 'nav_menu_item') {
return $data;
}
if (!isset($data['post_content'])) {
return $data;
}
$content = $data['post_content'];
// Fix 1: Remove id="custom_class=" or id="custom_class" from content
$content = preg_replace(
'/\s+id=["\']custom_class(?:=["\'])?/i',
'',
$content
);
// Fix 2: Fix unclosed custom_class=' attributes
$content = preg_replace(
"/custom_class='(?=\s|'|])/i",
"custom_class=''",
$content
);
// Fix 3: Fix unclosed custom_class=" (double quotes)
$content = preg_replace(
'/custom_class="(?=\s|"|])/i',
'custom_class=""',
$content
);
// Fix 4: Remove empty custom_class='' or custom_class="" attributes
$content = preg_replace(
"/\s+custom_class=['\"]{2}/i",
'',
$content
);
$data['post_content'] = $content;
return $data;
}
/**
* Solution 3: Clean content right after Polylang duplication
* This runs immediately after Polylang creates a sync post
*
* CRITICAL: Fixes unclosed custom_class=' attributes that break Enfold
* IMPORTANT: Excludes nav_menu_item to prevent menu structure issues
*/
add_action('pll_created_sync_post', 'clean_enfold_content_after_duplication', 5, 4);
function clean_enfold_content_after_duplication($post_id, $tr_id, $lang, $strategy) {
$post = get_post($tr_id);
if (!$post) {
return;
}
// Exclude menu items - they don't contain Enfold shortcodes
if ($post->post_type === 'nav_menu_item') {
return;
}
$content = $post->post_content;
$original = $content;
// Fix 1: Remove malformed id="custom_class" issues
$content = preg_replace('/\s+id=["\']custom_class(?:=["\'])?/i', '', $content);
// Fix 2: Fix unclosed custom_class=' attributes (THE MAIN ISSUE)
// Replace custom_class=' (unclosed) with custom_class='' (properly closed)
$before_fix2 = $content;
$content = preg_replace(
"/custom_class='(?=\s|'|])/i",
"custom_class=''",
$content
);
$fix2_count = substr_count($before_fix2, "custom_class='") - substr_count($content, "custom_class='");
// Fix 3: Fix unclosed custom_class=" (double quotes)
$before_fix3 = $content;
$content = preg_replace(
'/custom_class="(?=\s|"|])/i',
'custom_class=""',
$content
);
$fix3_count = substr_count($before_fix3, 'custom_class="') - substr_count($content, 'custom_class="');
// Fix 4: Remove empty custom_class='' or custom_class="" attributes entirely
// Enfold doesn't need empty custom_class attributes
$before_fix4 = $content;
$content = preg_replace(
"/\s+custom_class=['\"]{2}/i",
'',
$content
);
$fix4_count = (substr_count($before_fix4, "custom_class=''") + substr_count($before_fix4, 'custom_class=""')) -
(substr_count($content, "custom_class=''") + substr_count($content, 'custom_class=""'));
// Fix 5: Ensure custom_class attributes with values are properly formatted
$content = preg_replace_callback(
'/\[([a-z_]+)([^\]]*?)\s+custom_class=([^\s"\']+)([^\]]*?)\]/i',
function($matches) {
// Ensure custom_class has quotes if missing
$value = trim($matches[3]);
if (!preg_match('/^["\'].*["\']$/', $value)) {
$value = '"' . esc_attr($value) . '"';
}
return '[' . $matches[1] . $matches[2] . ' custom_class=' . $value . $matches[4] . ']';
},
$content
);
// Debug logging (only if WP_DEBUG is enabled)
if (defined('WP_DEBUG') && WP_DEBUG && $content !== $original) {
error_log(sprintf(
'[POLYLANG FIX] Post ID %d (from %d): Fixed %d unclosed custom_class=\', %d unclosed custom_class=", %d empty custom_class attributes',
$tr_id,
$post_id,
$fix2_count,
$fix3_count,
$fix4_count
));
}
if ($content !== $original) {
wp_update_post(array(
'ID' => $tr_id,
'post_content' => $content
));
}
}
/**
* Solution 4: Clean content on frontend display (Final Safety Net)
* This ensures broken content doesn't display even if it got into the database
*/
add_filter('the_content', 'remove_custom_class_id_from_html', 999);
function remove_custom_class_id_from_html($content) {
// Remove id="custom_class=" (malformed with extra equals)
$content = preg_replace('/\s+id=["\']custom_class=["\']?/i', ' ', $content);
// Remove id="custom_class" (literal value only)
$content = preg_replace('/\s+id=["\']custom_class["\']/i', ' ', $content);
return $content;
}