Advanced Content & Style Fixes
This guide documents specialized interventions for correcting complex content rendering issues in WordPress that cannot be handled via the standard editor.
The "Smart Quotes" CSS Problem
Issue
When injecting <style> blocks directly into WordPress post content via PHP (e.g., using wp_update_post), WordPress runs the wptexturize filter on the entire content body. This filter automatically converts standard single quotes (') and double quotes (") into "smart" typographic quotes (e.g., ‘, ’).
Impact: This breaks CSS syntax immediately.
- Original:
font-family: 'Open Sans', sans-serif; - Result:
font-family: ‘Open Sans’, sans-serif;-> Invalid CSS
Solution: The "No-Quote" Strategy
To safely inject style blocks that survive WordPress processing, strip all quotes from url() paths and font-family names.
Clean CSS Pattern:
/* Bad (Broken by WP) */
@import url('https://fonts.googleapis.com/css2?family=Roboto');
.my-class { font-family: 'Roboto', sans-serif; }
/* Good (Safe) */
@import url(https://fonts.googleapis.com/css2?family=Roboto);
.my-class { font-family: Roboto, sans-serif; }
Automation Script Example
Use PHP's Heredoc syntax to define clean CSS blocks without risking quote conversion in your source code.
$css = <<<CSS
<!-- wp:html -->
<style>
@import url(https://fonts.googleapis.com/css2?family=UnifrakturMaguntia&display=swap);
.fraktur-text {
font-family: UnifrakturMaguntia, cursive !important;
}
</style>
<!-- /wp:html -->
CSS;
$content .= "\n" . $css;
wp_update_post(array('ID' => $post_id, 'post_content' => $content));
Structural Content Fixes via Regex
For batch-fixing missing headers or malformed HTML structures across multiple posts, use PHP scripts with strict regex patterns.
Pattern: Inserting Missing Headers
If a section header is missing but the preceding content is consistent, use look-behind assertions or specific anchor text to inject the header.
// Find specific anchor text and append header
$pattern = '/(End of previous section text\.\s*)(?!(<h3))/s';
$replacement = '$1<h3 class="fraktur-header">New Section Header</h3>';
$content = preg_replace($pattern, $replacement, $content);
Pattern: Cleaning Duplicate Styles
When iterating on style fixes, old <style> blocks often accumulate. Use a loop to aggressively purge them before adding the new verified block.
// Remove ALL <style> blocks aggressively
while (preg_match('/<style.*?>.*?<\/style>/si', $content)) {
$content = preg_replace('/<style.*?>.*?<\/style>/si', '', $content);
}
Saving <style> Tags: Bypassing WordPress KSES
Issue
WordPress's KSES security filter silently strips <style> tags from post content when saving via wp_update_post(). This means even if you include a valid <style> block, it is removed from the database, leaving raw CSS rendered as visible plain text on the front end.
Symptom
After running a PHP update script, the page renders raw CSS like:
@import url('...');
.k-row{display:flex;...}
instead of applying the styles visually.
Solution: Disable KSES Before Saving
Before calling wp_update_post(), remove the KSES content filters:
remove_filter('content_save_pre', 'wp_filter_post_kses');
remove_filter('content_filtered_save_pre', 'wp_filter_post_kses');
$post->post_content = $new_content_with_style_tags;
wp_update_post($post);
Full Pattern: Wrap Existing Loose CSS in <style> Tags
If a post already has raw CSS stored (without the wrapper), locate and wrap it:
function wrap_css_in_style_tag($post_id, $start_marker, $end_marker) {
remove_filter('content_save_pre', 'wp_filter_post_kses');
remove_filter('content_filtered_save_pre', 'wp_filter_post_kses');
$post = get_post($post_id);
$content = $post->post_content;
if (strpos($content, $start_marker) !== false && strpos($content, '<style>') === false) {
$start_pos = strpos($content, $start_marker);
$end_pos = strpos($content, $end_marker) + strlen($end_marker);
// Insert <style> before start
$content = substr_replace($content, '<style>', $start_pos, 0);
$end_pos += 7; // Compensate for inserted chars
// Insert </style> after end
$content = substr_replace($content, '</style>', $end_pos, 0);
$post->post_content = $content;
wp_update_post($post);
}
}
Caution: Only disable KSES temporarily and within a controlled CLI/admin script context. Never do this in front-facing AJAX handlers.
CSS Delivery: Definitive Method — $wpdb->update()
Why remove_filter + wp_update_post Can Still Fail
Even with KSES disabled, WordPress's wp_update_post() can still alter content through other hooks (revision creation, wptexturize). For guaranteed preservation of <style> blocks at position 0 of post_content, use direct database writes.
The _wpb_shortcodes_custom_css Trap
WPBakery Page Builder reserves the meta key _wpb_shortcodes_custom_css for its own CSS output. This only fires for posts built with the WPBakery visual editor (posts containing [vc_row] shortcodes). For hand-crafted HTML posts it is silently ignored — the CSS is saved to the database but never output in <head>.
Do not use update_post_meta($id, '_wpb_shortcodes_custom_css', $css) for non-WPBakery posts, even if js_composer is active on the site.
Definitive Pattern: Direct DB Write
global $wpdb;
// Remove any existing <style> block at start (idempotent)
$content = preg_replace('/^\s*<style[^>]*>.*?<\/style>\s*/si', '', $content);
// Prepend fresh <style> block
$new_content = '<style>' . "\n" . $css . '</style>' . "\n\n" . ltrim($content);
$wpdb->update(
$wpdb->posts,
[
'post_content' => $new_content,
'post_modified' => current_time('mysql'),
'post_modified_gmt' => current_time('mysql', true),
],
['ID' => $post_id],
['%s', '%s', '%s'],
['%d']
);
clean_post_cache($post_id);
wp_cache_flush();
$wpdb->update() bypasses WordPress's entire save pipeline — no KSES, no wptexturize, no revision hooks. The <style> block is preserved exactly. Always call clean_post_cache() to prevent the object cache from serving stale content.
Verification — confirm with curl after every deploy:
curl -sL 'https://vania-novikau.me/{post-slug}/' | grep -c 'your-css-class'
# Expected: > 0
Real-World Example: Kant Parallel Texts — Full CSS Journey (2026-02-16 → 2026-02-18)
Posts affected: 6692, 6693, 6694, 6696, 6697
Stage 1 (2026-02-16): Raw CSS visible as text
apply_kant_css.php injected <style> blocks via wp_update_post(). WordPress stripped the <style> tags, leaving raw CSS rendered as visible page text.
Fix: fix_kant_css_tags.php — re-wrapped with KSES disabled (remove_filter).
Stage 2 (2026-02-18): CSS stripped again, moved to wpb meta
A verification run found the <style> blocks had been stripped again. CSS was moved to _wpb_shortcodes_custom_css post meta (per kant.agent.md spec). Appeared correct in DB.
Symptom: curl | grep css-class returned 0 — CSS not in rendered HTML.
Root cause: WPBakery only outputs _wpb_shortcodes_custom_css for its own built pages. These posts use plain HTML. The meta was silently ignored.
Stage 3 (2026-02-18): Direct DB write — final fix
fix_css_direct_db.php uses $wpdb->update() to write <style> block directly into post_content, bypassing the entire WordPress save pipeline. Verified working.
| Post | ID | URL slug | CSS hits (curl) |
|---|---|---|---|
| L0 EN | 6692 | l0-english-norman-kemp-smith-1929 | 3 ✓ |
| L0 DE-A | 6693 | l0-german-a-edition-1781 | 20 ✓ |
| L0 DE-B | 6694 | l0-german-b-edition-1787 | 20 ✓ |
| L1 | 6696 | visual-guide-critique-of-pure-reason-transcendental-aesthetic | 3 ✓ |
| L2 | 6697 | kant-critique-of-pure-reason-parallel-texts | 53 ✓ |
Note: L0/L1 posts are pix-article type nested under the L1 post's slug:
/pix-article/visual-guide-critique-of-pure-reason-transcendental-aesthetic/{slug}/
CSS classes in all posts: .sync-section-anchor, .kant-sidebar-image, .kant-source-block, .kant-layer-pill/.kant-layer-nav
Additional CSS in L0 posts: .kant-arg-block (arg/premise/conclusion/def/terms variants), .l0-first-para (drop-cap), .nks-page-ref, .kant-aa-ref, .de-lnum, .kant-page-nav
Font Sizing & Typography
When adjusting typography for readability on historical texts:
- Use
emunits: Allow scaling based on user settings. - Target Specific Containers:
- Main Text:
.fraktur-text(e.g.,1.2emfor heavy scripts). - Metadata/Notes:
.k-num-col,.k-text-col(e.g.,0.9em).
- Main Text:
- Font Imports: Always use
display=swapto prevent invisible text during load.
Broken HTML in ACF Repeater Fields Causing Layout Cascades
Issue
When using Advanced Custom Fields (ACF) repeater blocks (e.g., content_with_repated_images), if a specific text block contains an unclosed HTML tag (like <div class="kant-content">), the standard WordPress output will fail to close the parent outer wrapper correctly.
For complex layouts like nested tab views (posts-ai-chatboxes), the parser forces sibling elements—such as other tabs (#tab-l1, #tab-l0)—to suddenly render as children of the broken repeater block's container.
Impact:
When a user clicks a different tab, JavaScript might apply a display: none or toggle the .active class on the parent tab wrapper. In the unclosed-HTML scenario, hiding the current tab inadvertently hides all nested tabs, making it appear as if the layout is entirely blank or "invisible."
Diagnosis
If tabs or sections go blank rather than switching properly:
- Fetch the raw HTML source and find the misnested elements.
- If
tab-l1andtab-l0appear insidetab-l2, it signifies an unclosed div. - Validate database directly to pinpoint the exact row:
// Diagnostic logic example:
$rows = get_field('field_name', $post_id);
foreach ($rows as $index => $row) {
$open_divs = substr_count($row['content'], '<div');
$close_divs = substr_count($row['content'], '</div');
if ($open_divs !== $close_divs) {
echo "Mismatch in row " . ($index + 1);
}
}
Solution
Append the missing closing tag(s) explicitly via an ACF update script and immediately flush the instance cache.
$rows = get_field('field_name', $post_id);
$rows[0]['content'] .= "\n</div>\n"; // Fix specific row
update_field('field_xyz123', wp_slash($rows), $post_id);
// Run: wp cache flush
Layer 1 (L1) Mathematical Formula Conventions (KaTeX/LaTeX)
Issue
When embedding mathematical formulas into L1 and L2 layers, using standard unstructured equations (e.g., $$\text{Appearance} = \text{Matter} \times \text{Form}$$) creates visually misaligned layouts compared to reference architecture designs. Additionally, injecting LaTeX equations directly via standard WP update_post filters strips crucial double-backslashes needed for KaTeX parsing.
Aesthetic Structure
To maintain formatting parity across Kantian parallel texts, mathematical expressions must utilize verbose bounding boxes, grouping structures (\underbrace), and clear spacing (\quad):
Incorrect (Plain):
$$ \text{Appearance} = \text{Matter} \times \text{Form} $$
Correct (Structured & Styled):
<div class="kant-formula-card formula-l1">
<div class="kant-formula-equation formula-long">
$$ \text{Appearance} \quad = \quad \underbrace{\quad \text{Form} \quad}_{\textit{a priori}} \quad \times \quad \underbrace{\quad \text{Matter} \quad}_{\textit{a posteriori}} $$
</div>
</div>
Database Escaping Restrictions
WordPress natively strips backslashes when strings pass through $wpdb. When pushing KaTeX definitions, the internal WordPress DB layers will corrupt the command format.
Solution: You MUST double-escape backslashes in PHP string literals natively interacting with $wpdb or ACF array data.
// Fails: Becomes "$$ \underbrace..." causing formatting corruption after WP slash-stripping
$math = "$$ \\underbrace{\\text{Matter}} $$";
// Succeeds: WordPress strips the first layer, storing the necessary escaping layer in DB.
$math = "$$ \\\\underbrace{\\\\text{Matter}} $$";
wp_update_post([
'ID' => 6696,
'post_content' => wp_slash($math) // Crucial when using WP standard methods
]);
Wrapper Redundancy (Mermaid Diagrams)
When appending Mermaid architecture components to WordPress fields inside L1 content, duplicate nested blocks (<div class="kant-arg-block arg-terms">) frequently occur if markdown to HTML translations are chained improperly.
Constraint: Ensure a single root wrapper per block when performing get_post_content analysis or manipulation:
<!-- Correct output wrapper for Diagrams -->
<div class="kant-arg-block arg-terms diagram-container">
<pre class="mermaid">...</pre>
</div>