Files
prautoblogger/includes/admin/class-board-data-provider.php
peptiderepo ff140de050 fix: remediate QA P1s -- restore hooks, split board data provider, restore ARCHITECTURE.md
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
2026-06-12 07:39:02 +00:00

186 lines
9.3 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
}