$item ) { if ( isset( $item[2] ) && self::PAGE_SLUG === $item[2] ) { $board_idx = $i; break; } } if ( null !== $board_idx && $board_idx > 0 ) { $board_item = $items[ $board_idx ]; unset( $items[ $board_idx ] ); $submenu['prautoblogger-settings'] = array_merge( array( $board_item ), array_values( $items ) ); } } } /** * Render the board page. * * @return void */ public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/board-page.php'; } /** * Enqueue board-specific JS + CSS and localize the poller + inspector config. * * Called via `admin_enqueue_scripts` action, gated to this page's hook suffix. * * @param string $hook_suffix Current admin page hook suffix. * @return void */ public function on_enqueue_assets( string $hook_suffix ): void { $board_hook = 'prautoblogger_page_' . self::PAGE_SLUG; if ( $hook_suffix !== $board_hook ) { return; } wp_enqueue_style( 'prautoblogger-board', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/board.css', array( 'prautoblogger-admin' ), PRAUTOBLOGGER_VERSION ); wp_enqueue_script( 'prautoblogger-board', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/board.js', array( 'jquery' ), PRAUTOBLOGGER_VERSION, true ); $poll_interval = max( 3, (int) get_option( 'prautoblogger_board_poll_interval', 5 ) ); $published_days = max( 1, (int) get_option( 'prautoblogger_board_published_window_days', 7 ) ); wp_localize_script( 'prautoblogger-board', 'prabBoard', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE_ACTION ), 'pollInterval' => $poll_interval * 1000, 'publishedWindowDays' => $published_days, 'action' => self::AJAX_ACTION, 'inspectorAction' => PRAutoBlogger_Board_Inspector_Handler::ACTION, 'ideasUrl' => admin_url( 'admin.php?page=prautoblogger-ideas' ), 'strings' => array( 'generating' => __( 'Generating', 'prautoblogger' ), 'inReview' => __( 'In review', 'prautoblogger' ), 'published' => __( 'Published', 'prautoblogger' ), 'failed' => __( 'Failed', 'prautoblogger' ), 'stalled' => __( 'Stalled', 'prautoblogger' ), 'empty' => __( 'Nothing here yet.', 'prautoblogger' ), 'cost' => __( 'Cost', 'prautoblogger' ), 'view' => __( 'View', 'prautoblogger' ), 'edit' => __( 'Edit', 'prautoblogger' ), 'viewLog' => __( 'View log', 'prautoblogger' ), 'pollError' => __( 'Board update failed — retrying.', 'prautoblogger' ), 'humanModified' => __( 'Human-modified', 'prautoblogger' ), 'inspectorEmpty' => __( 'Select an article to preview its pipeline.', 'prautoblogger' ), 'inspectorLoad' => __( 'Loading…', 'prautoblogger' ), 'inspectorError' => __( 'Could not load run details.', 'prautoblogger' ), 'openDossier' => __( 'Open dossier →', 'prautoblogger' ), 'newArticle' => __( 'New article', 'prautoblogger' ), 'input' => __( 'Input', 'prautoblogger' ), 'output' => __( 'Output', 'prautoblogger' ), 'pruned' => __( '[pruned — past retention window]', 'prautoblogger' ), 'noOutput' => __( '[no output recorded]', 'prautoblogger' ), 'totalCost' => __( 'Total cost', 'prautoblogger' ), 'tokens' => __( 'tok', 'prautoblogger' ), ), ) ); } /** * AJAX handler: return the current board snapshot (all four sections). * * Nonce: `prautoblogger_board`. Requires `manage_options` capability. * * Side effects: DB reads only -- no writes. * * @return void */ public function on_ajax_board_status(): void { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ), 403 ); return; } $provider = new PRAutoBlogger_Board_Data_Provider(); $snapshot = $provider->get_board_snapshot(); wp_send_json_success( $snapshot ); } }