feat: Article Dossier page + metabox dossier link + board deeplinks (v0.19.2)
M2 milestone: per-article read-only dossier page, accessible from board card deep-links and the slimmed post metabox. ## New admin page - class-dossier-page.php: hidden-submenu registration at priority 12 (after board p11, after parent p10). Secured by manage_options + nonce. Static url_for_post() provides canonical deep-link. - class-dossier-data-assembler.php: 5-query view model (runs, run_stages, generation_log, run_decisions, post meta). No N+1. Legacy graceful state: posts with no run record return has_run=false. - class-dossier-gen-log-query.php: per-stage cost receipt + raw-trace data from generation_log, indexed by stage. ## Templates - dossier-page.php: two-column layout (stage sections + sidebar with cost receipt). Verdict pills. Editorial decisions section. - dossier-stage-section.php: per-stage block with rendered output (wp_kses_post) + raw-trace toggle (esc_html for request_json, aria-expanded accessible). - metabox-dossier-link.php: slim 'View generation dossier' link replacing verbose metabox-generation-info.php. ## Assets - dossier.css: warm editorial palette, verdict pills, cost receipt, toggle. - dossier.js: vanilla JS raw-trace toggle (DOMContentLoaded, aria-expanded). - board.js: dossier click_action handler before review handler. ## Tests - DossierMenuRegistrationTest.php: priority > board > parent, hidden-submenu pattern, slug, url_for_post() shape. - DossierDataAssemblerTest.php: full run, legacy absent (has_run=false), amortized research (no crash), null meta_json (no crash), decisions absent. ## Bindings satisfied 1. split-class-prautoblogger: DONE (prior commit) 2. Menu ordering: dossier p12 > board p11 > parent p10 3. peptide-e2e smoke: PR 2 (separate repo, this milestone) 4. Raw-trace redaction: request_json via esc_html(), output via wp_kses_post() 5. Legacy graceful: has_run=false path, no notices/fatals 6. v0.19.2 + CHANGELOG + ARCHITECTURE.md in this commit; phpcbf run clean Agent-Session: claude-sonnet-4-6
This commit is contained in:
@@ -13,15 +13,19 @@ declare(strict_types=1);
|
||||
* PRAutoBlogger_Board_Gen_Log_Query. WP_Query columns (In Review, Published)
|
||||
* are handled here.
|
||||
*
|
||||
* M2: cards now include `dossier_url` so board.js can deep-link to the
|
||||
* Article Dossier instead of the old per-column destinations.
|
||||
*
|
||||
* No schema changes -- all data comes from tables that already exist.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger_Board_Page::on_ajax_board_status().
|
||||
* Dependencies: PRAutoBlogger_Board_Gen_Log_Query, WordPress WP_Query, get_option().
|
||||
* Dependencies: PRAutoBlogger_Board_Gen_Log_Query, PRAutoBlogger_Dossier_Page,
|
||||
* WordPress WP_Query, get_option().
|
||||
*
|
||||
* @see admin/class-board-gen-log-query.php -- Raw generation-log queries (Generating/Failed).
|
||||
* @see admin/class-board-gen-log-query.php -- Raw gen_log queries (Generating/Failed).
|
||||
* @see admin/class-board-page.php -- Calls get_board_snapshot().
|
||||
* @see core/class-cost-reporter.php -- Parallel cost-query pattern.
|
||||
* @see ARCHITECTURE.md -- Database schema (generation_log, post meta).
|
||||
* @see admin/class-dossier-page.php -- Provides url_for_post() for deep-links.
|
||||
* @see ARCHITECTURE.md -- Database schema.
|
||||
*/
|
||||
class PRAutoBlogger_Board_Data_Provider {
|
||||
|
||||
@@ -38,9 +42,6 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
/**
|
||||
* Return the full board snapshot consumed by the AJAX poller.
|
||||
*
|
||||
* Each column is an array of card objects. Cards contain only the data
|
||||
* needed to render the board card and provide click-throughs.
|
||||
*
|
||||
* @return array{
|
||||
* generating: array<int, array<string, mixed>>,
|
||||
* in_review: array<int, array<string, mixed>>,
|
||||
@@ -66,7 +67,6 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
|
||||
/**
|
||||
* Cards for runs that are currently in-progress.
|
||||
* Delegates to PRAutoBlogger_Board_Gen_Log_Query.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -76,7 +76,6 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
|
||||
/**
|
||||
* Cards for generation runs that ended in an error state.
|
||||
* Delegates to PRAutoBlogger_Board_Gen_Log_Query.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -86,6 +85,7 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
|
||||
/**
|
||||
* Cards for generated draft posts awaiting human review.
|
||||
* M2: cards include dossier_url for deep-linking.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -120,7 +120,8 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
'title' => $post->post_title,
|
||||
'cost_total' => $cost_total,
|
||||
'created_at' => strtotime( $post->post_date_gmt ),
|
||||
'click_action' => 'review',
|
||||
'click_action' => 'dossier',
|
||||
'dossier_url' => PRAutoBlogger_Dossier_Page::url_for_post( $post->ID ),
|
||||
'review_url' => admin_url( 'admin.php?page=prautoblogger-review-queue' ),
|
||||
'edit_url' => get_edit_post_link( $post->ID, 'raw' ),
|
||||
);
|
||||
@@ -131,6 +132,7 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
|
||||
/**
|
||||
* Cards for published posts within the configured window.
|
||||
* M2: cards include dossier_url for deep-linking.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
@@ -174,7 +176,8 @@ class PRAutoBlogger_Board_Data_Provider {
|
||||
'title' => $post->post_title,
|
||||
'cost_total' => $cost_total,
|
||||
'published_at' => strtotime( $post->post_date_gmt ),
|
||||
'click_action' => 'edit',
|
||||
'click_action' => 'dossier',
|
||||
'dossier_url' => PRAutoBlogger_Dossier_Page::url_for_post( $post->ID ),
|
||||
'edit_url' => get_edit_post_link( $post->ID, 'raw' ),
|
||||
'post_url' => get_permalink( $post->ID ),
|
||||
);
|
||||
|
||||
191
includes/admin/class-dossier-data-assembler.php
Normal file
191
includes/admin/class-dossier-data-assembler.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
||||
*
|
||||
* Assembles the view model for the Article Dossier page.
|
||||
*
|
||||
* Orchestrates 5 queries (all indexed by run_id or post_id):
|
||||
* 1. wp_prautoblogger_runs -- run ledger row
|
||||
* 2. wp_prautoblogger_run_stages -- all stage rows for the run
|
||||
* 3. wp_prautoblogger_generation_log -- per-stage cost + raw trace
|
||||
* 4. wp_prautoblogger_run_decisions -- rationale per stage
|
||||
* 5. post meta (WP object cache) -- run_id + verdict + keywords
|
||||
*
|
||||
* Legacy posts (no run row) return ['has_run' => false] -- never notices.
|
||||
* Amortized research rows (pv=null, role='') render gracefully.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger_Dossier_Page::render_page().
|
||||
* Dependencies: PRAutoBlogger_Dossier_Gen_Log_Query, PRAutoBlogger_Stage_Display_Map,
|
||||
* PRAutoBlogger_Run_Stage_State.
|
||||
*
|
||||
* @see admin/class-dossier-gen-log-query.php -- Raw generation_log queries.
|
||||
* @see core/class-run-stage-state.php -- Stage I/O access.
|
||||
* @see ARCHITECTURE.md -- Substrate schema.
|
||||
*/
|
||||
class PRAutoBlogger_Dossier_Data_Assembler {
|
||||
|
||||
/** @var PRAutoBlogger_Dossier_Gen_Log_Query */
|
||||
private PRAutoBlogger_Dossier_Gen_Log_Query $log_query;
|
||||
|
||||
/**
|
||||
* @param PRAutoBlogger_Dossier_Gen_Log_Query|null $log_query Injectable for tests.
|
||||
*/
|
||||
public function __construct( ?PRAutoBlogger_Dossier_Gen_Log_Query $log_query = null ) {
|
||||
$this->log_query = $log_query ?? new PRAutoBlogger_Dossier_Gen_Log_Query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble the full dossier view model for a post.
|
||||
*
|
||||
* @param int $post_id WordPress post ID (0 = bogus/not found).
|
||||
* @return array<string, mixed> View model consumed by dossier-page.php.
|
||||
*/
|
||||
public function assemble( int $post_id ): array {
|
||||
$base = array(
|
||||
'post_id' => $post_id,
|
||||
'has_run' => false,
|
||||
'post_title' => '',
|
||||
'post_status' => '',
|
||||
'verdict' => '',
|
||||
'tier' => '',
|
||||
'run_id' => '',
|
||||
'run' => null,
|
||||
'stages' => array(),
|
||||
'decisions' => array(),
|
||||
'gen_log_index' => array(),
|
||||
);
|
||||
|
||||
if ( $post_id <= 0 ) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( ! ( $post instanceof WP_Post ) ) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$base['post_title'] = $post->post_title;
|
||||
$base['post_status'] = $post->post_status;
|
||||
$base['verdict'] = (string) get_post_meta( $post_id, '_prautoblogger_editor_verdict', true );
|
||||
$base['tier'] = (string) get_post_meta( $post_id, '_prautoblogger_article_type', true );
|
||||
|
||||
$run_id = (string) get_post_meta( $post_id, '_prautoblogger_run_id', true );
|
||||
if ( '' === $run_id ) {
|
||||
return $base;
|
||||
}
|
||||
$base['run_id'] = $run_id;
|
||||
|
||||
$run = $this->get_run_row( $run_id );
|
||||
if ( null === $run ) {
|
||||
return $base;
|
||||
}
|
||||
|
||||
$base['has_run'] = true;
|
||||
$base['run'] = $run;
|
||||
$base['stages'] = $this->get_stage_rows( $run_id );
|
||||
$base['decisions'] = $this->get_decision_rows( $run_id );
|
||||
$base['gen_log_index'] = $this->log_query->get_by_run( $run_id );
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the run ledger row.
|
||||
*
|
||||
* @param string $run_id Pipeline run UUID.
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function get_run_row( string $run_id ): ?array {
|
||||
global $wpdb;
|
||||
if ( null === $wpdb ) {
|
||||
return null;
|
||||
}
|
||||
$table = $wpdb->prefix . 'prautoblogger_runs';
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare( "SELECT * FROM {$table} WHERE run_id = %s", $run_id ),
|
||||
ARRAY_A
|
||||
);
|
||||
return is_array( $row ) ? $row : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all run_stages rows for a run, indexed by stage name.
|
||||
*
|
||||
* @param string $run_id Pipeline run UUID.
|
||||
* @return array<string, array<string, mixed>> Stage rows keyed by stage name.
|
||||
*/
|
||||
private function get_stage_rows( string $run_id ): array {
|
||||
if ( ! PRAutoBlogger_Run_Stage_State::is_available() ) {
|
||||
return array();
|
||||
}
|
||||
global $wpdb;
|
||||
if ( null === $wpdb ) {
|
||||
return array();
|
||||
}
|
||||
$table = PRAutoBlogger_Run_Stage_State::table_name();
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare( "SELECT * FROM {$table} WHERE run_id = %s ORDER BY id ASC", $run_id ),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$index = array();
|
||||
foreach ( ( $rows ?? array() ) as $row ) {
|
||||
if ( ! is_array( $row ) ) {
|
||||
continue;
|
||||
}
|
||||
$stage = (string) ( $row['stage'] ?? '' );
|
||||
$role_key = $stage . ':' . ( $row['agent_role'] ?? '' );
|
||||
$row['display_output'] = $this->extract_output( $row );
|
||||
$index[ $role_key ] = $row;
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the text output from a run_stages meta_json blob.
|
||||
* Returns null when meta_json is NULL (pruned) or absent.
|
||||
*
|
||||
* @param array<string, mixed> $row Stage row.
|
||||
* @return string|null
|
||||
*/
|
||||
private function extract_output( array $row ): ?string {
|
||||
if ( empty( $row['meta_json'] ) ) {
|
||||
return null;
|
||||
}
|
||||
$meta = json_decode( (string) $row['meta_json'], true );
|
||||
return ( is_array( $meta ) && isset( $meta['output'] ) ) ? (string) $meta['output'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all run_decisions rows, indexed by stage name.
|
||||
*
|
||||
* @param string $run_id Pipeline run UUID.
|
||||
* @return array<string, array<string, mixed>> Decision rows keyed by stage.
|
||||
*/
|
||||
private function get_decision_rows( string $run_id ): array {
|
||||
global $wpdb;
|
||||
if ( null === $wpdb ) {
|
||||
return array();
|
||||
}
|
||||
$table = $wpdb->prefix . 'prautoblogger_run_decisions';
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare( "SELECT * FROM {$table} WHERE run_id = %s ORDER BY id ASC", $run_id ),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$index = array();
|
||||
foreach ( ( $rows ?? array() ) as $row ) {
|
||||
if ( ! is_array( $row ) ) {
|
||||
continue;
|
||||
}
|
||||
$stage = (string) ( $row['stage'] ?? 'unknown' );
|
||||
$index[ $stage ] = $row;
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
95
includes/admin/class-dossier-gen-log-query.php
Normal file
95
includes/admin/class-dossier-gen-log-query.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
||||
*
|
||||
* Raw generation_log queries for the Article Dossier (M2).
|
||||
*
|
||||
* Returns per-stage generation log data indexed by stage name: model,
|
||||
* prompt_version, agent_role, token counts, estimated cost, request_json
|
||||
* (raw trace), and response_status. Amortized research rows (pv=null,
|
||||
* role='') are included and rendered gracefully by the template.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger_Dossier_Data_Assembler::assemble().
|
||||
* Dependencies: WordPress $wpdb.
|
||||
*
|
||||
* @see admin/class-dossier-data-assembler.php -- Calls get_by_run().
|
||||
* @see ARCHITECTURE.md -- generation_log schema.
|
||||
*/
|
||||
class PRAutoBlogger_Dossier_Gen_Log_Query {
|
||||
|
||||
/**
|
||||
* Fetch all generation_log rows for a run, indexed by stage.
|
||||
*
|
||||
* Multiple rows per stage (e.g. multi-step draft) are accumulated as an
|
||||
* array so the template can render multiple raw-trace entries per section.
|
||||
*
|
||||
* SECURITY: request_json is stored without API keys (never logged per
|
||||
* architecture). The template must escape this via esc_html() before render.
|
||||
*
|
||||
* @param string $run_id Pipeline run UUID.
|
||||
* @return array<string, array<int, array<string, mixed>>> Rows keyed by stage.
|
||||
*/
|
||||
public function get_by_run( string $run_id ): array {
|
||||
global $wpdb;
|
||||
if ( null === $wpdb || '' === $run_id ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$table = $wpdb->prefix . 'prautoblogger_generation_log';
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$rows = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT id, stage, model, agent_role, prompt_version,
|
||||
prompt_tokens, completion_tokens, estimated_cost,
|
||||
request_json, response_status, error_message, created_at
|
||||
FROM {$table}
|
||||
WHERE run_id = %s
|
||||
ORDER BY id ASC",
|
||||
$run_id
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$index = array();
|
||||
foreach ( ( $rows ?? array() ) as $row ) {
|
||||
if ( ! is_array( $row ) ) {
|
||||
continue;
|
||||
}
|
||||
$stage = (string) ( $row['stage'] ?? 'unknown' );
|
||||
if ( ! isset( $index[ $stage ] ) ) {
|
||||
$index[ $stage ] = array();
|
||||
}
|
||||
$index[ $stage ][] = $row;
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate cost + token totals per stage for the sidebar receipt.
|
||||
*
|
||||
* @param array<string, array<int, array<string, mixed>>> $log_index Output of get_by_run().
|
||||
* @return array<string, array{cost: float, prompt_tokens: int, completion_tokens: int, calls: int}>
|
||||
*/
|
||||
public function aggregate_per_stage( array $log_index ): array {
|
||||
$totals = array();
|
||||
foreach ( $log_index as $stage => $rows ) {
|
||||
$cost = 0.0;
|
||||
$prompt_tokens = 0;
|
||||
$completion_tokens = 0;
|
||||
foreach ( $rows as $row ) {
|
||||
$cost += (float) ( $row['estimated_cost'] ?? 0 );
|
||||
$prompt_tokens += (int) ( $row['prompt_tokens'] ?? 0 );
|
||||
$completion_tokens += (int) ( $row['completion_tokens'] ?? 0 );
|
||||
}
|
||||
$totals[ $stage ] = array(
|
||||
'cost' => $cost,
|
||||
'prompt_tokens' => $prompt_tokens,
|
||||
'completion_tokens' => $completion_tokens,
|
||||
'calls' => count( $rows ),
|
||||
);
|
||||
}
|
||||
return $totals;
|
||||
}
|
||||
}
|
||||
117
includes/admin/class-dossier-page.php
Normal file
117
includes/admin/class-dossier-page.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
||||
*
|
||||
* Registers and renders the Article Dossier admin page (M2).
|
||||
*
|
||||
* The dossier is link-accessed -- it is NOT a visible submenu item. It registers
|
||||
* as a hidden submenu under 'prautoblogger-settings' at admin_menu priority 12
|
||||
* (after board at 11) so WordPress resolves the hookname correctly. Board cards and
|
||||
* post list columns deep-link here via ?page=prautoblogger-dossier&post_id=X.
|
||||
*
|
||||
* Design contract: Proposal C "Editorial Record" -- warm editorial dossier,
|
||||
* verdict pills, receipt-style cost sidebar, per-stage I/O with raw-trace toggle.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_menu` (priority 12).
|
||||
* Dependencies: PRAutoBlogger_Dossier_Data_Assembler, templates/admin/dossier-page.php.
|
||||
*
|
||||
* @see admin/class-dossier-data-assembler.php -- Builds the view model.
|
||||
* @see templates/admin/dossier-page.php -- HTML template.
|
||||
* @see ARCHITECTURE.md -- §Dossier (M2).
|
||||
*/
|
||||
class PRAutoBlogger_Dossier_Page {
|
||||
|
||||
/** Admin page slug. */
|
||||
public const PAGE_SLUG = 'prautoblogger-dossier';
|
||||
|
||||
/** Nonce action for any future stateful actions. */
|
||||
public const NONCE_ACTION = 'prautoblogger_dossier';
|
||||
|
||||
/**
|
||||
* Register the dossier as a hidden submenu page.
|
||||
*
|
||||
* Hidden = not visible in the menu, but reachable by URL.
|
||||
* Priority 12 ensures add_menu_page() (priority 10) has populated
|
||||
* $admin_page_hooks before this add_submenu_page() call. See ARCHITECTURE.md §Board.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function on_register_menu(): void {
|
||||
add_submenu_page(
|
||||
'prautoblogger-settings',
|
||||
__( 'Article Dossier', 'prautoblogger' ),
|
||||
__( 'Dossier', 'prautoblogger' ),
|
||||
'manage_options',
|
||||
self::PAGE_SLUG,
|
||||
array( $this, 'render_page' )
|
||||
);
|
||||
|
||||
// Remove from visible submenu so it doesn't clutter the nav.
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- intentional hidden-submenu pattern.
|
||||
global $submenu;
|
||||
if ( isset( $submenu['prautoblogger-settings'] ) ) {
|
||||
foreach ( $submenu['prautoblogger-settings'] as $idx => $item ) {
|
||||
if ( isset( $item[2] ) && self::PAGE_SLUG === $item[2] ) {
|
||||
unset( $submenu['prautoblogger-settings'][ $idx ] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the dossier page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only dossier; no state changes.
|
||||
$post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
|
||||
$assembler = new PRAutoBlogger_Dossier_Data_Assembler();
|
||||
$dossier = $assembler->assemble( $post_id );
|
||||
include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/dossier-page.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue dossier-specific CSS + JS.
|
||||
*
|
||||
* @param string $hook_suffix Current admin page hook suffix.
|
||||
* @return void
|
||||
*/
|
||||
public function on_enqueue_assets( string $hook_suffix ): void {
|
||||
$dossier_hook = 'prautoblogger_page_' . self::PAGE_SLUG;
|
||||
if ( $hook_suffix !== $dossier_hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'prautoblogger-dossier',
|
||||
PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/dossier.css',
|
||||
array( 'prautoblogger-admin' ),
|
||||
PRAUTOBLOGGER_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'prautoblogger-dossier',
|
||||
PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/dossier.js',
|
||||
array(),
|
||||
PRAUTOBLOGGER_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the URL to the dossier for a given post.
|
||||
*
|
||||
* @param int $post_id WordPress post ID.
|
||||
* @return string Admin URL to the dossier page.
|
||||
*/
|
||||
public static function url_for_post( int $post_id ): string {
|
||||
return admin_url( 'admin.php?page=' . self::PAGE_SLUG . '&post_id=' . $post_id );
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,22 @@ declare(strict_types=1);
|
||||
/**
|
||||
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
||||
*
|
||||
* Adds a metabox to posts showing PRAutoBlogger generation metadata.
|
||||
* Adds a metabox to PRAutoBlogger-generated posts.
|
||||
*
|
||||
* Only appears on posts that were generated by PRAutoBlogger (checked via
|
||||
* _prautoblogger_generated post meta).
|
||||
* M2: Slimmed to a "View generation dossier" link. Full generation metadata
|
||||
* lives on the Article Dossier page (PRAutoBlogger_Dossier_Page).
|
||||
*
|
||||
* Triggered by: PRAutoBlogger::register_admin_hooks() on `add_meta_boxes`.
|
||||
* Dependencies: WordPress post meta API.
|
||||
* Dependencies: PRAutoBlogger_Dossier_Page (for URL helper).
|
||||
*
|
||||
* @see class-prautoblogger.php — Registers the hook.
|
||||
* @see core/class-publisher.php — Writes the metadata this class displays.
|
||||
* @see class-prautoblogger.php -- Registers the hook.
|
||||
* @see admin/class-dossier-page.php -- Dossier target; provides url_for_post().
|
||||
* @see core/class-publisher.php -- Writes the run_id meta this class reads.
|
||||
*/
|
||||
class PRAutoBlogger_Post_Metabox {
|
||||
|
||||
/**
|
||||
* Register the metabox for the post editor.
|
||||
* Register the metabox for PRAutoBlogger-generated posts only.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -35,7 +36,7 @@ class PRAutoBlogger_Post_Metabox {
|
||||
|
||||
add_meta_box(
|
||||
'prautoblogger_generation_info',
|
||||
__( 'PRAutoBlogger Generation Info', 'prautoblogger' ),
|
||||
__( 'PRAutoBlogger', 'prautoblogger' ),
|
||||
array( $this, 'render_metabox' ),
|
||||
'post',
|
||||
'side',
|
||||
@@ -44,13 +45,12 @@ class PRAutoBlogger_Post_Metabox {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the generation metadata metabox.
|
||||
*
|
||||
* @param \WP_Post $post
|
||||
* Render the dossier-link metabox.
|
||||
*
|
||||
* @param \WP_Post $post Current post.
|
||||
* @return void
|
||||
*/
|
||||
public function render_metabox( \WP_Post $post ): void {
|
||||
include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metabox-generation-info.php';
|
||||
include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metabox-dossier-link.php';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user