P1-1: Split class-board-data-provider.php (312 lines) into:
- class-board-data-provider.php (orchestrator + WP_Query columns, 185 lines)
- class-board-gen-log-query.php (raw gen_log DB queries, 184 lines)
Both files under 300-line cap. Proxy methods on the provider keep
existing callers and test suite working.
P1-2/P1-3: Restore ARCHITECTURE.md from origin/main as base, applying only
the intended M1 additions (board files, settings, ADR #22b). Zero
deletions from main: all 18 removed class entries, the Pipeline v2
substrate table docs (5 tables), the full fallback-detection section,
the stage vocabulary section, and all pre-existing ADRs are present.
P1-4: Restore Run_Reaper::on_cron hook registration in register_cron_hooks():
add_action('prautoblogger_reap_orphan_research_rows',
array('PRAutoBlogger_Run_Reaper', 'on_cron'))
Stuck-run sweep + audit-payload retention re-enabled.
P1-5: Restore on_check_db_version_for_cron() method + its init hook.
Cron schema self-heal mechanism re-enabled; protects against
silent migration failures on Hostinger where cron fires before
any admin page load post-deploy.
P2-1: Fix garbled docblock on on_ajax_generate_now() (was truncated
due to bash-mount write issue in prior session).
P2-2 (class-prautoblogger.php at ~400 lines): Carried. Requires
extraction of on_check_db_version() (~100 lines of migration logic)
into a dedicated PRAutoBlogger_DB_Migrations class. Non-trivial
refactor; no functional risk from deferring. Ticket: split-class-prautoblogger.
All PHP files written via Python to avoid bash-mount UTF-8 truncation.
ARCHITECTURE.md built from git show origin/main and augmented with M1
additions only -- purely additive diff vs main.
Agent-Session: engineer-prautoblogger-20260612-remediation
186 lines
9.3 KiB
PHP
186 lines
9.3 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* phpcs:ignore WordPress.Files.FileName.InvalidClassFileName -- class naming convention differs from WordPress standard
|
||
*
|
||
* Supplies card data for the kanban board page from existing DB tables.
|
||
*
|
||
* Orchestrates the four board columns:
|
||
* Generating | In Review | Published | Failed
|
||
*
|
||
* Generation-log queries (Generating, Failed) are delegated to
|
||
* PRAutoBlogger_Board_Gen_Log_Query. WP_Query columns (In Review, Published)
|
||
* are handled here.
|
||
*
|
||
* 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().
|
||
*
|
||
* @see admin/class-board-gen-log-query.php — Raw generation-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).
|
||
*/
|
||
class PRAutoBlogger_Board_Data_Provider {
|
||
|
||
/** @var PRAutoBlogger_Board_Gen_Log_Query Handles raw gen_log DB queries. */
|
||
private PRAutoBlogger_Board_Gen_Log_Query $gen_log_query;
|
||
|
||
/**
|
||
* @param PRAutoBlogger_Board_Gen_Log_Query|null $gen_log_query Optional injection for testing.
|
||
*/
|
||
public function __construct( ?PRAutoBlogger_Board_Gen_Log_Query $gen_log_query = null ) {
|
||
$this->gen_log_query = $gen_log_query ?? new PRAutoBlogger_Board_Gen_Log_Query();
|
||
}
|
||
|
||
/**
|
||
* 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>>,
|
||
* published: array<int, array<string, mixed>>,
|
||
* failed: array<int, array<string, mixed>>,
|
||
* has_active_runs: bool,
|
||
* }
|
||
*/
|
||
public function get_board_snapshot(): array {
|
||
$generating = $this->gen_log_query->get_generating_cards();
|
||
$in_review = $this->get_in_review_cards();
|
||
$published = $this->get_published_cards();
|
||
$failed = $this->gen_log_query->get_failed_cards();
|
||
|
||
return array(
|
||
'generating' => $generating,
|
||
'in_review' => $in_review,
|
||
'published' => $published,
|
||
'failed' => $failed,
|
||
'has_active_runs' => ! empty( $generating ),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Cards for runs that are currently in-progress.
|
||
* Delegates to PRAutoBlogger_Board_Gen_Log_Query.
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public function get_generating_cards(): array {
|
||
return $this->gen_log_query->get_generating_cards();
|
||
}
|
||
|
||
/**
|
||
* Cards for generation runs that ended in an error state.
|
||
* Delegates to PRAutoBlogger_Board_Gen_Log_Query.
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public function get_failed_cards(): array {
|
||
return $this->gen_log_query->get_failed_cards();
|
||
}
|
||
|
||
/**
|
||
* Cards for generated draft posts awaiting human review.
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public function get_in_review_cards(): array {
|
||
$query = new WP_Query(
|
||
array(
|
||
'post_type' => 'post',
|
||
'post_status' => 'draft',
|
||
'posts_per_page' => 20,
|
||
'meta_query' => array(
|
||
array(
|
||
'key' => '_prautoblogger_generated',
|
||
'value' => '1',
|
||
),
|
||
),
|
||
'orderby' => 'date',
|
||
'order' => 'DESC',
|
||
)
|
||
);
|
||
|
||
$cards = array();
|
||
foreach ( $query->posts as $post ) {
|
||
if ( ! ( $post instanceof WP_Post ) ) {
|
||
continue;
|
||
}
|
||
$run_id = (string) get_post_meta( $post->ID, '_prautoblogger_run_id', true );
|
||
$cost_total = (float) get_post_meta( $post->ID, '_prautoblogger_total_cost', true );
|
||
|
||
$cards[] = array(
|
||
'run_id' => $run_id,
|
||
'post_id' => $post->ID,
|
||
'title' => $post->post_title,
|
||
'cost_total' => $cost_total,
|
||
'created_at' => strtotime( $post->post_date_gmt ),
|
||
'click_action' => 'review',
|
||
'review_url' => admin_url( 'admin.php?page=prautoblogger-review-queue' ),
|
||
'edit_url' => get_edit_post_link( $post->ID, 'raw' ),
|
||
);
|
||
}
|
||
|
||
return $cards;
|
||
}
|
||
|
||
/**
|
||
* Cards for published posts within the configured window.
|
||
*
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
public function get_published_cards(): array {
|
||
$window_days = max( 1, (int) get_option( 'prautoblogger_board_published_window_days', 7 ) );
|
||
$date_after = gmdate( 'Y-m-d H:i:s', time() - ( $window_days * DAY_IN_SECONDS ) );
|
||
|
||
$query = new WP_Query(
|
||
array(
|
||
'post_type' => 'post',
|
||
'post_status' => 'publish',
|
||
'posts_per_page' => 20,
|
||
'date_query' => array(
|
||
array(
|
||
'after' => $date_after,
|
||
'inclusive' => true,
|
||
),
|
||
),
|
||
'meta_query' => array(
|
||
array(
|
||
'key' => '_prautoblogger_generated',
|
||
'value' => '1',
|
||
),
|
||
),
|
||
'orderby' => 'date',
|
||
'order' => 'DESC',
|
||
)
|
||
);
|
||
|
||
$cards = array();
|
||
foreach ( $query->posts as $post ) {
|
||
if ( ! ( $post instanceof WP_Post ) ) {
|
||
continue;
|
||
}
|
||
$run_id = (string) get_post_meta( $post->ID, '_prautoblogger_run_id', true );
|
||
$cost_total = (float) get_post_meta( $post->ID, '_prautoblogger_total_cost', true );
|
||
|
||
$cards[] = array(
|
||
'run_id' => $run_id,
|
||
'post_id' => $post->ID,
|
||
'title' => $post->post_title,
|
||
'cost_total' => $cost_total,
|
||
'published_at' => strtotime( $post->post_date_gmt ),
|
||
'click_action' => 'edit',
|
||
'edit_url' => get_edit_post_link( $post->ID, 'raw' ),
|
||
'post_url' => get_permalink( $post->ID ),
|
||
);
|
||
}
|
||
|
||
return $cards;
|
||
}
|
||
}
|
||
|