キャッシュ(リクエスト内) */ private array $counts_cache = array(); public function __construct( ACSIL_Database $db ) { $this->db = $db; } public function register_column_hooks(): void { $settings = ACSIL_Core::get_settings(); if ( ! $settings['show_list_columns'] ) { return; } $post_types = $settings['post_types']; foreach ( $post_types as $post_type ) { add_filter( "manage_{$post_type}_posts_columns", array( $this, 'add_columns' ) ); add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'render_column' ), 10, 2 ); add_filter( "manage_edit-{$post_type}_sortable_columns", array( $this, 'make_sortable' ) ); } add_action( 'pre_get_posts', array( $this, 'sort_query' ) ); } // ── カラム追加 ──────────────────────────────────────────────────────── public function add_columns( array $columns ): array { // 「タグ」の直後に挿入 $new = array(); foreach ( $columns as $key => $label ) { $new[ $key ] = $label; if ( 'tags' === $key || 'author' === $key ) { $new['acsil_outgoing'] = '' . esc_html__( 'Outbound', 'acs-internal-links' ) . ''; $new['acsil_incoming'] = '' . esc_html__( 'Inbound', 'acs-internal-links' ) . ''; } } // 挿入されなかった場合は末尾に追加 if ( ! array_key_exists( 'acsil_outgoing', $new ) ) { $new['acsil_outgoing'] = esc_html__( 'Outbound', 'acs-internal-links' ); $new['acsil_incoming'] = esc_html__( 'Inbound', 'acs-internal-links' ); } return $new; } // ── カラム内容表示 ──────────────────────────────────────────────────── public function render_column( string $column, int $post_id ): void { if ( ! in_array( $column, array( 'acsil_outgoing', 'acsil_incoming' ), true ) ) { return; } // 一覧ページの全カウントを1回だけ取得してキャッシュ if ( empty( $this->counts_cache ) ) { $this->counts_cache = $this->db->get_all_link_counts(); } $outgoing = $this->counts_cache[ $post_id ]['outgoing'] ?? 0; $incoming = $this->counts_cache[ $post_id ]['incoming'] ?? 0; if ( 'acsil_outgoing' === $column ) { $class = 0 === $outgoing ? 'acsil-count-zero-gray' : ''; printf( '%d', esc_attr( $class ), (int) $outgoing ); } elseif ( 'acsil_incoming' === $column ) { $class = 0 === $incoming ? 'acsil-count-zero-red' : ''; printf( '%d', esc_attr( $class ), (int) $incoming ); } } // ── ソート対応 ──────────────────────────────────────────────────────── public function make_sortable( array $columns ): array { $columns['acsil_outgoing'] = 'acsil_outgoing'; $columns['acsil_incoming'] = 'acsil_incoming'; return $columns; } public function sort_query( WP_Query $query ): void { if ( ! is_admin() || ! $query->is_main_query() ) { return; } $orderby = $query->get( 'orderby' ); if ( ! in_array( $orderby, array( 'acsil_outgoing', 'acsil_incoming' ), true ) ) { return; } global $wpdb; $table = $wpdb->prefix . 'acsil_links'; if ( 'acsil_outgoing' === $orderby ) { $query->set( 'orderby', 'acsil_count' ); $query->set( 'meta_query', array() ); add_filter( 'posts_clauses', function ( $clauses ) use ( $wpdb, $table, $query ) { $order = strtoupper( $query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC'; $clauses['join'] .= " LEFT JOIN (SELECT source_post_id, COUNT(*) AS out_cnt FROM {$table} GROUP BY source_post_id) AS acsil_out ON acsil_out.source_post_id = {$wpdb->posts}.ID"; $clauses['orderby'] = "COALESCE(acsil_out.out_cnt, 0) {$order}"; return $clauses; } ); } elseif ( 'acsil_incoming' === $orderby ) { add_filter( 'posts_clauses', function ( $clauses ) use ( $wpdb, $table, $query ) { $order = strtoupper( $query->get( 'order' ) ) === 'ASC' ? 'ASC' : 'DESC'; $clauses['join'] .= " LEFT JOIN (SELECT target_post_id, COUNT(*) AS in_cnt FROM {$table} GROUP BY target_post_id) AS acsil_in ON acsil_in.target_post_id = {$wpdb->posts}.ID"; $clauses['orderby'] = "COALESCE(acsil_in.in_cnt, 0) {$order}"; return $clauses; } ); } } }