Skip to main content

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., &#8216;, &#8217;).

Impact: This breaks CSS syntax immediately.

  • Original: font-family: 'Open Sans', sans-serif;
  • Result: font-family: &#8216;Open Sans&#8217;, 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>.

danger

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.

PostIDURL slugCSS hits (curl)
L0 EN6692l0-english-norman-kemp-smith-19293 ✓
L0 DE-A6693l0-german-a-edition-178120 ✓
L0 DE-B6694l0-german-b-edition-178720 ✓
L16696visual-guide-critique-of-pure-reason-transcendental-aesthetic3 ✓
L26697kant-critique-of-pure-reason-parallel-texts53 ✓

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:

  1. Use em units: Allow scaling based on user settings.
  2. Target Specific Containers:
    • Main Text: .fraktur-text (e.g., 1.2em for heavy scripts).
    • Metadata/Notes: .k-num-col, .k-text-col (e.g., 0.9em).
  3. Font Imports: Always use display=swap to 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:

  1. Fetch the raw HTML source and find the misnested elements.
  2. If tab-l1 and tab-l0 appear inside tab-l2, it signifies an unclosed div.
  3. 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>