db = $db; $this->scanner = $scanner; } // ── メニュー登録 ────────────────────────────────────────────────────── public function register_menus(): void { $hooks = array(); $hooks[] = add_menu_page( __( 'Internal Links', 'acs-internal-links' ), __( 'Internal Links', 'acs-internal-links' ), 'edit_posts', 'acsil-link-map', array( $this, 'render_link_map_page' ), 'dashicons-admin-links', 30 ); $hooks[] = add_submenu_page( 'acsil-link-map', __( 'Link Map', 'acs-internal-links' ), __( 'Link Map', 'acs-internal-links' ), 'edit_posts', 'acsil-link-map', array( $this, 'render_link_map_page' ) ); $hooks[] = add_submenu_page( 'acsil-link-map', __( 'Orphaned Posts', 'acs-internal-links' ), __( 'Orphaned Posts', 'acs-internal-links' ), 'edit_posts', 'acsil-orphan-posts', array( $this, 'render_orphan_page' ) ); $hooks[] = add_submenu_page( 'acsil-link-map', __( 'Settings', 'acs-internal-links' ), __( 'Settings', 'acs-internal-links' ), 'manage_options', 'acsil-settings', array( $this, 'render_settings_page' ) ); // メニュータイトルは翻訳されるため、フックサフィックスは実測値を保持する $this->page_hooks = array_values( array_filter( $hooks, 'is_string' ) ); } // ── スクリプト・スタイル読み込み ────────────────────────────────────── public function enqueue_scripts( string $hook ): void { // CSS:プラグインページ全体 + 投稿一覧/編集画面 $is_plugin_page = in_array( $hook, $this->page_hooks, true ); $is_post_screen = in_array( $hook, array( 'edit.php', 'post.php', 'post-new.php' ), true ); if ( $is_plugin_page || $is_post_screen ) { wp_enqueue_style( 'acsil-admin', ACSIL_URL . 'assets/css/admin.css', array(), ACSIL_VERSION ); } if ( $is_plugin_page ) { $scan_nonce = wp_create_nonce( 'acsil_scan_nonce' ); $delete_nonce = wp_create_nonce( 'acsil_delete_nonce' ); $settings = ACSIL_Core::get_settings(); wp_enqueue_script( 'acsil-admin', ACSIL_URL . 'assets/js/admin.js', array(), ACSIL_VERSION, true ); wp_localize_script( 'acsil-admin', 'acsilData', array( 'restUrl' => esc_url_raw( rest_url( 'acsil/v1/' ) ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'ajaxNonce' => $scan_nonce, 'deleteNonce' => $delete_nonce, 'settings' => array( 'maxLabelLength' => (int) $settings['max_label_length'], 'defaultDisplayCount' => (int) $settings['default_limit'], ), 'i18n' => array( 'scanComplete' => __( 'Scan complete', 'acs-internal-links' ), 'scanError' => __( 'An error occurred during the scan', 'acs-internal-links' ), 'confirmDelete' => __( 'Delete all link data? This action cannot be undone.', 'acs-internal-links' ), 'noLinks' => __( 'No internal links', 'acs-internal-links' ), 'outgoing' => __( 'Outbound', 'acs-internal-links' ), 'incoming' => __( 'Inbound', 'acs-internal-links' ), 'orphanWarning' => __( 'Orphaned Posts', 'acs-internal-links' ), 'scanning' => __( 'Scanning…', 'acs-internal-links' ), 'articles' => __( 'posts processed', 'acs-internal-links' ), 'category' => __( 'Category', 'acs-internal-links' ), 'date' => __( 'Date', 'acs-internal-links' ), /* translators: %d: number of posts shown on the link map */ 'mapManyNodes' => __( '%d posts — labels are shown on hover. Using the filters is recommended.', 'acs-internal-links' ), 'mapTooManyNodes' => __( '⚠ This site has more than 1,000 posts. Please use the category filter.', 'acs-internal-links' ), 'mapLoadError' => __( 'Failed to load the link map. Please reload the page.', 'acs-internal-links' ), ), ) ); // リンクマップページのみ D3.js + link-map.js を読み込む if ( 'toplevel_page_acsil-link-map' === $hook ) { wp_enqueue_script( 'acsil-d3', ACSIL_URL . 'assets/js/d3.v7.min.js', array(), '7.9.0', true ); wp_enqueue_script( 'acsil-link-map', ACSIL_URL . 'assets/js/link-map.js', array( 'acsil-d3', 'acsil-admin' ), ACSIL_VERSION, true ); } } } // ── リンクマップページ ──────────────────────────────────────────────── public function render_link_map_page(): void { if ( ! current_user_can( 'edit_posts' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to access this page.', 'acs-internal-links' ) ); } $categories = get_categories( array( 'hide_empty' => false ) ); $scan_nonce = wp_create_nonce( 'acsil_scan_nonce' ); ?>