ob_start过滤WordPress后台页面时错误移动</script>标签的问题
这个问题我之前也碰到过,根源出在DOMDocument处理HTML的方式上,咱们一步步来拆解:
为什么你的脚本会破坏<script>标签?
你用ob_start捕获了整个页面的HTML输出,然后交给DOMDocument->loadHTML()处理。但DOMDocument是按照标准HTML规则来解析内容的,它不会区分<script type="text/html">里的内容是模板代码——它会把里面的<div>、</div>当作正常的HTML元素来解析,甚至会自动"修复"它认为不规范的结构。
比如你看到的异常:原本应该在script内部的</script>被DOMDocument误判为闭合外层的script标签,导致整个文档结构错乱,最终输出的HTML里script标签被提前闭合,后面的内容自然就乱了。
最优解决方案:用WordPress原生钩子替代DOM解析(推荐)
其实完全不需要捕获整个页面输出再修改,WordPress本身提供了精准的钩子来控制用户资料页的字段显示,既安全又高效:
function remove_extra_profile_fields() { // 移除"视觉编辑器"选项 remove_action( 'show_user_profile', '_user_edit_rich_editing' ); remove_action( 'edit_user_profile', '_user_edit_rich_editing' ); // 移除"语法高亮"选项 remove_action( 'show_user_profile', '_user_edit_syntax_highlighting' ); remove_action( 'edit_user_profile', '_user_edit_syntax_highlighting' ); // 隐藏第一个<h2>标题(个人资料标题) add_action( 'admin_head-profile.php', function() { echo '<style>.user-profile-form h2:first-of-type { display: none; }</style>'; }); } add_action( 'admin_init', 'remove_extra_profile_fields' );
这个方法直接通过remove_action移除WordPress默认输出这两个字段的动作,用CSS隐藏不需要的标题,完全不会触碰页面其他内容,自然也就不会影响媒体框的模板脚本了。
如果一定要用输出缓冲的方式(不推荐,但可以修复)
如果你坚持要用原来的思路,需要先把<script type="text/html">里的内容包裹成CDATA,让DOMDocument把它当作纯文本处理,避免解析内部结构:
function remove_extra_field_profile() { $current_file_url = preg_replace( "#\?.*#" , "" , basename( $_SERVER['REQUEST_URI'] ) ); if( $current_file_url == "profile.php" ) { add_action( 'wp_loaded', function(){ ob_start("profile_callback"); } ); add_action( 'shutdown', function(){ ob_end_flush(); } ); } } add_action( 'init', 'remove_extra_field_profile' ); function profile_callback( $html ) { // 第一步:把text/html类型的script内容包裹成CDATA,避免DOM解析 $html = preg_replace_callback( '#<script type="text/html" id="(.*?)">(.*?)</script>#is', function( $matches ) { return '<script type="text/html" id="' . $matches[1] . '"><![CDATA[' . $matches[2] . ']]></script>'; }, $html ); // 初始化DOMDocument并禁用错误提示 $profile_dom = new DOMDocument; libxml_use_internal_errors(true); $profile_dom->loadHTML( $html ); libxml_clear_errors(); // 移除目标<tr>元素 $all_lines = $profile_dom->getElementsByTagname( 'tr' ); $excludes = array( 'user-syntax-highlighting-wrap', 'user-rich-editing-wrap' ); $deletes = array(); foreach ( $all_lines as $line ) { $tr_class = $line->getAttribute("class"); if( in_array( $tr_class, $excludes ) ) { $deletes[] = $line; } } // 移除第一个<h2> $first_h2 = $profile_dom->getElementsByTagname( 'h2' )->item(0); if ($first_h2) { $deletes[] = $first_h2; } // 执行删除操作 foreach ($deletes as $delete) { $delete->parentNode->removeChild( $delete ); } // 第二步:还原CDATA内容,去掉CDATA标签 $processed_html = $profile_dom->saveHTML(); $processed_html = preg_replace_callback( '#<script type="text/html" id="(.*?)"><!\[CDATA\[(.*?)\]\]></script>#is', function( $matches ) { return '<script type="text/html" id="' . $matches[1] . '">' . $matches[2] . '</script>'; }, $processed_html ); return $processed_html; }
这样处理后,DOMDocument就不会解析script内部的模板代码,也就不会破坏原有的结构了。
内容的提问来源于stack exchange,提问作者Clms

