store = $store; } /** * Registers the REST routes. Call during `rest_api_init`. * * @since 1.0.0 * * @return void */ public function register_routes(): void { register_rest_route( self::REST_NAMESPACE, '/connector-approvals', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_state' ), 'permission_callback' => array( $this, 'permission_callback' ), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'update_approval' ), 'permission_callback' => array( $this, 'permission_callback' ), 'args' => array( 'plugin_basename' => array( 'type' => 'string', 'required' => true, ), 'connector_id' => array( 'type' => 'string', 'required' => true, ), 'approved' => array( 'type' => 'boolean', 'required' => true, ), ), ), ) ); register_rest_route( self::REST_NAMESPACE, '/connector-approvals/pending', array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_pending' ), 'permission_callback' => array( $this, 'permission_callback' ), 'args' => array( 'key' => array( 'type' => 'string', 'required' => true, ), ), ) ); } /** * Permission callback; restricts the endpoints to administrators. * * @since 1.0.0 * * @return bool */ public function permission_callback(): bool { return current_user_can( 'manage_options' ); } /** * Returns the full state snapshot used by the UI. * * @since 1.0.0 * * @return \WP_REST_Response */ public function get_state(): WP_REST_Response { $pending = $this->store->get_pending(); $pending_list = array(); foreach ( $pending as $key => $entry ) { $entry['key'] = $key; $pending_list[] = $entry; } return new WP_REST_Response( array( 'connectors' => $this->describe_connectors(), 'approvals' => $this->store->get_approvals(), 'pending' => $pending_list, 'plugins' => $this->list_active_plugins(), 'themes' => $this->list_active_themes(), ), 200 ); } /** * Sets or revokes approval for a single plugin/connector pair. * * @since 1.0.0 * * @param \WP_REST_Request $request REST request. * @return \WP_REST_Response|\WP_Error */ public function update_approval( WP_REST_Request $request ) { $plugin_basename = (string) $request->get_param( 'plugin_basename' ); $connector_id = (string) $request->get_param( 'connector_id' ); $approved = (bool) $request->get_param( 'approved' ); if ( '' === $plugin_basename || '' === $connector_id ) { return new WP_Error( 'wpai_invalid_approval', __( 'Plugin basename and connector ID are required.', 'ai' ), array( 'status' => 400 ) ); } $this->store->set_approval( $plugin_basename, $connector_id, $approved ); if ( $approved ) { $this->store->remove_pending( $this->store->pending_key( $plugin_basename, $connector_id ) ); } return $this->get_state(); } /** * Removes a pending entry without approving it. * * @since 1.0.0 * * @param \WP_REST_Request $request REST request. * @return \WP_REST_Response|\WP_Error */ public function delete_pending( WP_REST_Request $request ) { $key = (string) $request->get_param( 'key' ); if ( ! $this->store->remove_pending( $key ) ) { return new WP_Error( 'wpai_pending_not_found', __( 'No pending approval request matches the provided key.', 'ai' ), array( 'status' => 404 ) ); } return $this->get_state(); } /** * Returns the AI provider connectors the UI should render. * * @since 1.0.0 * * @return list */ private function describe_connectors(): array { $summary = array(); foreach ( get_ai_connectors() as $connector_id => $data ) { $summary[] = array( 'id' => $connector_id, 'name' => isset( $data['name'] ) && is_string( $data['name'] ) && '' !== $data['name'] ? $data['name'] : $connector_id, ); } usort( $summary, static fn( array $a, array $b ): int => strcasecmp( $a['name'], $b['name'] ) ); return $summary; } /** * Returns the list of active plugins for the UI. * * @since 1.0.0 * * @return list */ private function list_active_plugins(): array { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); $active_plugins = (array) get_option( 'active_plugins', array() ); if ( is_multisite() ) { $network = array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ); $active_plugins = array_merge( $active_plugins, $network ); } $list = array(); foreach ( array_unique( $active_plugins ) as $basename ) { if ( ! is_string( $basename ) || '' === $basename ) { continue; } $data = $all_plugins[ $basename ] ?? array(); $list[] = array( 'basename' => $basename, 'name' => isset( $data['Name'] ) && is_string( $data['Name'] ) && '' !== $data['Name'] ? $data['Name'] : $basename, ); } usort( $list, static fn( array $a, array $b ): int => strcasecmp( $a['name'], $b['name'] ) ); return $list; } /** * Returns the active theme for the UI. * * @since 1.0.0 * * @return list */ private function list_active_themes(): array { $theme = wp_get_theme(); $name = $theme->get( 'Name' ); return array( array( 'basename' => get_stylesheet(), 'name' => is_string( $name ) && '' !== $name ? $name : get_stylesheet(), ), ); } }