'mu-plugin:' . $slug, 'label' => '' !== $name ? $name : $slug, 'icon' => 'dashicons-admin-plugins', 'order' => 20, ); } } if ( defined( 'WP_PLUGIN_DIR' ) ) { $plugins_dir = trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ); if ( 0 === strpos( $path, $plugins_dir ) ) { $rel = ltrim( substr( $path, strlen( $plugins_dir ) ), '/' ); $folder = ( false !== strpos( $rel, '/' ) ) ? strtok( $rel, '/' ) : ''; if ( '' === $folder ) { // Single-file plugin living directly in `plugins/`. $name = openstation_my_wordpress_plugin_header_name( $plugins_dir . $rel ); return array( 'id' => 'plugin:' . $rel, 'label' => '' !== $name ? $name : $rel, 'icon' => 'dashicons-admin-plugins', 'order' => 20, ); } return array( 'id' => 'plugin:' . $folder, 'label' => openstation_my_wordpress_plugin_folder_name( $folder ), 'icon' => 'dashicons-admin-plugins', 'order' => 20, ); } } foreach ( (array) get_theme_roots() as $theme_root ) { $root = trailingslashit( wp_normalize_path( get_theme_root( (string) $theme_root ) ) ); if ( 0 !== strpos( $path, $root ) ) { continue; } $rel = ltrim( substr( $path, strlen( $root ) ), '/' ); $stylesheet = ( false !== strpos( $rel, '/' ) ) ? strtok( $rel, '/' ) : $rel; if ( '' === $stylesheet ) { continue; } $theme = wp_get_theme( $stylesheet ); $name = $theme->exists() ? (string) $theme->get( 'Name' ) : ''; return array( 'id' => 'theme:' . $stylesheet, 'label' => '' !== $name ? $name : $stylesheet, 'icon' => 'dashicons-admin-appearance', 'order' => 30, ); } return null; } /** * Human-readable name for an active plugin folder. * * Matches the folder against `wp_get_active_and_valid_plugins()` — which * returns absolute paths to each active plugin's main file and is * available on every request — then reads the `Plugin Name` header from * that file. Falls back to the folder slug when the plugin isn't active * or carries no header (a CPT registered from an inactive plugin folder * is possible via `require`). * * @param string $folder Plugin folder name. * @return string Display name. */ function openstation_my_wordpress_plugin_folder_name( $folder ) { $slug = (string) $folder; if ( '' === $slug ) { return ''; } static $cache = array(); if ( isset( $cache[ $slug ] ) ) { return $cache[ $slug ]; } $name = ''; foreach ( (array) wp_get_active_and_valid_plugins() as $main_file ) { $norm = wp_normalize_path( (string) $main_file ); $rel = ltrim( str_replace( trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ), '', $norm ), '/' ); if ( 0 !== strpos( $rel, $slug . '/' ) ) { continue; } $name = openstation_my_wordpress_plugin_header_name( $norm ); break; } $cache[ $slug ] = '' !== $name ? $name : $slug; return $cache[ $slug ]; } /** * Read the `Plugin Name` header from a plugin file. * * @param string $file Absolute path to a PHP file. * @return string Plugin name, or an empty string. */ function openstation_my_wordpress_plugin_header_name( $file ) { if ( ! is_string( $file ) || '' === $file || ! is_readable( $file ) ) { return ''; } $data = get_file_data( $file, array( 'Name' => 'Plugin Name' ), 'plugin' ); return isset( $data['Name'] ) ? trim( (string) $data['Name'] ) : ''; } /** * Collect the distinct groups referenced by a set of entity * descriptors, in render order. * * The bundle renders one folder tile per entry and drills into it via * the `group` route. Ordering is `order` ascending, then label, so * plugin folders cluster ahead of theme folders. * * @param array[] $entities Entity descriptors. * @return array[] Group descriptors, ordered. */ function openstation_my_wordpress_collect_groups( $entities ) { $groups = array(); foreach ( (array) $entities as $entity ) { if ( empty( $entity['group'] ) ) { continue; } $id = (string) $entity['group']; if ( isset( $groups[ $id ] ) ) { continue; } $groups[ $id ] = array( 'id' => $id, 'label' => isset( $entity['groupLabel'] ) ? (string) $entity['groupLabel'] : $id, 'icon' => isset( $entity['groupIcon'] ) ? (string) $entity['groupIcon'] : 'dashicons-admin-plugins', 'order' => isset( $entity['groupOrder'] ) ? (int) $entity['groupOrder'] : 20, ); } $groups = array_values( $groups ); usort( $groups, static function ( $a, $b ) { if ( $a['order'] === $b['order'] ) { return strnatcasecmp( $a['label'], $b['label'] ); } return $a['order'] < $b['order'] ? -1 : 1; } ); /** * Filter the folder groups shown at the root of the site window. * * Each entry declares `id`, `label`, `icon`, and `order`. Removing * an entry does not hide its post types — they fall back to * rendering loose at the root. To move a type between folders, * use `openstation_my_wordpress_post_type_group` instead. * * **Status: Experimental** — the descriptor may gain fields. * * @param array[] $groups Ordered group descriptors. * @param array[] $entities The entity descriptors they were derived from. */ $filtered = apply_filters( 'openstation_my_wordpress_post_type_groups', $groups, $entities ); return is_array( $filtered ) ? array_values( $filtered ) : $groups; }