$routes ) ); } /** * Get routes with transient caching. * * @param bool $force_refresh Whether to bypass cache. * @return array */ public static function get_routes( $force_refresh = false ) { $cache_key = 'aradt_routes_cache'; if ( ! $force_refresh ) { $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } } $server = rest_get_server(); $raw_routes = $server->get_routes(); $routes = array(); foreach ( $raw_routes as $path => $handlers ) { $route_data = array( 'path' => $path, 'namespace' => '', 'methods' => array(), 'endpoints' => array(), ); foreach ( $handlers as $handler ) { if ( isset( $handler['namespace'] ) ) { $route_data['namespace'] = $handler['namespace']; } if ( isset( $handler['methods'] ) ) { $methods = is_array( $handler['methods'] ) ? array_keys( $handler['methods'] ) : array( $handler['methods'] ); $route_data['methods'] = array_unique( array_merge( $route_data['methods'], $methods ) ); } $endpoint = array( 'methods' => isset( $handler['methods'] ) ? array_keys( (array) $handler['methods'] ) : array(), 'args' => isset( $handler['args'] ) ? self::simplify_args( $handler['args'] ) : array(), ); // Source detection via callback reflection. if ( isset( $handler['callback'] ) && is_callable( $handler['callback'] ) ) { $endpoint['source'] = self::detect_source( $handler['callback'] ); } // Permission callback info. if ( isset( $handler['permission_callback'] ) ) { $endpoint['has_permission_callback'] = true; } $route_data['endpoints'][] = $endpoint; } $routes[] = $route_data; } set_transient( $cache_key, $routes, 3600 ); return $routes; } /** * Simplify args schema for display. * * @param array $args Route arguments. * @return array Simplified args. */ private static function simplify_args( $args ) { $simplified = array(); foreach ( $args as $name => $config ) { $simplified[ $name ] = array( 'required' => ! empty( $config['required'] ), 'type' => isset( $config['type'] ) ? $config['type'] : 'string', 'description' => isset( $config['description'] ) ? $config['description'] : '', 'default' => isset( $config['default'] ) ? $config['default'] : null, ); } return $simplified; } /** * Detect the source (plugin/theme/core) of a callback. * * @param callable $callback The route callback. * @return array Source info. */ private static function detect_source( $callback ) { try { if ( is_array( $callback ) ) { $ref = new \ReflectionMethod( $callback[0], $callback[1] ); } elseif ( is_string( $callback ) ) { $ref = new \ReflectionFunction( $callback ); } elseif ( $callback instanceof \Closure ) { $ref = new \ReflectionFunction( $callback ); } else { return array( 'type' => 'unknown' ); } $file = $ref->getFileName(); if ( ! $file ) { return array( 'type' => 'built-in' ); } $file = wp_normalize_path( $file ); // Check if it's a plugin. $plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); if ( str_starts_with( $file, $plugin_dir ) ) { $relative = substr( $file, strlen( $plugin_dir ) + 1 ); $parts = explode( '/', $relative ); return array( 'type' => 'plugin', 'name' => $parts[0], 'file' => $relative, 'line' => $ref->getStartLine(), ); } // Check if it's a theme. $theme_dir = wp_normalize_path( get_theme_root() ); if ( str_starts_with( $file, $theme_dir ) ) { $relative = substr( $file, strlen( $theme_dir ) + 1 ); $parts = explode( '/', $relative ); return array( 'type' => 'theme', 'name' => $parts[0], 'file' => $relative, 'line' => $ref->getStartLine(), ); } // Otherwise it's core. return array( 'type' => 'core', 'file' => basename( $file ), 'line' => $ref->getStartLine(), ); } catch ( \ReflectionException $e ) { return array( 'type' => 'unknown' ); } } }