[], 'sub' => [ 'enabled' => false, 'label' => '', 'options' => [], 'roles' => [], 'required' => false, ], ]; private const TRANSLIT = [ 'ç' => 'c', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u', 'Ç' => 'c', 'Ğ' => 'g', 'İ' => 'i', 'Ö' => 'o', 'Ş' => 's', 'Ü' => 'u', 'ä' => 'a', 'Ä' => 'a', 'ß' => 'ss', 'é' => 'e', 'è' => 'e', 'ê' => 'e', 'ë' => 'e', 'É' => 'e', 'È' => 'e', 'à' => 'a', 'â' => 'a', 'À' => 'a', 'î' => 'i', 'ï' => 'i', 'ô' => 'o', 'û' => 'u', 'ù' => 'u', 'ñ' => 'n', 'Ñ' => 'n', 'á' => 'a', 'í' => 'i', 'ó' => 'o', 'ú' => 'u', 'Á' => 'a', 'Í' => 'i', 'Ó' => 'o', 'Ú' => 'u', 'ã' => 'a', 'õ' => 'o', 'ø' => 'o', 'å' => 'a', 'æ' => 'ae', 'œ' => 'oe', ]; /** @param array $raw @return array */ public static function normalize( array $raw ): array { $out = self::DEFAULTS; $out['roles'] = self::list( $raw['roles'] ?? [] ); $sub = is_array( $raw['sub'] ?? null ) ? $raw['sub'] : []; $subRoles = is_array( $sub['roles'] ?? null ) ? array_map( 'strval', $sub['roles'] ) : []; $out['sub'] = [ 'enabled' => ! empty( $sub['enabled'] ), 'label' => trim( (string) ( $sub['label'] ?? '' ) ), 'options' => self::list( $sub['options'] ?? [] ), 'roles' => array_values( array_intersect( $subRoles, self::slugs( $out['roles'] ) ) ), 'required' => ! empty( $sub['required'] ), ]; return $out; } /** * "One label per line" → items. Keeps the slug of an $existing item whose label is unchanged. * * @param array $existing * @return array */ public static function parseList( string $lines, array $existing = [] ): array { $byLabel = []; foreach ( $existing as $e ) { if ( isset( $e['slug'], $e['label'] ) ) { $byLabel[ mb_strtolower( trim( (string) $e['label'] ) ) ] = (string) $e['slug']; } } $out = []; $used = []; foreach ( preg_split( '/\r\n|\r|\n/', $lines ) ?: [] as $line ) { $label = trim( $line ); if ( $label === '' ) { continue; } $slug = $byLabel[ mb_strtolower( $label ) ] ?? self::slugify( $label ); $slug = self::unique( $slug, $used ); $out[] = [ 'slug' => $slug, 'label' => $label, ]; } return $out; } public static function slugify( string $label ): string { $s = strtr( trim( $label ), self::TRANSLIT ); $s = mb_strtolower( $s ); $s = preg_replace( '/[^a-z0-9]+/', '-', $s ) ?? ''; $s = trim( $s, '-' ); return $s !== '' ? $s : 'item'; } /** @param array $items @return array slug => label */ public static function labels( array $items ): array { $out = []; foreach ( $items as $it ) { $out[ $it['slug'] ] = $it['label']; } return $out; } /** @param array $items @return array */ public static function slugs( array $items ): array { return array_values( array_map( static function ( $it ) { return $it['slug']; }, $items ) ); } /** * Drops malformed entries, derives a slug from the label when missing, de-duplicates slugs. * Also used by extensions that keep their own lists in the same settings array. * * @param mixed $raw @return array */ public static function list( $raw ): array { if ( ! is_array( $raw ) ) { return []; } $out = []; $used = []; foreach ( $raw as $it ) { if ( ! is_array( $it ) || trim( (string) ( $it['label'] ?? '' ) ) === '' ) { continue; } $label = trim( (string) $it['label'] ); $slug = trim( (string) ( $it['slug'] ?? '' ) ); $slug = self::slugify( $slug !== '' ? $slug : $label ); $out[] = [ 'slug' => self::unique( $slug, $used ), 'label' => $label, ]; } return $out; } /** @param array $used (by reference) */ private static function unique( string $slug, array &$used ): string { $base = $slug; $i = 2; while ( isset( $used[ $slug ] ) ) { $slug = $base . '-' . ( $i++ ); } $used[ $slug ] = true; return $slug; } }