Compare commits

...

6 Commits

Author SHA1 Message Date
peptiderepo
7669a9c199 chore: revert PHPCS to summary report 2026-04-24 01:34:09 +07:00
peptiderepo
97f5936d27 style: fix PSR2.Files.EndFileNewline — single trailing newline 2026-04-24 01:33:57 +07:00
peptiderepo
58496156ee chore: temp PHPCS full report for debug 2026-04-24 01:31:08 +07:00
peptiderepo
7b397d74c7 style: fix PHPCS — trailing ws + blank line before class } 2026-04-24 01:21:43 +07:00
peptiderepo
4b05180bca docs: add missing docblocks and parameter documentation
- Add comprehensive docblock to private get_style_suffix() method explaining empty-string safeguard
- Add @param documentation for new cost_tracker parameter in publish() and save_as_draft() methods
- Improves code clarity for future maintainers
2026-04-23 18:11:43 +00:00
peptiderepo
9bd843297d fix: image style suffix empty-string bypass and image cost run_id mismatch (v0.12.2)
Bug 1: Image style suffix empty-string bypass
- get_option() for prautoblogger_image_style_suffix returns empty string from DB instead of default
- Root cause of post #675 rendering photorealistically instead of newspaper comic
- Fixed by extracting private helper get_style_suffix() that treats both absent AND empty as default
- Applied to all three call sites in class-image-prompt-builder.php (lines 85, 107, 272)

Bug 2: Image cost logged under wrong run_id
- class-image-pipeline.php created fresh cost tracker when invoked without argument
- Image costs logged under different run_id than article's other stages
- Cost breakdowns aggregating by run_id silently excluded image line items
- Fixed by threading article worker's cost tracker through Publisher to image pipeline
- Modified publisher.php and post-assembler.php to accept and pass cost_tracker
- Updated article-worker.php to pass cost_tracker to publisher methods
- Image costs now share same run_id as analysis, draft, review, and llm_research stages

Version: 0.12.1 -> 0.12.2
CHANGELOG: Added comprehensive entry describing both fixes
2026-04-23 18:06:54 +00:00
6 changed files with 49 additions and 19 deletions

View File

