db = $db; $this->scanner = $scanner; } public function register_routes(): void { register_rest_route( 'acsil/v1', '/link-map', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_link_map' ), 'permission_callback' => array( $this, 'check_edit_posts' ), 'args' => array( 'category' => array( 'type' => 'integer', 'sanitize_callback' => 'absint', ), 'status' => array( 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'enum' => array( 'any', 'publish', 'draft' ), 'default' => 'any', ), 'limit' => array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 0, ), ), ) ); register_rest_route( 'acsil/v1', '/post-links/(?P\d+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_post_links' ), 'permission_callback' => array( $this, 'check_edit_post' ), 'args' => array( 'post_id' => array( 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint', ), ), ) ); } // ── GET /acsil/v1/link-map ──────────────────────────────────────────── public function get_link_map( WP_REST_Request $request ): WP_REST_Response { $args = array( 'status' => $request->get_param( 'status' ) ?? 'any', 'category' => $request->get_param( 'category' ), 'limit' => $request->get_param( 'limit' ), ); $data = $this->db->get_all_links( $args ); $posts = $data['posts']; $edges = $data['edges']; // リンク数の集計 $counts = $this->db->get_all_link_counts(); // カテゴリー情報の取得 $all_categories = get_categories( array( 'hide_empty' => false ) ); $category_colors = array(); foreach ( $all_categories as $cat ) { $category_colors[ $cat->term_id ] = $this->generate_color( $cat->term_id ); } // ノード構築 $nodes = array(); foreach ( $posts as $post ) { $post_id = (int) $post['ID']; $post_cats = wp_get_post_categories( $post_id, array( 'fields' => 'all' ) ); $cat_name = ''; $cat_id = 0; $cat_color = '#7f8c8d'; if ( ! empty( $post_cats ) ) { $first_cat = $post_cats[0]; $cat_name = $first_cat->name; $cat_id = $first_cat->term_id; $cat_color = $category_colors[ $cat_id ] ?? $this->generate_color( $cat_id ); } $nodes[] = array( 'id' => $post_id, 'title' => esc_html( $post['post_title'] ), 'category' => esc_html( $cat_name ), 'category_id' => $cat_id, 'category_color' => $cat_color, 'status' => $post['post_status'], 'edit_url' => esc_url( (string) get_edit_post_link( $post_id, 'raw' ) ), 'outgoing_count' => $counts[ $post_id ]['outgoing'] ?? 0, 'incoming_count' => $counts[ $post_id ]['incoming'] ?? 0, 'date' => mysql2date( 'Y-m-d', $post['post_date'] ), ); } // カテゴリー一覧(凡例用) $categories = array(); foreach ( $all_categories as $cat ) { $categories[] = array( 'id' => $cat->term_id, 'name' => esc_html( $cat->name ), 'color' => $category_colors[ $cat->term_id ], ); } // エッジ整形 $formatted_edges = array(); foreach ( $edges as $edge ) { $formatted_edges[] = array( 'source' => (int) $edge['source'], 'target' => (int) $edge['target'], 'anchor_text' => esc_html( $edge['anchor_text'] ), ); } return new WP_REST_Response( array( 'nodes' => $nodes, 'edges' => $formatted_edges, 'categories' => $categories, ), 200 ); } // ── GET /acsil/v1/post-links/{post_id} ─────────────────────────────── public function get_post_links( WP_REST_Request $request ): WP_REST_Response { $post_id = (int) $request->get_param( 'post_id' ); $outgoing = $this->db->get_outgoing_links( $post_id ); $incoming = $this->db->get_incoming_links( $post_id ); return new WP_REST_Response( array( 'outgoing' => $outgoing, 'incoming' => $incoming, ), 200 ); } // ── 権限コールバック ────────────────────────────────────────────────── public function check_edit_posts(): bool { return current_user_can( 'edit_posts' ); } public function check_edit_post( WP_REST_Request $request ): bool { $post_id = (int) $request->get_param( 'post_id' ); return current_user_can( 'edit_post', $post_id ); } // ── カテゴリーIDからカラー生成 ──────────────────────────────────────── private function generate_color( int $id ): string { $hue = ( $id * 47 ) % 360; $saturation = 55 + ( $id % 20 ); $lightness = 45 + ( $id % 15 ); return "hsl({$hue},{$saturation}%,{$lightness}%)"; } }