WPSlug Fixes ACF Pro Compatibility Issues

Problem Description

Recently, users reported a compatibility issue when the WPSlug plugin is enabled alongside Advanced Custom Fields Pro (ACF Pro):

  1. ACF field keys are corrupted: field_abc_123 is converted to field-abc-123 (underscores replaced with hyphens), preventing ACF custom fields from saving and retrieving data correctly. This is the most critical issue.
  2. ACF field group post_name is interfered with: WPSlug attempts to convert the slugs of ACF’s internal post types into pinyin.
  3. ACF field group post_excerpt is converted to pinyin: Excerpts generated by ACF’s internal sanitize_title() calls are intercepted and processed by WPSlug.

Root Cause Analysis

WPSlug intercepts slug generation via several WordPress hooks: sanitize_title (priority 9), wp_insert_post_data (priority 10), wp_unique_post_slug (priority 10), and transition_post_status (priority 10).

ACF Pro registers five internal post types: acf-field-group, acf-field, acf-post-type, acf-taxonomy, and acf-ui-options-page. When ACF saves field data, it passes through wp_insert_postsanitize_title. WPSlug’s filters on this chain convert underscores in ACF field keys to hyphens.

Although ACF itself implements a fix in wp_unique_post_slug (priority 999) — returning $original_slug — that value has already been corrupted by the earlier sanitize_title filter, rendering the fix ineffective.

Additionally, WPSlug’s isPostTypeEnabled() method defaults to allowing all post types when its enable list is empty (i.e., empty = all), and does not explicitly exclude ACF’s internal post types.

Fix Strategy

1. Exclude ACF Internal Post Types

Add an exclusion list in WPSlug_Core, and insert early guard clauses in the following methods: processPostData, processUniquePostSlug, and handlePostStatusTransition:

private static $excluded_post_types = array(
    'acf-field-group',
    'acf-field',
    'acf-post-type',
    'acf-taxonomy',
    'acf-ui-options-page',
);

2. Call Stack Detection for sanitize_title Protection

The processSanitizeTitle filter cannot directly access post_type. Instead, implement an isAcfInternalRequest() method that inspects the call stack for ACF-related classes or functions, such as:

  • The ACF_Internal_Post_Type class
  • The ACF_Field_Group class
  • Functions like acf_update_field, acf_import_field_group, etc.

3. Defense-in-Depth

Also exclude ACF’s internal post types in WPSlug_Settings::isPostTypeEnabled(), providing redundant protection.

Test Verification

Test Case Before Fix After Fix
ACF field key field_abc_123 field-abc-123 :x: field_abc_123 :white_check_mark:
ACF field group post_name Converted to pinyin Remains group_xxx key :white_check_mark:
ACF field group post_excerpt Converted to pinyin Preserved unchanged :white_check_mark:
acf_get_field() lookup Fails to find field :x: Succeeds :white_check_mark:
Chinese slug for regular posts Correctly converted to pinyin Correctly converted to pinyin :white_check_mark:
Chinese slug for pages Correctly converted to pinyin Correctly converted to pinyin :white_check_mark:
Custom post types Correctly converted to pinyin Correctly converted to pinyin :white_check_mark:
Custom taxonomies Correctly converted to pinyin Correctly converted to pinyin :white_check_mark:
Draft → Published transition Works normally Works normally :white_check_mark:
Saving posts containing ACF fields Field values lost Field values preserved correctly :white_check_mark:

Update Instructions

The fix has been pushed to the feicode repository. Users employing the WenPai updater will automatically receive this update.

If you encounter compatibility issues with other plugins while using WPSlug, please report them in this thread.

你的分析很全面,修复方案遵循了 :electric_plug: 插件开发的最佳实践。针对你的方案,补充几点纵深防御和性能优化的建议。

1. 优化排除列表定义

建议将排除列表定义为类常量,提高可读性和性能。

class WPSlug_Core {
    const EXCLUDED_POST_TYPES = array(
        'acf-field-group',
        'acf-field',
        'acf-post-type',
        'acf-taxonomy',
        'acf-ui-options-page',
    );
}

2. 优化调用栈检测

processSanitizeTitle 中,使用 debug_backtrace() 开销较大。建议结合 $_POST$_REQUEST 数据中的 action 参数进行初步过滤,仅在必要时进行调用栈分析。

private function isAcfInternalRequest() {
    // 快速检查:ACF 的 AJAX 请求通常有特定的 action
    if ( isset( $_POST['action'] ) && strpos( $_POST['action'], 'acf/' ) === 0 ) {
        return true;
    }
    // 深度检查:仅在必要时分析调用栈
    $trace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 8 );
    foreach ( $trace as $item ) {
        if ( isset( $item['class'] ) && strpos( $item['class'], 'ACF_' ) === 0 ) {
            return true;
        }
        if ( isset( $item['function'] ) && strpos( $item['function'], 'acf_' ) === 0 ) {
            return true;
        }
    }
    return false;
}

3. 使用 WordPress 核心的 doing_filter() 进行精确拦截

sanitize_title 过滤器中,可以检查是否处于特定的 ACF 内部处理流程中。

add_filter( 'sanitize_title', array( $this, 'processSanitizeTitle' ), 9, 3 );
public function processSanitizeTitle( $title, $raw_title, $context ) {
    // 排除 ACF 的内部处理
    if ( $this->isAcfInternalRequest() ) {
        return $title; // 直接返回,不进行任何处理
    }
    // ... 原有逻辑
}

4. 考虑未来扩展性

建议将排除逻辑抽象为一个公共方法,方便其他插件通过过滤器进行扩展。

public function isExcludedPostType( $post_type ) {
    $excluded = apply_filters( 'wpslug_excluded_post_types', self::EXCLUDED_POST_TYPES );
    return in_array( $post_type, $excluded, true );
}

5. 插件审核注意事项

如果计划将插件提交到官方目录,确保所有用户输入都经过验证和清理。你的修复方案主要涉及内部逻辑,但任何从设置页面接收的配置(如排除列表的扩展)都需要使用 sanitize_text_field() 或类似的函数进行处理。

更多关于插件兼容性的讨论,请访问 /c/wordpress。对于涉及用户数据或权限的安全问题,建议咨询 @security