| 1 |
<?php |
| 2 |
/** |
| 3 |
* Import Texture Gate. This disables WordPress `wptexturize` on imported pages. |
| 4 |
* |
| 5 |
* Why this exists |
| 6 |
* ---------------- |
| 7 |
* WordPress runs `wptexturize` on every `the_content` render. The filter |
| 8 |
* silently rewrites characters in the post body: |
| 9 |
* |
| 10 |
* `'` → `’` (curly right single quotation mark, U+2019) |
| 11 |
* `"foo"` → `“foo”` (curly double quotes) |
| 12 |
* `--` → `—` (em-dash) |
| 13 |
* `...` → `…` (horizontal ellipsis) |
| 14 |
* |
| 15 |
* For a blog post this is a usability win. For a designed page imported from |
| 16 |
* raw HTML it is text corruption. The source authored "I'd" with ASCII U+0027 |
| 17 |
* and expects that exact glyph. The rendered page ships "I’d" with U+2019. That |
| 18 |
* glyph renders about 10px wider in most fonts. It shifts text wrapping. This |
| 19 |
* was verified on adam-preiser. The hero h1 wrapped to 5 lines on the import |
| 20 |
* and 4 lines on the source. The CSS, font, width and line-height matched. Only |
| 21 |
* the apostrophe character differed. This added 73px of vertical drift. Across |
| 22 |
* every paragraph with punctuation, the whole hero section grew 186px taller |
| 23 |
* than the source. |
| 24 |
* |
| 25 |
* Scope |
| 26 |
* ----- |
| 27 |
* This is active only when the rendered post has the |
| 28 |
* `spectra_blocks_pro_gs_user_css` post meta. The FSE import pipeline sets that |
| 29 |
* meta. All non-imported posts still get the standard wptexturize treatment. |
| 30 |
* |
| 31 |
* `run_wptexturize` is a core WordPress filter. It short-circuits |
| 32 |
* `wptexturize()` entirely. Returning `false` makes the function pass text |
| 33 |
* through unchanged. The filter is checked once per `wptexturize()` call, not |
| 34 |
* per character. So this adds one extra `get_post_meta` lookup per |
| 35 |
* imported-page render. The result is cached statically after the first call. |
| 36 |
* This avoids repeating it across the many internal wptexturize calls on a |
| 37 |
* single page. |
| 38 |
* |
| 39 |
* @package zip-ai |
| 40 |
*/ |
| 41 |
|
| 42 |
namespace ZipAI\MCP\Classes\Imports; |
| 43 |
|
| 44 |
defined( 'ABSPATH' ) || exit; |
| 45 |
|
| 46 |
/** |
| 47 |
* Disables wptexturize on imported posts so authored characters render |
| 48 |
* verbatim (no `'`→`’`, `"`→`“”`, `--`→`—`, `...`→`…` rewrites). |
| 49 |
* |
| 50 |
* @since 0.0.5 |
| 51 |
*/ |
| 52 |
class ImportTextureGate { |
| 53 |
/** |
| 54 |
* Per-page imported-CSS post-meta key (the GBS-local store). Its presence |
| 55 |
* marks a page as FSE-imported. Mirrors spectra-blocks' |
| 56 |
* `GenCssOrphanStripper::META_KEY` (kept local to avoid coupling to a |
| 57 |
* spectra-blocks class that may be inactive). |
| 58 |
* |
| 59 |
* @var string |
| 60 |
*/ |
| 61 |
private const PAGE_CSS_META_KEY = 'spectra_blocks_pro_gs_user_css'; |
| 62 |
|
| 63 |
/** |
| 64 |
* Register the wptexturize gate filter. |
| 65 |
* |
| 66 |
* @return void |
| 67 |
*/ |
| 68 |
public function __construct() { |
| 69 |
add_filter( 'run_wptexturize', array( $this, 'maybe_disable' ) ); |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Filter callback. Returns `false` (disable wptexturize) when the |
| 74 |
* current post is an imported page; otherwise returns the value |
| 75 |
* unchanged so other plugins / WP defaults still drive the decision. |
| 76 |
* |
| 77 |
* @param bool $run Current wptexturize-enabled state. |
| 78 |
* @return bool |
| 79 |
*/ |
| 80 |
public function maybe_disable( bool $run ): bool { |
| 81 |
// Skip admin views — block editor (and any admin renders of |
| 82 |
// post content) should see the WP-standard treatment. The |
| 83 |
// editor doesn't actually run wptexturize on the served block |
| 84 |
// markup, but we keep the admin/REST skips for consistency. |
| 85 |
if ( is_admin() ) { |
| 86 |
return $run; |
| 87 |
} |
| 88 |
|
| 89 |
// Skip REST renders (block editor's `ServerSideRender` preview) |
| 90 |
// for the same consistency reason. |
| 91 |
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { |
| 92 |
return $run; |
| 93 |
} |
| 94 |
|
| 95 |
$post_id = get_the_ID(); |
| 96 |
if ( ! $post_id ) { |
| 97 |
return $run; |
| 98 |
} |
| 99 |
|
| 100 |
// Gate: per-page imported CSS post meta exists IFF the page came |
| 101 |
// through the FSE import pipeline. |
| 102 |
/** |
| 103 |
* Narrowed type for `$is_imported_cache`. |
| 104 |
* |
| 105 |
* @var array<int,bool> $is_imported_cache |
| 106 |
*/ |
| 107 |
static $is_imported_cache = array(); |
| 108 |
if ( ! isset( $is_imported_cache[ $post_id ] ) ) { |
| 109 |
$is_imported_cache[ $post_id ] = '' !== get_post_meta( $post_id, self::PAGE_CSS_META_KEY, true ); |
| 110 |
} |
| 111 |
|
| 112 |
return $is_imported_cache[ $post_id ] ? false : $run; |
| 113 |
} |
| 114 |
} |
| 115 |
|