properties. * @param callable $normalise callable( string $key, array $definition ): array — one entry * with every property present and typed, `position` among them. * @param string[] $core Properties of an own entry that are put back as `$defaults` has them. * @return array */ public static function build( $tag, array $defaults, $normalise, array $core ) { $registered = apply_filters( (string) $tag, $defaults ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- careback_statuses or careback_routes, the final prefix. $out = array(); foreach ( is_array( $registered ) ? $registered : array() as $key => $definition ) { $key = sanitize_key( (string) $key ); if ( '' === $key || ! is_array( $definition ) ) { continue; } $out[ $key ] = call_user_func( $normalise, $key, $definition ); } // The plugin's own, with the properties its rules read, whatever the // callbacks did to them. foreach ( $defaults as $key => $definition ) { $own = call_user_func( $normalise, $key, $definition ); $out[ $key ] = isset( $out[ $key ] ) ? array_merge( $out[ $key ], array_intersect_key( $own, array_flip( $core ) ) ) : $own; } return self::by_position( $out ); } /** * Entries ordered by their `position`, lower first. Stable: two entries at * the same position keep the order they came in (also on PHP 7.4, whose * sort is not). * * @param array $entries Key => entry carrying `position`. * @return array */ public static function by_position( array $entries ) { $order = array_flip( array_keys( $entries ) ); uksort( $entries, static function ( $a, $b ) use ( $entries, $order ) { if ( $entries[ $a ]['position'] === $entries[ $b ]['position'] ) { return $order[ $a ] - $order[ $b ]; } return $entries[ $a ]['position'] < $entries[ $b ]['position'] ? -1 : 1; } ); return $entries; } }