hash_key = $hash_key ?? ( new LocalHashKey() )->derive(); $this->sanitizer = new ComponentEvidenceSanitizer( $this->hash_key ); $this->paths = $paths ?? new WordPressPathProvider(); } /** * @return list */ public function candidates(): array { return array_map( static function ( array $plugin ): array { return array( 'id' => $plugin['id'], 'name' => $plugin['name'], 'version' => $plugin['version'], 'state' => $plugin['state'], ); }, $this->inventory() ); } /** * Resolve exactly one opaque current selection; no slug, version or path is * accepted from REST input. * * @return array{id:string,slug:string,name:string,version:string,state:string,root:string} */ public function selected( string $opaque_id ): array { if ( 1 !== preg_match( '/^plugin_[a-f0-9]{24}$/D', $opaque_id ) ) { throw new \InvalidArgumentException( 'plugin_checksum_selection_invalid' ); } foreach ( $this->inventory() as $plugin ) { if ( hash_equals( $plugin['id'], $opaque_id ) ) { return $plugin; } } throw new \InvalidArgumentException( 'plugin_checksum_selection_unavailable' ); } /** * @return list */ private function inventory(): array { if ( ! function_exists( 'get_plugins' ) ) { require_once $this->paths->core_file( 'wp-admin/includes/plugin.php' ); } $active = array_map( 'strval', (array) get_option( 'active_plugins', array() ) ); $network_active = is_multisite() ? array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) : array(); $self = defined( 'PX320_SITE_AUDIT_FILE' ) ? plugin_basename( PX320_SITE_AUDIT_FILE ) : ''; $plugins = array(); foreach ( array_slice( get_plugins(), 0, self::MAX_PLUGINS, true ) as $file => $headers ) { $file = (string) $file; if ( '' !== $self && $file === $self ) { continue; } $directory = dirname( $file ); if ( '.' === $directory ) { continue; } $slug = '.' === $directory ? basename( $file, '.php' ) : basename( $directory ); $plugin_path = $this->paths->plugin_path( $directory ); $root = realpath( $plugin_path ); $version = isset( $headers['Version'] ) && is_string( $headers['Version'] ) ? $this->sanitizer->version( $headers['Version'] ) : 'unknown'; if ( false === $root || is_link( $plugin_path ) || 1 !== preg_match( '/^[a-z0-9][a-z0-9-]{0,99}$/D', $slug ) || 'unknown' === $version ) { continue; } $root = wp_normalize_path( $root ); if ( 0 !== strpos( $root . '/', rtrim( wp_normalize_path( $this->paths->plugins_root() ), '/' ) . '/' ) ) { continue; } $state = in_array( $file, $network_active, true ) ? 'network_active' : ( in_array( $file, $active, true ) ? 'active' : 'inactive' ); $identity = $slug . '|' . $version . '|' . $file; $plugins[] = array( 'id' => 'plugin_' . substr( hash_hmac( 'sha256', $identity, $this->hash_key ), 0, 24 ), 'slug' => $slug, 'name' => $this->sanitizer->text( isset( $headers['Name'] ) && is_string( $headers['Name'] ) ? $headers['Name'] : $slug ), 'version' => $version, 'state' => $state, 'root' => $root, ); } usort( $plugins, static function ( array $left, array $right ): int { return array( $left['name'], $left['version'], $left['id'] ) <=> array( $right['name'], $right['version'], $right['id'] ); } ); return $plugins; } }