@@ -5,6 +5,14 @@ All notable changes to PRAutoBlogger will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project uses [Semantic Versioning](https://semver.org/). and this project uses [Semantic Versioning](https://semver.org/).
## [0.12.2] — 2026-04-24
### Fixed
- **Image style suffix empty-string bypass** — `get_option()` for `prautoblogger_image_style_suffix` only returns the default constant when the key is absent from `wp_options`; an empty string stored in the DB returns `''`, bypassing fallback entirely. Root cause of post #675 rendering photorealistically instead of as a newspaper comic. Fixed all three call sites in `class-image-prompt-builder.php` (lines 85, 107, 272) by extracting a private helper `get_style_suffix()` that treats both absent AND empty string as "use default".
- **Image cost logged under wrong run_id** — `class-image-pipeline.php` created a fresh cost tracker when invoked without an argument, so image costs were logged under a different `run_id` than the article's other stages. Cost breakdowns aggregating by `run_id` silently excluded the image line item. Fixed by threading the article worker's cost tracker through `PRAutoBlogger_Publisher` and `PRAutoBlogger_Post_Assembler::attach_generated_images()` to the image pipeline constructor. Image costs now share the same `run_id` as analysis, draft, review, and llm_research stages.
## [0.12.1] — 2026-04-23 ## [0.12.1] — 2026-04-23
### Fixed ### Fixed

View File

@@ -138,13 +138,13 @@ class PRAutoBlogger_Article_Worker {
: $content; : $content;
if ( $auto_publish ) { if ( $auto_publish ) {
$publisher->publish( $final, $idea, $review, $run_id ); $publisher->publish( $final, $idea, $review, $run_id, $this->cost_tracker );
++$result['published']; ++$result['published'];
} else { } else {
$publisher->save_as_draft( $final, $idea, $review, $run_id ); $publisher->save_as_draft( $final, $idea, $review, $run_id, $this->cost_tracker );
} }
} else { } else {
$publisher->save_as_draft( $content, $idea, $review, $run_id ); $publisher->save_as_draft( $content, $idea, $review, $run_id, $this->cost_tracker );
++$result['rejected']; ++$result['rejected'];
PRAutoBlogger_Logger::instance()->info( PRAutoBlogger_Logger::instance()->info(
'Article rejected by editor: ' . $idea->get_topic(), 'Article rejected by editor: ' . $idea->get_topic(),

View File

@@ -82,7 +82,7 @@ PROMPT;
$parsed = $this->rewrite_via_llm( $title, $first_para ); $parsed = $this->rewrite_via_llm( $title, $first_para );
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX ); $style_suffix = $this->get_style_suffix();
return array( return array(
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ), 'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
@@ -104,7 +104,7 @@ PROMPT;
$parsed = $this->rewrite_via_llm( $title, $context ); $parsed = $this->rewrite_via_llm( $title, $context );
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX ); $style_suffix = $this->get_style_suffix();
return array( return array(
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ), 'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
@@ -269,7 +269,7 @@ PROMPT;
*/ */
public function build_fallback_prompt( string $title ): array { public function build_fallback_prompt( string $title ): array {
$parsed = $this->synthesize_visual_concepts_fallback( $title, '' ); $parsed = $this->synthesize_visual_concepts_fallback( $title, '' );
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX ); $style_suffix = $this->get_style_suffix();
return array( return array(
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ), 'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
'caption' => $parsed['caption'], 'caption' => $parsed['caption'],
@@ -311,4 +311,20 @@ PROMPT;
$override = (string) get_option( 'prautoblogger_image_prompt_instructions', '' ); $override = (string) get_option( 'prautoblogger_image_prompt_instructions', '' );
return '' !== trim( $override ) ? $override : self::REWRITER_SYSTEM_PROMPT; return '' !== trim( $override ) ? $override : self::REWRITER_SYSTEM_PROMPT;
} }
/**
* Get image style suffix with empty-string safeguard.
*
* Treats both absent and empty string as "use default" to prevent
* silent style loss if the admin settings field is saved empty.
*
* @return string The style suffix with safe fallback.
*/
private function get_style_suffix(): string {
$style_suffix = (string) get_option( 'prautoblogger_image_style_suffix', '' );
if ( '' === trim( $style_suffix ) ) {
$style_suffix = PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX;
}
return $style_suffix;
}
} }

View File

@@ -232,11 +232,12 @@ class PRAutoBlogger_Post_Assembler {
* Generate and attach images to a published post. * Generate and attach images to a published post.
* Non-blocking — logs errors but doesn't re-throw since the post is already created. * Non-blocking — logs errors but doesn't re-throw since the post is already created.
* *
* @param int $post_id Post ID. * @param int $post_id Post ID.
* @param PRAutoBlogger_Article_Idea $idea Article idea (contains source IDs). * @param PRAutoBlogger_Article_Idea $idea Article idea (contains source IDs).
* @param array $post_data Post data array (for article content). * @param array $post_data Post data array (for article content).
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
*/ */
public static function attach_generated_images( int $post_id, PRAutoBlogger_Article_Idea $idea, array $post_data ): void { public static function attach_generated_images( int $post_id, PRAutoBlogger_Article_Idea $idea, array $post_data, ?PRAutoBlogger_Cost_Tracker $cost_tracker = null ): void {
// Fetch the original Reddit source data for Image B's source-driven prompt. // Fetch the original Reddit source data for Image B's source-driven prompt.
// The idea's source_ids point to rows in the source_data table collected // The idea's source_ids point to rows in the source_data table collected
// during the pipeline's collection step. // during the pipeline's collection step.
@@ -247,7 +248,7 @@ class PRAutoBlogger_Post_Assembler {
// internally, immediately after each image generates. This // internally, immediately after each image generates. This
// ensures attachment persists even if the process times out // ensures attachment persists even if the process times out
// before this method returns. // before this method returns.
$result = ( new PRAutoBlogger_Image_Pipeline() )->generate_and_attach_images( $post_id, $post_data, $source_data ); $result = ( new PRAutoBlogger_Image_Pipeline( null, $cost_tracker ) )->generate_and_attach_images( $post_id, $post_data, $source_data );
if ( ! empty( $result['errors'] ) ) { if ( ! empty( $result['errors'] ) ) {
foreach ( $result['errors'] as $error ) { foreach ( $result['errors'] as $error ) {

View File

@@ -28,6 +28,7 @@ class PRAutoBlogger_Publisher {
* @param PRAutoBlogger_Article_Idea $idea The original article idea. * @param PRAutoBlogger_Article_Idea $idea The original article idea.
* @param PRAutoBlogger_Editorial_Review $review The editor's review. * @param PRAutoBlogger_Editorial_Review $review The editor's review.
* @param string|null $run_id Pipeline run ID for log linking. * @param string|null $run_id Pipeline run ID for log linking.
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
* @return int The created post ID. * @return int The created post ID.
* @throws \RuntimeException If post creation fails. * @throws \RuntimeException If post creation fails.
*/ */
@@ -35,9 +36,10 @@ class PRAutoBlogger_Publisher {
string $content, string $content,
PRAutoBlogger_Article_Idea $idea, PRAutoBlogger_Article_Idea $idea,
PRAutoBlogger_Editorial_Review $review, PRAutoBlogger_Editorial_Review $review,
?string $run_id = null ?string $run_id = null,
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
): int { ): int {
return $this->create_post( $content, $idea, $review, 'publish', $run_id ); return $this->create_post( $content, $idea, $review, 'publish', $run_id, $cost_tracker );
} }
/** /**
@@ -47,6 +49,7 @@ class PRAutoBlogger_Publisher {
* @param PRAutoBlogger_Article_Idea $idea The original article idea. * @param PRAutoBlogger_Article_Idea $idea The original article idea.
* @param PRAutoBlogger_Editorial_Review $review The editor's review with rejection notes. * @param PRAutoBlogger_Editorial_Review $review The editor's review with rejection notes.
* @param string|null $run_id Pipeline run ID for log linking. * @param string|null $run_id Pipeline run ID for log linking.
* @param ?PRAutoBlogger_Cost_Tracker $cost_tracker Optional cost tracker for image generation cost logging.
* @return int The created post ID. * @return int The created post ID.
* @throws \RuntimeException If post creation fails. * @throws \RuntimeException If post creation fails.
*/ */
@@ -54,9 +57,10 @@ class PRAutoBlogger_Publisher {
string $content, string $content,
PRAutoBlogger_Article_Idea $idea, PRAutoBlogger_Article_Idea $idea,
PRAutoBlogger_Editorial_Review $review, PRAutoBlogger_Editorial_Review $review,
?string $run_id = null ?string $run_id = null,
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
): int { ): int {
return $this->create_post( $content, $idea, $review, 'draft', $run_id ); return $this->create_post( $content, $idea, $review, 'draft', $run_id, $cost_tracker );
} }
/** /**
@@ -75,7 +79,8 @@ class PRAutoBlogger_Publisher {
PRAutoBlogger_Article_Idea $idea, PRAutoBlogger_Article_Idea $idea,
PRAutoBlogger_Editorial_Review $review, PRAutoBlogger_Editorial_Review $review,
string $post_status, string $post_status,
?string $run_id = null ?string $run_id = null,
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
): int { ): int {
// Clean LLM artifacts, then inject peptide hyperlinks deterministically // Clean LLM artifacts, then inject peptide hyperlinks deterministically
// before the content enters WordPress. Peptide linker no-ops gracefully // before the content enters WordPress. Peptide linker no-ops gracefully
@@ -106,7 +111,7 @@ class PRAutoBlogger_Publisher {
PRAutoBlogger_Post_Assembler::link_generation_logs( $post_id, $run_id ); PRAutoBlogger_Post_Assembler::link_generation_logs( $post_id, $run_id );
if ( 'publish' === $post_status ) { if ( 'publish' === $post_status ) {
PRAutoBlogger_Post_Assembler::attach_generated_images( $post_id, $idea, $post_data ); PRAutoBlogger_Post_Assembler::attach_generated_images( $post_id, $idea, $post_data, $cost_tracker );
} }
PRAutoBlogger_Logger::instance()->info( PRAutoBlogger_Logger::instance()->info(

View File

@@ -9,7 +9,7 @@
* Plugin Name: PRAutoBlogger * Plugin Name: PRAutoBlogger
* Plugin URI: https://peptiderepo.com/prautoblogger * Plugin URI: https://peptiderepo.com/prautoblogger
* Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics. * Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics.
* Version: 0.12.1 * Version: 0.12.2
* Requires at least: 6.0 * Requires at least: 6.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Author: PeptideRepo * Author: PeptideRepo
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
| Defined here so every file in the plugin can reference paths, versions, | Defined here so every file in the plugin can reference paths, versions,
| and limits without magic strings. | and limits without magic strings.
*/ */
define( 'PRAUTOBLOGGER_VERSION', '0.12.1' ); define( 'PRAUTOBLOGGER_VERSION', '0.12.2' );
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' ); define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ ); define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );