*/ public static function detect_intra_wpdo_conflicts(): array { global $wp_filter; $findings = array(); $hooks_to_check = array( 'update_post_metadata', 'add_post_metadata', 'delete_post_metadata', 'get_post_metadata', ); foreach ( $hooks_to_check as $hook ) { if ( ! isset( $wp_filter[ $hook ] ) ) { continue; } // Collect TMDO_* callbacks AND identify which are non-whitelisted. $wpdo_callbacks = array(); $non_whitelist_callbacks = array(); $priorities = $wp_filter[ $hook ]->callbacks ?? array(); foreach ( $priorities as $priority => $callbacks ) { foreach ( $callbacks as $cb ) { $cb = $cb['function'] ?? null; if ( null === $cb ) { continue; } $class_name = self::callable_class_name( $cb ); if ( null === $class_name || ! str_starts_with( $class_name, 'TMDO_' ) ) { continue; } $entry = array( 'class' => $class_name, 'priority' => (int) $priority, ); $wpdo_callbacks[] = $entry; if ( ! in_array( $class_name, self::COEXIST_WHITELIST, true ) ) { $non_whitelist_callbacks[] = $entry; } } } // Real conflict: any non-whitelisted WPDO callback overlapping with // other WPDO callbacks on the same hook. if ( ! empty( $non_whitelist_callbacks ) && count( $wpdo_callbacks ) > 1 ) { foreach ( $non_whitelist_callbacks as $cb ) { $findings[] = array( 'hook' => $hook, 'priority' => $cb['priority'], 'callback' => $cb['class'], ); } } } return $findings; } /** * Extract the class name from a callable, or return null when not class-bound. * * @param mixed $cb Any PHP callable. * @return string|null Fully qualified class name, or null. */ private static function callable_class_name( $cb ): ?string { if ( is_array( $cb ) && isset( $cb[0] ) ) { $obj = $cb[0]; if ( is_object( $obj ) ) { return get_class( $obj ); } if ( is_string( $obj ) ) { return $obj; } } if ( is_string( $cb ) && str_contains( $cb, '::' ) ) { return strtok( $cb, ':' ); } return null; } }