$raw Filter output. * @param string $filter_name Name of the filter that produced $raw, for diagnostics. * @param string $since Version string passed to `_doing_it_wrong()`. * @return array> Normalized entries, zero-indexed. */ public static function normalize( array $raw, string $filter_name, string $since ): array { $normalized = array(); $index = 0; foreach ( $raw as $entry ) { if ( ! is_array( $entry ) ) { continue; } $is_builtin = ! empty( $entry['_builtin'] ); $slug = isset( $entry['slug'] ) ? sanitize_key( (string) $entry['slug'] ) : ''; if ( '' === $slug ) { if ( ! $is_builtin ) { self::doing_it_wrong( $filter_name, 'entry missing slug', $since ); } continue; } $label = isset( $entry['label'] ) ? (string) $entry['label'] : ''; if ( '' === $label && ! $is_builtin ) { self::doing_it_wrong( $filter_name, sprintf( 'entry "%s" missing label', $slug ), $since ); continue; } $render_callback = $entry['render_callback'] ?? null; if ( ! $is_builtin && ! is_callable( $render_callback ) ) { self::doing_it_wrong( $filter_name, sprintf( 'entry "%s" missing callable render_callback', $slug ), $since ); continue; } $capability = isset( $entry['capability'] ) ? sanitize_key( (string) $entry['capability'] ) : 'manage_options'; if ( '' === $capability ) { $capability = 'manage_options'; } $visible_callback = $entry['visible_callback'] ?? null; if ( null !== $visible_callback && ! is_callable( $visible_callback ) ) { $visible_callback = null; } // Keyed by slug so later registrations REPLACE earlier ones with the // same slug (D41 last-wins). See the class docblock. $normalized[ $slug ] = array( 'slug' => $slug, 'label' => $label, 'priority' => isset( $entry['priority'] ) ? (int) $entry['priority'] : 100, 'capability' => $capability, 'render_callback' => $render_callback, 'visible_callback' => $visible_callback, '_builtin' => $is_builtin, '_index' => $index++, ); } // Convert back to a numeric array — downstream sort + iteration expects // zero-indexed sequential entries, not slug-keyed. return array_values( $normalized ); } /** * `_doing_it_wrong()` wrapper for malformed filter entries. * * PRESERVED INVARIANTS (Feature 084, security constraint C7 — these are * deliberate protections, not incidental detail; do not drop them in a * future refactor): * * 1. The `WP_DEBUG` early return. Malformed third-party registrations MUST * NOT surface as notices to production administrators — this matches the * signal-in-development-only pattern the extracted original used. * 2. `esc_html()` on `$filter_name`. Post-extraction this argument is * CALLER-supplied rather than read from a class constant, so it is newly * untrusted by shape even though both current callers pass constants. * 3. `esc_html()` on `$reason`. `$reason` interpolates a third-party-supplied * slug; that slug is already `sanitize_key()`-clean at every call site, * and it is escaped here anyway. "Sanitized upstream" is not a reason to * drop this — see bug pattern B8; `esc_*` is idempotent. * * @since 0.4.0 * @param string $filter_name Name of the filter that produced the entry. * @param string $reason Human-readable description of the malformation. * @param string $since Version string passed to `_doing_it_wrong()`. * @return void */ private static function doing_it_wrong( string $filter_name, string $reason, string $since ): void { if ( ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) { return; } _doing_it_wrong( esc_html( $filter_name ), esc_html( $reason ), esc_html( $since ) ); } }