style(phpcs): autofix WordPress-Core violations via phpcbf
Run phpcbf --standard=WordPress-Core across includes/, prautoblogger.php, and uninstall.php. Fixed 1,362 formatting violations in 75 files: - Short array syntax: [] → array() - Line breaking for long function calls - Consistent indentation - Function call parameter alignment No logic changes; purely mechanical formatting.
This commit is contained in:
@@ -23,14 +23,14 @@ class PRAutoBlogger_Analysis_Prompts {
|
||||
* @return string System prompt text.
|
||||
*/
|
||||
public static function build_system_prompt( string $niche, string $performance_context, int $target_count = 6 ): string {
|
||||
$prompt = "You are a content strategist analyzing social media discussions to find article ideas for a blog";
|
||||
$prompt = 'You are a content strategist analyzing social media discussions to find article ideas for a blog';
|
||||
if ( '' !== $niche ) {
|
||||
$prompt .= " in the {$niche} niche";
|
||||
}
|
||||
$prompt .= ".\n\n";
|
||||
|
||||
$prompt .= "IMPORTANT: You must identify exactly {$target_count} DISTINCT article ideas. ";
|
||||
$prompt .= "Each idea must cover a substantially different topic — no two ideas should overlap ";
|
||||
$prompt .= 'Each idea must cover a substantially different topic — no two ideas should overlap ';
|
||||
$prompt .= "in their main subject. Aim for diversity across these categories:\n";
|
||||
$prompt .= "1. QUESTIONS: Recurring questions people ask (\"How do I...\", \"What is...\", \"Is it safe to...\")\n";
|
||||
$prompt .= "2. COMPLAINTS: Pain points, frustrations, or problems people report\n";
|
||||
@@ -39,7 +39,7 @@ class PRAutoBlogger_Analysis_Prompts {
|
||||
$prompt .= "5. GUIDES: How-to topics, best practices, or educational content people need\n\n";
|
||||
|
||||
$prompt .= "Spread your {$target_count} ideas across multiple categories. ";
|
||||
$prompt .= "Be specific — \"peptide dosing for BPC-157\" is better than \"peptide information\". ";
|
||||
$prompt .= 'Be specific — "peptide dosing for BPC-157" is better than "peptide information". ';
|
||||
$prompt .= "Each idea should be narrow enough to be a single focused blog post.\n\n";
|
||||
|
||||
$prompt .= "For each idea, provide:\n";
|
||||
@@ -81,7 +81,7 @@ class PRAutoBlogger_Analysis_Prompts {
|
||||
* @return string User prompt text.
|
||||
*/
|
||||
public static function build_user_prompt( string $summary, int $target_count = 6 ): string {
|
||||
return "Here are the recent social media posts and comments to analyze. "
|
||||
return 'Here are the recent social media posts and comments to analyze. '
|
||||
. "Find {$target_count} distinct, diverse article ideas from this data:\n\n"
|
||||
. $summary;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ class PRAutoBlogger_Analysis_Prompts {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lines = [ 'Top performing past articles (learn from these):' ];
|
||||
$lines = array( 'Top performing past articles (learn from these):' );
|
||||
foreach ( $top_posts as $post ) {
|
||||
$lines[] = sprintf(
|
||||
'- "%s" (score: %.1f)',
|
||||
@@ -136,27 +136,29 @@ class PRAutoBlogger_Analysis_Prompts {
|
||||
* @return string Formatted context block, or empty string if no recent articles.
|
||||
*/
|
||||
public static function get_recent_articles_context(): string {
|
||||
$query = new \WP_Query( [
|
||||
'post_type' => 'post',
|
||||
'post_status' => [ 'publish', 'draft', 'pending' ],
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'date_query' => [ [ 'after' => '30 days ago' ] ],
|
||||
'posts_per_page' => 100,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
] );
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => array( 'publish', 'draft', 'pending' ),
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'date_query' => array( array( 'after' => '30 days ago' ) ),
|
||||
'posts_per_page' => 100,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $query->posts ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$titles = [];
|
||||
$titles = array();
|
||||
foreach ( $query->posts as $post_id ) {
|
||||
$titles[] = '- "' . get_the_title( $post_id ) . '"';
|
||||
}
|
||||
|
||||
$block = "IMPORTANT — Topics already covered (last 30 days). Do NOT suggest topics that overlap with these. ";
|
||||
$block = 'IMPORTANT — Topics already covered (last 30 days). Do NOT suggest topics that overlap with these. ';
|
||||
$block .= "Find genuinely NEW angles, questions, or subjects that are NOT already covered:\n";
|
||||
$block .= implode( "\n", $titles );
|
||||
|
||||
|
||||
@@ -47,20 +47,20 @@ class PRAutoBlogger_Article_Worker {
|
||||
|
||||
$auto_publish = in_array(
|
||||
get_option( 'prautoblogger_auto_publish', '0' ),
|
||||
[ '1', 'yes' ],
|
||||
array( '1', 'yes' ),
|
||||
true
|
||||
);
|
||||
|
||||
$result = [
|
||||
$result = array(
|
||||
'generated' => 0,
|
||||
'published' => 0,
|
||||
'rejected' => 0,
|
||||
'cost' => 0.0,
|
||||
];
|
||||
);
|
||||
|
||||
try {
|
||||
$this->broadcast_stage( __( 'Generating article draft via AI…', 'prautoblogger' ) );
|
||||
$content = $generator->generate( $idea );
|
||||
$content = $generator->generate( $idea );
|
||||
$result['generated'] = 1;
|
||||
|
||||
$this->broadcast_stage( __( 'Running editorial pass…', 'prautoblogger' ) );
|
||||
@@ -68,7 +68,12 @@ class PRAutoBlogger_Article_Worker {
|
||||
|
||||
$this->broadcast_stage( __( 'Saving and publishing…', 'prautoblogger' ) );
|
||||
$this->publish_or_draft(
|
||||
$content, $idea, $review, $publisher, $auto_publish, $result
|
||||
$content,
|
||||
$idea,
|
||||
$review,
|
||||
$publisher,
|
||||
$auto_publish,
|
||||
$result
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
PRAutoBlogger_Logger::instance()->error(
|
||||
@@ -114,13 +119,13 @@ class PRAutoBlogger_Article_Worker {
|
||||
|
||||
if ( $auto_publish ) {
|
||||
$publisher->publish( $final, $idea, $review, $run_id );
|
||||
$result['published']++;
|
||||
++$result['published'];
|
||||
} else {
|
||||
$publisher->save_as_draft( $final, $idea, $review, $run_id );
|
||||
}
|
||||
} else {
|
||||
$publisher->save_as_draft( $content, $idea, $review, $run_id );
|
||||
$result['rejected']++;
|
||||
++$result['rejected'];
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
'Article rejected by editor: ' . $idea->get_topic(),
|
||||
'pipeline'
|
||||
|
||||
@@ -51,16 +51,22 @@ class PRAutoBlogger_Chief_Editor {
|
||||
$user_prompt = $this->build_review_prompt( $content, $idea );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system_prompt ],
|
||||
[ 'role' => 'user', 'content' => $user_prompt ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system_prompt,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => $user_prompt,
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[
|
||||
array(
|
||||
'temperature' => 0.3,
|
||||
'max_tokens' => 5000,
|
||||
'response_format' => [ 'type' => 'json_object' ],
|
||||
]
|
||||
'response_format' => array( 'type' => 'json_object' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
@@ -81,7 +87,7 @@ class PRAutoBlogger_Chief_Editor {
|
||||
* @return string
|
||||
*/
|
||||
private function build_system_prompt( string $niche ): string {
|
||||
$prompt = "You are a senior blog editor";
|
||||
$prompt = 'You are a senior blog editor';
|
||||
if ( '' !== $niche ) {
|
||||
$prompt .= " specializing in {$niche} content";
|
||||
}
|
||||
@@ -161,27 +167,31 @@ class PRAutoBlogger_Chief_Editor {
|
||||
. mb_substr( $content, 0, 500 ),
|
||||
'editor'
|
||||
);
|
||||
return new PRAutoBlogger_Editorial_Review( [
|
||||
'verdict' => 'rejected',
|
||||
'notes' => 'Editor response could not be parsed.',
|
||||
'quality_score' => 0.0,
|
||||
'seo_score' => 0.0,
|
||||
'issues' => [ 'Unparseable editor response' ],
|
||||
] );
|
||||
return new PRAutoBlogger_Editorial_Review(
|
||||
array(
|
||||
'verdict' => 'rejected',
|
||||
'notes' => 'Editor response could not be parsed.',
|
||||
'quality_score' => 0.0,
|
||||
'seo_score' => 0.0,
|
||||
'issues' => array( 'Unparseable editor response' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$verdict = sanitize_text_field( $data['verdict'] ?? 'rejected' );
|
||||
if ( ! in_array( $verdict, [ 'approved', 'revised', 'rejected' ], true ) ) {
|
||||
if ( ! in_array( $verdict, array( 'approved', 'revised', 'rejected' ), true ) ) {
|
||||
$verdict = 'rejected';
|
||||
}
|
||||
|
||||
return new PRAutoBlogger_Editorial_Review( [
|
||||
'verdict' => $verdict,
|
||||
'notes' => sanitize_textarea_field( $data['notes'] ?? '' ),
|
||||
'revised_content' => isset( $data['revised_content'] ) ? wp_kses_post( $data['revised_content'] ) : null,
|
||||
'quality_score' => (float) ( $data['quality_score'] ?? 0.0 ),
|
||||
'seo_score' => (float) ( $data['seo_score'] ?? 0.0 ),
|
||||
'issues' => array_map( 'sanitize_text_field', $data['issues'] ?? [] ),
|
||||
] );
|
||||
return new PRAutoBlogger_Editorial_Review(
|
||||
array(
|
||||
'verdict' => $verdict,
|
||||
'notes' => sanitize_textarea_field( $data['notes'] ?? '' ),
|
||||
'revised_content' => isset( $data['revised_content'] ) ? wp_kses_post( $data['revised_content'] ) : null,
|
||||
'quality_score' => (float) ( $data['quality_score'] ?? 0.0 ),
|
||||
'seo_score' => (float) ( $data['seo_score'] ?? 0.0 ),
|
||||
'issues' => array_map( 'sanitize_text_field', $data['issues'] ?? array() ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
|
||||
if ( empty( $source_items ) ) {
|
||||
PRAutoBlogger_Logger::instance()->info( 'No new source data to analyze.', 'analyzer' );
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$niche = get_option( 'prautoblogger_niche_description', '' );
|
||||
@@ -61,16 +61,22 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
$user_prompt = PRAutoBlogger_Analysis_Prompts::build_user_prompt( $summary, $target_idea_count );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system_prompt ],
|
||||
[ 'role' => 'user', 'content' => $user_prompt ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system_prompt,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => $user_prompt,
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[
|
||||
array(
|
||||
'temperature' => 0.5,
|
||||
'max_tokens' => 8000,
|
||||
'response_format' => [ 'type' => 'json_object' ],
|
||||
]
|
||||
'response_format' => array( 'type' => 'json_object' ),
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
@@ -130,7 +136,7 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS )
|
||||
),
|
||||
ARRAY_A
|
||||
) ?: [];
|
||||
) ?: array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,8 +148,8 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
* @return string Formatted text summary.
|
||||
*/
|
||||
private function build_source_summary( array $items ): string {
|
||||
$parts = [];
|
||||
$char_limit = 30000; // Roughly ~7500 tokens.
|
||||
$parts = array();
|
||||
$char_limit = 30000; // Roughly ~7500 tokens.
|
||||
$current_chars = 0;
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
@@ -163,7 +169,7 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
break;
|
||||
}
|
||||
|
||||
$parts[] = $entry;
|
||||
$parts[] = $entry;
|
||||
$current_chars += strlen( $entry );
|
||||
}
|
||||
|
||||
@@ -185,27 +191,29 @@ class PRAutoBlogger_Content_Analyzer {
|
||||
. mb_substr( $content, 0, 500 ),
|
||||
'analyzer'
|
||||
);
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$source_ids = array_column( $source_items, 'id' );
|
||||
$results = [];
|
||||
$results = array();
|
||||
|
||||
foreach ( $data['patterns'] as $pattern ) {
|
||||
$results[] = new PRAutoBlogger_Analysis_Result( [
|
||||
'analysis_type' => sanitize_text_field( $pattern['type'] ?? 'question' ),
|
||||
'topic' => sanitize_text_field( $pattern['topic'] ?? '' ),
|
||||
'summary' => sanitize_textarea_field( $pattern['summary'] ?? '' ),
|
||||
'frequency' => absint( $pattern['frequency'] ?? 1 ),
|
||||
'relevance_score' => (float) ( $pattern['relevance_score'] ?? 0.0 ),
|
||||
'source_ids' => $source_ids,
|
||||
'analyzed_at' => current_time( 'mysql' ),
|
||||
'metadata' => [
|
||||
'suggested_title' => $pattern['suggested_title'] ?? '',
|
||||
'key_points' => $pattern['key_points'] ?? [],
|
||||
'target_keywords' => $pattern['target_keywords'] ?? [],
|
||||
],
|
||||
] );
|
||||
$results[] = new PRAutoBlogger_Analysis_Result(
|
||||
array(
|
||||
'analysis_type' => sanitize_text_field( $pattern['type'] ?? 'question' ),
|
||||
'topic' => sanitize_text_field( $pattern['topic'] ?? '' ),
|
||||
'summary' => sanitize_textarea_field( $pattern['summary'] ?? '' ),
|
||||
'frequency' => absint( $pattern['frequency'] ?? 1 ),
|
||||
'relevance_score' => (float) ( $pattern['relevance_score'] ?? 0.0 ),
|
||||
'source_ids' => $source_ids,
|
||||
'analyzed_at' => current_time( 'mysql' ),
|
||||
'metadata' => array(
|
||||
'suggested_title' => $pattern['suggested_title'] ?? '',
|
||||
'key_points' => $pattern['key_points'] ?? array(),
|
||||
'target_keywords' => $pattern['target_keywords'] ?? array(),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
@@ -49,7 +49,7 @@ class PRAutoBlogger_Content_Generator {
|
||||
absint( get_option( 'prautoblogger_min_word_count', 800 ) ),
|
||||
absint( get_option( 'prautoblogger_max_word_count', 2000 ) ),
|
||||
get_option( 'prautoblogger_niche_description', '' ),
|
||||
json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: [],
|
||||
json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: array(),
|
||||
get_option( 'prautoblogger_writing_instructions', '' )
|
||||
);
|
||||
|
||||
@@ -70,17 +70,30 @@ class PRAutoBlogger_Content_Generator {
|
||||
$model = get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => PRAutoBlogger_Content_Prompts::build_system( $request ) ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_single_pass( $request ) ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => PRAutoBlogger_Content_Prompts::build_system( $request ),
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => PRAutoBlogger_Content_Prompts::build_single_pass( $request ),
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[ 'temperature' => 0.7, 'max_tokens' => 4000 ]
|
||||
array(
|
||||
'temperature' => 0.7,
|
||||
'max_tokens' => 4000,
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
null, 'draft', $this->llm->get_provider_name(),
|
||||
$response['model'], $response['prompt_tokens'], $response['completion_tokens']
|
||||
null,
|
||||
'draft',
|
||||
$this->llm->get_provider_name(),
|
||||
$response['model'],
|
||||
$response['prompt_tokens'],
|
||||
$response['completion_tokens']
|
||||
);
|
||||
|
||||
return $response['content'];
|
||||
@@ -102,17 +115,30 @@ class PRAutoBlogger_Content_Generator {
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_outline( $request ) ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => PRAutoBlogger_Content_Prompts::build_outline( $request ),
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[ 'temperature' => 0.5, 'max_tokens' => 1500 ]
|
||||
array(
|
||||
'temperature' => 0.5,
|
||||
'max_tokens' => 1500,
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
null, 'outline', $this->llm->get_provider_name(),
|
||||
$response['model'], $response['prompt_tokens'], $response['completion_tokens']
|
||||
null,
|
||||
'outline',
|
||||
$this->llm->get_provider_name(),
|
||||
$response['model'],
|
||||
$response['prompt_tokens'],
|
||||
$response['completion_tokens']
|
||||
);
|
||||
|
||||
return $response['content'];
|
||||
@@ -123,17 +149,30 @@ class PRAutoBlogger_Content_Generator {
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_draft( $request, $outline ) ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => PRAutoBlogger_Content_Prompts::build_draft( $request, $outline ),
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[ 'temperature' => 0.7, 'max_tokens' => 4000 ]
|
||||
array(
|
||||
'temperature' => 0.7,
|
||||
'max_tokens' => 4000,
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
null, 'draft', $this->llm->get_provider_name(),
|
||||
$response['model'], $response['prompt_tokens'], $response['completion_tokens']
|
||||
null,
|
||||
'draft',
|
||||
$this->llm->get_provider_name(),
|
||||
$response['model'],
|
||||
$response['prompt_tokens'],
|
||||
$response['completion_tokens']
|
||||
);
|
||||
|
||||
return $response['content'];
|
||||
@@ -144,17 +183,30 @@ class PRAutoBlogger_Content_Generator {
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_polish( $draft ) ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => PRAutoBlogger_Content_Prompts::build_polish( $draft ),
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[ 'temperature' => 0.4, 'max_tokens' => 4000 ]
|
||||
array(
|
||||
'temperature' => 0.4,
|
||||
'max_tokens' => 4000,
|
||||
)
|
||||
);
|
||||
|
||||
$this->cost_tracker->log_api_call(
|
||||
null, 'polish', $this->llm->get_provider_name(),
|
||||
$response['model'], $response['prompt_tokens'], $response['completion_tokens']
|
||||
null,
|
||||
'polish',
|
||||
$this->llm->get_provider_name(),
|
||||
$response['model'],
|
||||
$response['prompt_tokens'],
|
||||
$response['completion_tokens']
|
||||
);
|
||||
|
||||
return $response['content'];
|
||||
|
||||
@@ -68,7 +68,7 @@ class PRAutoBlogger_Content_Prompts {
|
||||
"- Engaging intro, strong conclusion with CTA\n" .
|
||||
"- Do NOT include the title or <html>/<body> tags\n" .
|
||||
"- Output HTML only, no markdown or commentary\n" .
|
||||
"- Follow EVERY formatting and structural requirement from your system prompt style guide",
|
||||
'- Follow EVERY formatting and structural requirement from your system prompt style guide',
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
@@ -92,10 +92,10 @@ class PRAutoBlogger_Content_Prompts {
|
||||
"Create a detailed outline for a blog post titled: \"%s\"\n\n" .
|
||||
"Topic: %s\nArticle type: %s\n\nKey points to cover:\n%s\n\n" .
|
||||
"Target keywords: %s\n\n" .
|
||||
"The outline should have 4-6 main sections with bullet points under each. " .
|
||||
"Include an introduction hook and a conclusion with a call to action. " .
|
||||
'The outline should have 4-6 main sections with bullet points under each. ' .
|
||||
'Include an introduction hook and a conclusion with a call to action. ' .
|
||||
"Word count target: %d-%d words.\n\n" .
|
||||
"Plan the structure to satisfy EVERY requirement in your system prompt style guide.",
|
||||
'Plan the structure to satisfy EVERY requirement in your system prompt style guide.',
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
@@ -125,7 +125,7 @@ class PRAutoBlogger_Content_Prompts {
|
||||
"- Naturally incorporate these keywords: %s\n" .
|
||||
"- Do NOT include the title in the HTML (it will be set separately)\n" .
|
||||
"- Do NOT wrap in <html>, <head>, or <body> tags — just the article content\n" .
|
||||
"- Follow EVERY formatting and structural requirement from your system prompt style guide",
|
||||
'- Follow EVERY formatting and structural requirement from your system prompt style guide',
|
||||
$outline,
|
||||
$request->get_tone(),
|
||||
$request->get_min_word_count(),
|
||||
@@ -147,9 +147,9 @@ class PRAutoBlogger_Content_Prompts {
|
||||
"3. Engagement (hooks, transitions, call-to-action)\n" .
|
||||
"4. Accuracy and clarity\n" .
|
||||
"5. Remove any filler or redundant sentences\n\n" .
|
||||
"IMPORTANT: Preserve all bullet points, numbered lists, hyperlinks, and " .
|
||||
"structural elements from the draft. Do NOT flatten lists into prose or " .
|
||||
"remove links. Ensure every requirement from your system prompt style " .
|
||||
'IMPORTANT: Preserve all bullet points, numbered lists, hyperlinks, and ' .
|
||||
'structural elements from the draft. Do NOT flatten lists into prose or ' .
|
||||
'remove links. Ensure every requirement from your system prompt style ' .
|
||||
"guide is satisfied in the final output.\n\n" .
|
||||
"Return the polished HTML content only. Do not add commentary.\n\n" .
|
||||
"DRAFT:\n" . $draft;
|
||||
@@ -172,7 +172,7 @@ class PRAutoBlogger_Content_Prompts {
|
||||
|
||||
$rules .= self::build_article_links();
|
||||
|
||||
$rules .= "--- END LINKING RULES ---";
|
||||
$rules .= '--- END LINKING RULES ---';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
@@ -183,20 +183,22 @@ class PRAutoBlogger_Content_Prompts {
|
||||
* @return string Formatted list, or a fallback note.
|
||||
*/
|
||||
private static function build_article_links(): string {
|
||||
$posts = get_posts( [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 30,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'fields' => 'ids',
|
||||
] );
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 30,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $posts ) ) {
|
||||
return "No published articles available for internal linking yet.\n";
|
||||
}
|
||||
|
||||
$lines = [ "Available article links (use where topically relevant):\n" ];
|
||||
$lines = array( "Available article links (use where topically relevant):\n" );
|
||||
foreach ( $posts as $post_id ) {
|
||||
$title = get_the_title( $post_id );
|
||||
$url = get_permalink( $post_id );
|
||||
@@ -207,5 +209,4 @@ class PRAutoBlogger_Content_Prompts {
|
||||
|
||||
return implode( "\n", $lines ) . "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
public function get_daily_spend( int $days = 30 ): array {
|
||||
global $wpdb;
|
||||
if ( null === $wpdb ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$table = $wpdb->prefix . 'prautoblogger_generation_log';
|
||||
@@ -74,8 +74,8 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$daily = [];
|
||||
foreach ( ( $results ?: [] ) as $row ) {
|
||||
$daily = array();
|
||||
foreach ( ( $results ?: array() ) as $row ) {
|
||||
$daily[ $row['day'] ] = (float) $row['total_cost'];
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
public function get_spend_by_stage( string $start_date, string $end_date ): array {
|
||||
global $wpdb;
|
||||
if ( null === $wpdb ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$table = $wpdb->prefix . 'prautoblogger_generation_log';
|
||||
@@ -114,13 +114,13 @@ class PRAutoBlogger_Cost_Reporter {
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$breakdown = [];
|
||||
foreach ( ( $results ?: [] ) as $row ) {
|
||||
$breakdown[ $row['stage'] ] = [
|
||||
$breakdown = array();
|
||||
foreach ( ( $results ?: array() ) as $row ) {
|
||||
$breakdown[ $row['stage'] ] = array(
|
||||
'cost' => (float) $row['total_cost'],
|
||||
'tokens' => (int) $row['total_tokens'],
|
||||
'calls' => (int) $row['call_count'],
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return $breakdown;
|
||||
|
||||
@@ -87,19 +87,21 @@ class PRAutoBlogger_Cost_Tracker {
|
||||
|
||||
$this->current_run_cost += $cost;
|
||||
|
||||
$log_entry = new PRAutoBlogger_Generation_Log( [
|
||||
'post_id' => $post_id,
|
||||
'run_id' => $this->run_id,
|
||||
'stage' => $stage,
|
||||
'provider' => $provider,
|
||||
'model' => $model,
|
||||
'prompt_tokens' => $prompt_tokens,
|
||||
'completion_tokens' => $completion_tokens,
|
||||
'estimated_cost' => $cost,
|
||||
'response_status' => $response_status,
|
||||
'error_message' => $error_message,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
] );
|
||||
$log_entry = new PRAutoBlogger_Generation_Log(
|
||||
array(
|
||||
'post_id' => $post_id,
|
||||
'run_id' => $this->run_id,
|
||||
'stage' => $stage,
|
||||
'provider' => $provider,
|
||||
'model' => $model,
|
||||
'prompt_tokens' => $prompt_tokens,
|
||||
'completion_tokens' => $completion_tokens,
|
||||
'estimated_cost' => $cost,
|
||||
'response_status' => $response_status,
|
||||
'error_message' => $error_message,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
)
|
||||
);
|
||||
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'prautoblogger_generation_log';
|
||||
@@ -158,19 +160,22 @@ class PRAutoBlogger_Cost_Tracker {
|
||||
$table = $wpdb->prefix . 'prautoblogger_generation_log';
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->insert( $table, [
|
||||
'post_id' => $post_id,
|
||||
'run_id' => $this->run_id,
|
||||
'stage' => $stage,
|
||||
'provider' => 'cloudflare-workers-ai',
|
||||
'model' => $model,
|
||||
'prompt_tokens' => 0,
|
||||
'completion_tokens' => 0,
|
||||
'estimated_cost' => $cost_usd,
|
||||
'response_status' => 'success',
|
||||
'error_message' => '',
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
] );
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'post_id' => $post_id,
|
||||
'run_id' => $this->run_id,
|
||||
'stage' => $stage,
|
||||
'provider' => 'cloudflare-workers-ai',
|
||||
'model' => $model,
|
||||
'prompt_tokens' => 0,
|
||||
'completion_tokens' => 0,
|
||||
'estimated_cost' => $cost_usd,
|
||||
'response_status' => 'success',
|
||||
'error_message' => '',
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,32 +29,32 @@ class PRAutoBlogger_GA4_Client {
|
||||
public function fetch_data( array $posts ): array {
|
||||
$property_id = get_option( 'prautoblogger_ga4_property_id', '' );
|
||||
if ( '' === $property_id ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$credentials_encrypted = get_option( 'prautoblogger_ga4_credentials_json', '' );
|
||||
if ( '' === $credentials_encrypted ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$credentials = PRAutoBlogger_Encryption::decrypt( $credentials_encrypted );
|
||||
if ( '' === $credentials ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
// Build page paths for the GA4 query.
|
||||
$page_paths = [];
|
||||
$path_to_id = [];
|
||||
$page_paths = array();
|
||||
$path_to_id = array();
|
||||
foreach ( $posts as $post ) {
|
||||
$path = wp_parse_url( get_permalink( $post->ID ), PHP_URL_PATH );
|
||||
if ( $path ) {
|
||||
$page_paths[] = $path;
|
||||
$page_paths[] = $path;
|
||||
$path_to_id[ $path ] = (int) $post->ID;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $page_paths ) ) {
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
// GA4 Data API request.
|
||||
@@ -62,40 +62,45 @@ class PRAutoBlogger_GA4_Client {
|
||||
$access_token = $this->get_access_token( $credentials );
|
||||
if ( '' === $access_token ) {
|
||||
PRAutoBlogger_Logger::instance()->error( 'GA4: Failed to get access token. Possible causes: invalid service account JSON, expired or revoked private key, or network error reaching oauth2.googleapis.com. Verify credentials in PRAutoBlogger → Settings.', 'ga4' );
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$request_body = [
|
||||
'dateRanges' => [ [ 'startDate' => '30daysAgo', 'endDate' => 'today' ] ],
|
||||
'dimensions' => [ [ 'name' => 'pagePath' ] ],
|
||||
'metrics' => [
|
||||
[ 'name' => 'screenPageViews' ],
|
||||
[ 'name' => 'averageSessionDuration' ],
|
||||
[ 'name' => 'bounceRate' ],
|
||||
],
|
||||
'dimensionFilter' => [
|
||||
'filter' => [
|
||||
$request_body = array(
|
||||
'dateRanges' => array(
|
||||
array(
|
||||
'startDate' => '30daysAgo',
|
||||
'endDate' => 'today',
|
||||
),
|
||||
),
|
||||
'dimensions' => array( array( 'name' => 'pagePath' ) ),
|
||||
'metrics' => array(
|
||||
array( 'name' => 'screenPageViews' ),
|
||||
array( 'name' => 'averageSessionDuration' ),
|
||||
array( 'name' => 'bounceRate' ),
|
||||
),
|
||||
'dimensionFilter' => array(
|
||||
'filter' => array(
|
||||
'fieldName' => 'pagePath',
|
||||
'inListFilter' => [ 'values' => $page_paths ],
|
||||
],
|
||||
],
|
||||
];
|
||||
'inListFilter' => array( 'values' => $page_paths ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$response = wp_remote_post(
|
||||
"https://analyticsdata.googleapis.com/v1beta/properties/{$property_id}:runReport",
|
||||
[
|
||||
array(
|
||||
'timeout' => 30,
|
||||
'headers' => [
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
),
|
||||
'body' => wp_json_encode( $request_body ),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
PRAutoBlogger_Logger::instance()->error( 'GA4 API request failed (WP HTTP error): ' . $response->get_error_message(), 'ga4' );
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$ga4_status = wp_remote_retrieve_response_code( $response );
|
||||
@@ -105,20 +110,20 @@ class PRAutoBlogger_GA4_Client {
|
||||
sprintf( 'GA4 API request failed (HTTP %d): %s', $ga4_status, substr( $ga4_body, 0, 500 ) ),
|
||||
'ga4'
|
||||
);
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
|
||||
$data = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$results = [];
|
||||
$results = array();
|
||||
|
||||
foreach ( ( $data['rows'] ?? [] ) as $row ) {
|
||||
foreach ( ( $data['rows'] ?? array() ) as $row ) {
|
||||
$path = $row['dimensionValues'][0]['value'] ?? '';
|
||||
if ( isset( $path_to_id[ $path ] ) ) {
|
||||
$results[ $path_to_id[ $path ] ] = [
|
||||
$results[ $path_to_id[ $path ] ] = array(
|
||||
'pageviews' => (int) ( $row['metricValues'][0]['value'] ?? 0 ),
|
||||
'avg_time_on_page' => (float) ( $row['metricValues'][1]['value'] ?? 0 ),
|
||||
'bounce_rate' => (float) ( $row['metricValues'][2]['value'] ?? 0 ),
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,19 +145,26 @@ class PRAutoBlogger_GA4_Client {
|
||||
|
||||
// Build JWT for service account auth.
|
||||
$now = time();
|
||||
$header = wp_json_encode( [ 'alg' => 'RS256', 'typ' => 'JWT' ] );
|
||||
$claim = wp_json_encode( [
|
||||
'iss' => $creds['client_email'],
|
||||
'scope' => 'https://www.googleapis.com/auth/analytics.readonly',
|
||||
'aud' => 'https://oauth2.googleapis.com/token',
|
||||
'exp' => $now + 3600,
|
||||
'iat' => $now,
|
||||
] );
|
||||
$header = wp_json_encode(
|
||||
array(
|
||||
'alg' => 'RS256',
|
||||
'typ' => 'JWT',
|
||||
)
|
||||
);
|
||||
$claim = wp_json_encode(
|
||||
array(
|
||||
'iss' => $creds['client_email'],
|
||||
'scope' => 'https://www.googleapis.com/auth/analytics.readonly',
|
||||
'aud' => 'https://oauth2.googleapis.com/token',
|
||||
'exp' => $now + 3600,
|
||||
'iat' => $now,
|
||||
)
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
$base64_header = rtrim( strtr( base64_encode( $header ), '+/', '-_' ), '=' );
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
$base64_claim = rtrim( strtr( base64_encode( $claim ), '+/', '-_' ), '=' );
|
||||
$base64_claim = rtrim( strtr( base64_encode( $claim ), '+/', '-_' ), '=' );
|
||||
|
||||
$signing_input = $base64_header . '.' . $base64_claim;
|
||||
$signature = '';
|
||||
@@ -171,13 +183,13 @@ class PRAutoBlogger_GA4_Client {
|
||||
|
||||
$response = wp_remote_post(
|
||||
'https://oauth2.googleapis.com/token',
|
||||
[
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'body' => [
|
||||
'body' => array(
|
||||
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
'assertion' => $jwt,
|
||||
],
|
||||
]
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
|
||||
@@ -44,8 +44,8 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
* @return PRAutoBlogger_Article_Idea[] Top-ranked ideas, sorted by score desc.
|
||||
*/
|
||||
public function score_and_rank( array $analysis_results, int $target_count ): array {
|
||||
$exclusions = json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: [];
|
||||
$ideas = [];
|
||||
$exclusions = json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: array();
|
||||
$ideas = array();
|
||||
$excluded_count = 0;
|
||||
$deduped_count = 0;
|
||||
|
||||
@@ -55,11 +55,11 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
}
|
||||
|
||||
// Keyword sets kept for fallback (passed through to Semantic_Dedup).
|
||||
$accepted_keyword_sets = [];
|
||||
$accepted_keyword_sets = array();
|
||||
|
||||
foreach ( $analysis_results as $result ) {
|
||||
if ( $this->is_excluded( $result->get_topic(), $exclusions ) ) {
|
||||
$excluded_count++;
|
||||
++$excluded_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
$topic_keywords = $this->extract_meaningful_keywords( $topic );
|
||||
|
||||
if ( $dedup->is_batch_duplicate( $topic, $topic_keywords, $accepted_keyword_sets ) ) {
|
||||
$deduped_count++;
|
||||
++$deduped_count;
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
sprintf( 'Dedup(batch): skipping "%s"', substr( $topic, 0, 80 ) ),
|
||||
'scorer'
|
||||
@@ -77,7 +77,7 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
}
|
||||
|
||||
if ( $dedup->is_recent_duplicate( $topic, $topic_keywords ) ) {
|
||||
$deduped_count++;
|
||||
++$deduped_count;
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
sprintf( 'Dedup(recent): skipping "%s"', substr( $topic, 0, 80 ) ),
|
||||
'scorer'
|
||||
@@ -89,19 +89,21 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
$dedup->record_accepted( $topic );
|
||||
}
|
||||
|
||||
$metadata = $result->get_metadata() ?? [];
|
||||
$metadata = $result->get_metadata() ?? array();
|
||||
|
||||
$ideas[] = new PRAutoBlogger_Article_Idea( [
|
||||
'topic' => $result->get_topic(),
|
||||
'article_type' => $this->map_type( $result->get_analysis_type() ),
|
||||
'suggested_title' => $metadata['suggested_title'] ?? $result->get_topic(),
|
||||
'summary' => $result->get_summary() ?? '',
|
||||
'score' => $this->compute_score( $result ),
|
||||
'analysis_id' => $result->get_id(),
|
||||
'source_ids' => $result->get_source_ids(),
|
||||
'key_points' => $metadata['key_points'] ?? [],
|
||||
'target_keywords' => $metadata['target_keywords'] ?? [],
|
||||
] );
|
||||
$ideas[] = new PRAutoBlogger_Article_Idea(
|
||||
array(
|
||||
'topic' => $result->get_topic(),
|
||||
'article_type' => $this->map_type( $result->get_analysis_type() ),
|
||||
'suggested_title' => $metadata['suggested_title'] ?? $result->get_topic(),
|
||||
'summary' => $result->get_summary() ?? '',
|
||||
'score' => $this->compute_score( $result ),
|
||||
'analysis_id' => $result->get_id(),
|
||||
'source_ids' => $result->get_source_ids(),
|
||||
'key_points' => $metadata['key_points'] ?? array(),
|
||||
'target_keywords' => $metadata['target_keywords'] ?? array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
@@ -116,9 +118,12 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
'scorer'
|
||||
);
|
||||
|
||||
usort( $ideas, static function ( PRAutoBlogger_Article_Idea $a, PRAutoBlogger_Article_Idea $b ): int {
|
||||
return $b->get_score() <=> $a->get_score();
|
||||
} );
|
||||
usort(
|
||||
$ideas,
|
||||
static function ( PRAutoBlogger_Article_Idea $a, PRAutoBlogger_Article_Idea $b ): int {
|
||||
return $b->get_score() <=> $a->get_score();
|
||||
}
|
||||
);
|
||||
|
||||
return array_slice( $ideas, 0, $target_count );
|
||||
}
|
||||
@@ -165,27 +170,122 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
* @return string[] Unique lowercase keywords.
|
||||
*/
|
||||
private function extract_meaningful_keywords( string $text ): array {
|
||||
static $stopwords = [
|
||||
'a', 'an', 'the', 'and', 'or', 'but', 'is', 'are', 'was', 'were',
|
||||
'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'as',
|
||||
'it', 'its', 'this', 'that', 'your', 'you', 'how', 'what', 'when',
|
||||
'why', 'where', 'which', 'who', 'do', 'does', 'did', 'not', 'no',
|
||||
'can', 'will', 'should', 'could', 'would', 'may', 'might', 'have',
|
||||
'has', 'had', 'be', 'been', 'being', 'about', 'into', 'through',
|
||||
'during', 'before', 'after', 'above', 'below', 'between', 'same',
|
||||
'up', 'down', 'out', 'off', 'over', 'under', 'again', 'then',
|
||||
'here', 'there', 'all', 'each', 'every', 'both', 'few', 'more',
|
||||
'most', 'other', 'some', 'such', 'than', 'too', 'very', 'just',
|
||||
'also', 'only', 'own', 'so', 'if', 'while', 'because', 'until',
|
||||
'vs', 'versus', 'guide', 'complete', 'ultimate', 'best', 'top',
|
||||
'new', 'first', 'need', 'know', 'everything',
|
||||
];
|
||||
static $stopwords = array(
|
||||
'a',
|
||||
'an',
|
||||
'the',
|
||||
'and',
|
||||
'or',
|
||||
'but',
|
||||
'is',
|
||||
'are',
|
||||
'was',
|
||||
'were',
|
||||
'in',
|
||||
'on',
|
||||
'at',
|
||||
'to',
|
||||
'for',
|
||||
'of',
|
||||
'with',
|
||||
'by',
|
||||
'from',
|
||||
'as',
|
||||
'it',
|
||||
'its',
|
||||
'this',
|
||||
'that',
|
||||
'your',
|
||||
'you',
|
||||
'how',
|
||||
'what',
|
||||
'when',
|
||||
'why',
|
||||
'where',
|
||||
'which',
|
||||
'who',
|
||||
'do',
|
||||
'does',
|
||||
'did',
|
||||
'not',
|
||||
'no',
|
||||
'can',
|
||||
'will',
|
||||
'should',
|
||||
'could',
|
||||
'would',
|
||||
'may',
|
||||
'might',
|
||||
'have',
|
||||
'has',
|
||||
'had',
|
||||
'be',
|
||||
'been',
|
||||
'being',
|
||||
'about',
|
||||
'into',
|
||||
'through',
|
||||
'during',
|
||||
'before',
|
||||
'after',
|
||||
'above',
|
||||
'below',
|
||||
'between',
|
||||
'same',
|
||||
'up',
|
||||
'down',
|
||||
'out',
|
||||
'off',
|
||||
'over',
|
||||
'under',
|
||||
'again',
|
||||
'then',
|
||||
'here',
|
||||
'there',
|
||||
'all',
|
||||
'each',
|
||||
'every',
|
||||
'both',
|
||||
'few',
|
||||
'more',
|
||||
'most',
|
||||
'other',
|
||||
'some',
|
||||
'such',
|
||||
'than',
|
||||
'too',
|
||||
'very',
|
||||
'just',
|
||||
'also',
|
||||
'only',
|
||||
'own',
|
||||
'so',
|
||||
'if',
|
||||
'while',
|
||||
'because',
|
||||
'until',
|
||||
'vs',
|
||||
'versus',
|
||||
'guide',
|
||||
'complete',
|
||||
'ultimate',
|
||||
'best',
|
||||
'top',
|
||||
'new',
|
||||
'first',
|
||||
'need',
|
||||
'know',
|
||||
'everything',
|
||||
);
|
||||
|
||||
$text = strtolower( $text );
|
||||
$words = preg_split( '/[^a-z0-9-]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
|
||||
$words = array_filter( $words, static function ( string $w ) use ( $stopwords ): bool {
|
||||
return strlen( $w ) >= 2 && ! in_array( $w, $stopwords, true );
|
||||
} );
|
||||
$words = array_filter(
|
||||
$words,
|
||||
static function ( string $w ) use ( $stopwords ): bool {
|
||||
return strlen( $w ) >= 2 && ! in_array( $w, $stopwords, true );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( array_unique( $words ) );
|
||||
}
|
||||
@@ -197,13 +297,13 @@ class PRAutoBlogger_Idea_Scorer {
|
||||
* @return string Article type label.
|
||||
*/
|
||||
private function map_type( string $analysis_type ): string {
|
||||
$map = [
|
||||
$map = array(
|
||||
'question' => 'guide',
|
||||
'complaint' => 'solution',
|
||||
'comparison' => 'comparison',
|
||||
'news' => 'news',
|
||||
'guide' => 'guide',
|
||||
];
|
||||
);
|
||||
return $map[ $analysis_type ] ?? 'article';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ class PRAutoBlogger_Image_Media_Sideloader {
|
||||
}
|
||||
|
||||
// Prepare file array for media_handle_sideload().
|
||||
$file_array = [
|
||||
$file_array = array(
|
||||
'name' => $this->generate_filename( $image_data['model'], $image_data['width'], $image_data['height'] ),
|
||||
'tmp_name' => $temp_file,
|
||||
];
|
||||
);
|
||||
|
||||
// Import the file into media library.
|
||||
$attachment_id = media_handle_sideload( $file_array, $post_id );
|
||||
@@ -108,8 +108,8 @@ class PRAutoBlogger_Image_Media_Sideloader {
|
||||
* @return string|\WP_Error Path to temporary file on success, WP_Error on failure.
|
||||
*/
|
||||
private function create_temp_file( string $bytes, string $mime_type ) {
|
||||
$temp_dir = get_temp_dir();
|
||||
$filename = 'prautoblogger_img_' . wp_generate_uuid4() . $this->get_extension_for_mime( $mime_type );
|
||||
$temp_dir = get_temp_dir();
|
||||
$filename = 'prautoblogger_img_' . wp_generate_uuid4() . $this->get_extension_for_mime( $mime_type );
|
||||
$temp_path = $temp_dir . $filename;
|
||||
|
||||
$written = file_put_contents( $temp_path, $bytes );
|
||||
@@ -156,11 +156,11 @@ class PRAutoBlogger_Image_Media_Sideloader {
|
||||
* @return string File extension with leading dot (e.g., '.png').
|
||||
*/
|
||||
private function get_extension_for_mime( string $mime_type ): string {
|
||||
$map = [
|
||||
$map = array(
|
||||
'image/png' => '.png',
|
||||
'image/jpeg' => '.jpg',
|
||||
'image/jpg' => '.jpg',
|
||||
];
|
||||
);
|
||||
|
||||
return $map[ strtolower( $mime_type ) ] ?? '.png';
|
||||
}
|
||||
|
||||
@@ -106,15 +106,15 @@ class PRAutoBlogger_Image_NSFW_Retry {
|
||||
'image_pipeline'
|
||||
);
|
||||
|
||||
$retry_request = [
|
||||
$retry_request = array(
|
||||
'prompt' => $fallback['prompt'],
|
||||
'width' => (int) ( $batch_requests[ $key ]['width'] ?? 0 ),
|
||||
'width' => (int) ( $batch_requests[ $key ]['width'] ?? 0 ),
|
||||
'height' => (int) ( $batch_requests[ $key ]['height'] ?? 0 ),
|
||||
'options' => $batch_requests[ $key ]['options'] ?? [],
|
||||
];
|
||||
'options' => $batch_requests[ $key ]['options'] ?? array(),
|
||||
);
|
||||
|
||||
$retry_results = $this->provider->generate_image_batch( [ $key => $retry_request ] );
|
||||
$retry_entry = $retry_results[ $key ] ?? [ 'error' => 'Retry provider returned no result.' ];
|
||||
$retry_results = $this->provider->generate_image_batch( array( $key => $retry_request ) );
|
||||
$retry_entry = $retry_results[ $key ] ?? array( 'error' => 'Retry provider returned no result.' );
|
||||
|
||||
if ( isset( $retry_entry['error'] ) ) {
|
||||
PRAutoBlogger_Logger::instance()->warning(
|
||||
|
||||
@@ -47,10 +47,10 @@ class PRAutoBlogger_Image_Pipeline {
|
||||
?PRAutoBlogger_Image_Provider_Interface $provider = null,
|
||||
?PRAutoBlogger_Cost_Tracker $cost_tracker = null
|
||||
) {
|
||||
$this->provider = $provider ?? self::create_default_provider();
|
||||
$this->prompt_builder = new PRAutoBlogger_Image_Prompt_Builder();
|
||||
$this->sideloader = new PRAutoBlogger_Image_Media_Sideloader();
|
||||
$this->cost_tracker = $cost_tracker ?? new PRAutoBlogger_Cost_Tracker();
|
||||
$this->provider = $provider ?? self::create_default_provider();
|
||||
$this->prompt_builder = new PRAutoBlogger_Image_Prompt_Builder();
|
||||
$this->sideloader = new PRAutoBlogger_Image_Media_Sideloader();
|
||||
$this->cost_tracker = $cost_tracker ?? new PRAutoBlogger_Cost_Tracker();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +85,10 @@ class PRAutoBlogger_Image_Pipeline {
|
||||
array $article_data,
|
||||
?array $source_data = null
|
||||
): array {
|
||||
$result = [ 'cost_usd' => 0.0, 'errors' => [] ];
|
||||
$result = array(
|
||||
'cost_usd' => 0.0,
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
if ( ! get_option( 'prautoblogger_image_enabled' ) ) {
|
||||
PRAutoBlogger_Logger::instance()->info( 'Image generation disabled in settings.', 'image_pipeline' );
|
||||
@@ -107,24 +110,24 @@ class PRAutoBlogger_Image_Pipeline {
|
||||
|
||||
// Build all prompts upfront (LLM calls happen here, sequentially).
|
||||
$article_prompt = $this->prompt_builder->build_article_prompt( $article_data );
|
||||
$batch_requests = [
|
||||
'image_a' => [
|
||||
$batch_requests = array(
|
||||
'image_a' => array(
|
||||
'prompt' => $article_prompt['prompt'],
|
||||
'width' => self::DEFAULT_WIDTH,
|
||||
'height' => self::DEFAULT_HEIGHT,
|
||||
],
|
||||
];
|
||||
$captions = [ 'image_a' => $article_prompt['caption'] ];
|
||||
),
|
||||
);
|
||||
$captions = array( 'image_a' => $article_prompt['caption'] );
|
||||
|
||||
$source_prompt = null;
|
||||
if ( $generate_b ) {
|
||||
$source_prompt = $this->prompt_builder->build_source_prompt( $source_data );
|
||||
$batch_requests['image_b'] = [
|
||||
$source_prompt = $this->prompt_builder->build_source_prompt( $source_data );
|
||||
$batch_requests['image_b'] = array(
|
||||
'prompt' => $source_prompt['prompt'],
|
||||
'width' => self::DEFAULT_WIDTH,
|
||||
'height' => self::DEFAULT_HEIGHT,
|
||||
];
|
||||
$captions['image_b'] = $source_prompt['caption'];
|
||||
);
|
||||
$captions['image_b'] = $source_prompt['caption'];
|
||||
}
|
||||
|
||||
// Fire all image generation requests in parallel.
|
||||
@@ -290,9 +293,11 @@ class PRAutoBlogger_Image_Pipeline {
|
||||
esc_html( $caption )
|
||||
);
|
||||
|
||||
wp_update_post( [
|
||||
'ID' => $post_id,
|
||||
'post_content' => $caption_html . "\n\n" . $post->post_content,
|
||||
] );
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $post_id,
|
||||
'post_content' => $caption_html . "\n\n" . $post->post_content,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,10 @@ PROMPT;
|
||||
|
||||
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||
|
||||
return [
|
||||
return array(
|
||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||
'caption' => $parsed['caption'],
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,17 +97,17 @@ PROMPT;
|
||||
*/
|
||||
public function build_source_prompt( array $source_data ): array {
|
||||
$title = $source_data['title'] ?? 'Reddit Discussion';
|
||||
$comments = $source_data['comments'] ?? [];
|
||||
$comments = $source_data['comments'] ?? array();
|
||||
$context = is_array( $comments ) && ! empty( $comments ) ? $comments[0] : '';
|
||||
|
||||
$parsed = $this->rewrite_via_llm( $title, $context );
|
||||
|
||||
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||
|
||||
return [
|
||||
return array(
|
||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||
'caption' => $parsed['caption'],
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,15 +146,21 @@ PROMPT;
|
||||
$system = $this->resolve_system_prompt();
|
||||
|
||||
$result = $llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => $user_message ],
|
||||
],
|
||||
array(
|
||||
array(
|
||||
'role' => 'system',
|
||||
'content' => $system,
|
||||
),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => $user_message,
|
||||
),
|
||||
),
|
||||
$model,
|
||||
[
|
||||
array(
|
||||
'temperature' => 0.7,
|
||||
'max_tokens' => self::REWRITER_MAX_TOKENS,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$raw = trim( $result['content'] ?? '' );
|
||||
@@ -217,11 +223,17 @@ PROMPT;
|
||||
$caption = trim( $parts[1] );
|
||||
// Strip surrounding quotes from caption — the LLM wraps it in quotes.
|
||||
$caption = trim( $caption, '"\'' );
|
||||
return [ 'scene' => $scene, 'caption' => $caption ];
|
||||
return array(
|
||||
'scene' => $scene,
|
||||
'caption' => $caption,
|
||||
);
|
||||
}
|
||||
|
||||
// No blank-line separator found — treat entire response as scene.
|
||||
return [ 'scene' => trim( $raw ), 'caption' => '' ];
|
||||
return array(
|
||||
'scene' => trim( $raw ),
|
||||
'caption' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,10 +268,10 @@ PROMPT;
|
||||
public function build_fallback_prompt( string $title ): array {
|
||||
$parsed = $this->synthesize_visual_concepts_fallback( $title, '' );
|
||||
$style_suffix = get_option( 'prautoblogger_image_style_suffix', PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX );
|
||||
return [
|
||||
return array(
|
||||
'prompt' => trim( $parsed['scene'] . ' ' . $style_suffix ),
|
||||
'caption' => $parsed['caption'],
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,10 +290,10 @@ PROMPT;
|
||||
$title
|
||||
);
|
||||
|
||||
return [
|
||||
return array(
|
||||
'scene' => trim( $scene ),
|
||||
'caption' => 'Science is full of surprises.',
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/** Lazy-load the OpenRouter provider. */
|
||||
|
||||
@@ -26,19 +26,19 @@ class PRAutoBlogger_Logger {
|
||||
public const LEVEL_INFO = 2;
|
||||
public const LEVEL_DEBUG = 3;
|
||||
|
||||
private const LEVEL_MAP = [
|
||||
private const LEVEL_MAP = array(
|
||||
'error' => self::LEVEL_ERROR,
|
||||
'warning' => self::LEVEL_WARNING,
|
||||
'info' => self::LEVEL_INFO,
|
||||
'debug' => self::LEVEL_DEBUG,
|
||||
];
|
||||
);
|
||||
|
||||
private const LEVEL_LABELS = [
|
||||
private const LEVEL_LABELS = array(
|
||||
self::LEVEL_ERROR => 'error',
|
||||
self::LEVEL_WARNING => 'warning',
|
||||
self::LEVEL_INFO => 'info',
|
||||
self::LEVEL_DEBUG => 'debug',
|
||||
];
|
||||
);
|
||||
|
||||
/** Singleton instance. */
|
||||
private static ?self $instance = null;
|
||||
@@ -66,7 +66,7 @@ class PRAutoBlogger_Logger {
|
||||
* @param string $context Origin class or pipeline stage.
|
||||
* @param array<string, mixed> $meta Optional structured data.
|
||||
*/
|
||||
public function error( string $message, string $context = '', array $meta = [] ): void {
|
||||
public function error( string $message, string $context = '', array $meta = array() ): void {
|
||||
$this->log( self::LEVEL_ERROR, $message, $context, $meta );
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class PRAutoBlogger_Logger {
|
||||
* @param string $context
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
public function warning( string $message, string $context = '', array $meta = [] ): void {
|
||||
public function warning( string $message, string $context = '', array $meta = array() ): void {
|
||||
$this->log( self::LEVEL_WARNING, $message, $context, $meta );
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class PRAutoBlogger_Logger {
|
||||
* @param string $context
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
public function info( string $message, string $context = '', array $meta = [] ): void {
|
||||
public function info( string $message, string $context = '', array $meta = array() ): void {
|
||||
$this->log( self::LEVEL_INFO, $message, $context, $meta );
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class PRAutoBlogger_Logger {
|
||||
* @param string $context
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
public function debug( string $message, string $context = '', array $meta = [] ): void {
|
||||
public function debug( string $message, string $context = '', array $meta = array() ): void {
|
||||
$this->log( self::LEVEL_DEBUG, $message, $context, $meta );
|
||||
}
|
||||
|
||||
@@ -134,13 +134,16 @@ class PRAutoBlogger_Logger {
|
||||
$table = $wpdb->prefix . 'prautoblogger_event_log';
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->insert( $table, [
|
||||
'level' => $label,
|
||||
'context' => substr( $context, 0, 100 ),
|
||||
'message' => substr( $message, 0, 2000 ),
|
||||
'meta_json' => ! empty( $meta ) ? wp_json_encode( $meta ) : null,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
] );
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'level' => $label,
|
||||
'context' => substr( $context, 0, 100 ),
|
||||
'message' => substr( $message, 0, 2000 ),
|
||||
'meta_json' => ! empty( $meta ) ? wp_json_encode( $meta ) : null,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,8 +164,8 @@ class PRAutoBlogger_Logger {
|
||||
): array {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'prautoblogger_event_log';
|
||||
$where = [];
|
||||
$params = [];
|
||||
$where = array();
|
||||
$params = array();
|
||||
|
||||
if ( 'all' !== $level_filter && isset( self::LEVEL_MAP[ $level_filter ] ) ) {
|
||||
$where[] = 'level = %s';
|
||||
@@ -201,7 +204,10 @@ class PRAutoBlogger_Logger {
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
return [ 'rows' => $rows ?: [], 'total' => $total ];
|
||||
return array(
|
||||
'rows' => $rows ?: array(),
|
||||
'total' => $total,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,8 +35,8 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
}
|
||||
|
||||
$ga4_client = new PRAutoBlogger_GA4_Client();
|
||||
$ga4_data = $ga4_client->fetch_data( $posts );
|
||||
$scored = 0;
|
||||
$ga4_data = $ga4_client->fetch_data( $posts );
|
||||
$scored = 0;
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post_id = (int) $post->ID;
|
||||
@@ -47,11 +47,11 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ga_metrics = $ga4_data[ $post_id ] ?? [];
|
||||
$ga_metrics = $ga4_data[ $post_id ] ?? array();
|
||||
|
||||
$score = $this->compute_composite_score( $wp_metrics, $ga_metrics, $post );
|
||||
$this->store_score( $post_id, $wp_metrics, $ga_metrics, $score );
|
||||
$scored++;
|
||||
++$scored;
|
||||
}
|
||||
|
||||
PRAutoBlogger_Logger::instance()->info( sprintf( 'Metrics collected for %d posts.', $scored ), 'metrics' );
|
||||
@@ -64,15 +64,17 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
* @return \WP_Post[]
|
||||
*/
|
||||
private function get_generated_posts(): array {
|
||||
$query = new \WP_Query( [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'posts_per_page' => 100,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
] );
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'posts_per_page' => 100,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
return $query->posts;
|
||||
}
|
||||
@@ -93,10 +95,10 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
return array(
|
||||
'comment_count' => (int) ( $post->comment_count ?? 0 ),
|
||||
'days_published' => max( 1, (int) ( ( time() - strtotime( $post->post_date ) ) / DAY_IN_SECONDS ) ),
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,10 +112,10 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
* @return array{score: float, factors: array<string, float>}
|
||||
*/
|
||||
private function compute_composite_score( array $wp_metrics, array $ga_metrics, \WP_Post $post ): array {
|
||||
$factors = [];
|
||||
$factors = array();
|
||||
|
||||
// Comment engagement (0-25 points).
|
||||
$comments_per_day = $wp_metrics['comment_count'] / max( 1, $wp_metrics['days_published'] );
|
||||
$comments_per_day = $wp_metrics['comment_count'] / max( 1, $wp_metrics['days_published'] );
|
||||
$factors['comments'] = min( 25.0, $comments_per_day * 25.0 );
|
||||
|
||||
// Pageview performance (0-30 points, if GA4 available).
|
||||
@@ -142,10 +144,10 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
|
||||
$total = array_sum( $factors );
|
||||
|
||||
return [
|
||||
return array(
|
||||
'score' => round( $total, 2 ),
|
||||
'factors' => $factors,
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,16 +164,18 @@ class PRAutoBlogger_Metrics_Collector {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'prautoblogger_content_scores';
|
||||
|
||||
$entry = new PRAutoBlogger_Content_Score( [
|
||||
'post_id' => $post_id,
|
||||
'pageviews' => $ga_metrics['pageviews'] ?? 0,
|
||||
'avg_time_on_page' => $ga_metrics['avg_time_on_page'] ?? 0.0,
|
||||
'bounce_rate' => $ga_metrics['bounce_rate'] ?? 0.0,
|
||||
'comment_count' => $wp_metrics['comment_count'] ?? 0,
|
||||
'composite_score' => $score_data['score'],
|
||||
'score_factors' => $score_data['factors'],
|
||||
'measured_at' => current_time( 'mysql' ),
|
||||
] );
|
||||
$entry = new PRAutoBlogger_Content_Score(
|
||||
array(
|
||||
'post_id' => $post_id,
|
||||
'pageviews' => $ga_metrics['pageviews'] ?? 0,
|
||||
'avg_time_on_page' => $ga_metrics['avg_time_on_page'] ?? 0.0,
|
||||
'bounce_rate' => $ga_metrics['bounce_rate'] ?? 0.0,
|
||||
'comment_count' => $wp_metrics['comment_count'] ?? 0,
|
||||
'composite_score' => $score_data['score'],
|
||||
'score_factors' => $score_data['factors'],
|
||||
'measured_at' => current_time( 'mysql' ),
|
||||
)
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->insert( $table, $entry->to_db_row() );
|
||||
|
||||
@@ -38,13 +38,19 @@ class PRAutoBlogger_Peptide_Linker {
|
||||
// Build a single regex alternation of all peptide names, longest first
|
||||
// so "BPC-157" matches before a hypothetical "BPC" would.
|
||||
$names = array_keys( $link_map );
|
||||
usort( $names, static function ( string $a, string $b ): int {
|
||||
return strlen( $b ) - strlen( $a );
|
||||
} );
|
||||
usort(
|
||||
$names,
|
||||
static function ( string $a, string $b ): int {
|
||||
return strlen( $b ) - strlen( $a );
|
||||
}
|
||||
);
|
||||
|
||||
$escaped = array_map( static function ( string $name ): string {
|
||||
return preg_quote( $name, '/' );
|
||||
}, $names );
|
||||
$escaped = array_map(
|
||||
static function ( string $name ): string {
|
||||
return preg_quote( $name, '/' );
|
||||
},
|
||||
$names
|
||||
);
|
||||
|
||||
// Word-boundary match, case-insensitive.
|
||||
$pattern = '/\b(' . implode( '|', $escaped ) . ')\b/iu';
|
||||
@@ -63,16 +69,18 @@ class PRAutoBlogger_Peptide_Linker {
|
||||
* @return array<string, string> Name → URL pairs, case-preserved keys.
|
||||
*/
|
||||
private static function build_link_map(): array {
|
||||
$peptides = get_posts( [
|
||||
'post_type' => 'pr_peptide',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 200,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC',
|
||||
'fields' => 'ids',
|
||||
] );
|
||||
$peptides = get_posts(
|
||||
array(
|
||||
'post_type' => 'pr_peptide',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 200,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC',
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
$map = [];
|
||||
$map = array();
|
||||
foreach ( $peptides as $peptide_id ) {
|
||||
$title = get_the_title( $peptide_id );
|
||||
$url = get_permalink( $peptide_id );
|
||||
@@ -86,7 +94,7 @@ class PRAutoBlogger_Peptide_Linker {
|
||||
// If the name contains hyphens, also match the space-separated
|
||||
// variant (e.g., "BPC 157" → same URL as "BPC-157").
|
||||
if ( strpos( $title, '-' ) !== false ) {
|
||||
$space_variant = str_replace( '-', ' ', $title );
|
||||
$space_variant = str_replace( '-', ' ', $title );
|
||||
$map[ $space_variant ] = $url;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +123,7 @@ class PRAutoBlogger_Peptide_Linker {
|
||||
}
|
||||
|
||||
// Build a lowercase → URL lookup for case-insensitive matching.
|
||||
$lower_map = [];
|
||||
$lower_map = array();
|
||||
foreach ( $link_map as $name => $url ) {
|
||||
$lower_map[ mb_strtolower( $name ) ] = $url;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,12 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
|
||||
$ideas = $this->orchestrate( $cost_tracker );
|
||||
if ( empty( $ideas ) ) {
|
||||
return [ 'generated' => 0, 'published' => 0, 'rejected' => 0, 'cost' => 0.0 ];
|
||||
return array(
|
||||
'generated' => 0,
|
||||
'published' => 0,
|
||||
'rejected' => 0,
|
||||
'cost' => 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
// Generate article 1 in this process — always fits within time limit.
|
||||
@@ -156,7 +161,7 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
private function orchestrate( PRAutoBlogger_Cost_Tracker $cost_tracker ): array {
|
||||
$target = absint( get_option( 'prautoblogger_daily_article_target', 1 ) );
|
||||
|
||||
$enabled = json_decode( get_option( 'prautoblogger_enabled_sources', '["reddit"]' ), true );
|
||||
$enabled = json_decode( get_option( 'prautoblogger_enabled_sources', '["reddit"]' ), true );
|
||||
$source_labels = is_array( $enabled ) ? implode( ', ', $enabled ) : 'reddit';
|
||||
/* translators: %s is a comma-separated list of enabled source names, e.g. "reddit, llm_research". */
|
||||
$this->broadcast_stage( sprintf( __( 'Collecting sources from %s…', 'prautoblogger' ), $source_labels ) );
|
||||
@@ -197,11 +202,11 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
$ideas
|
||||
);
|
||||
|
||||
$queue = [
|
||||
$queue = array(
|
||||
'run_id' => $run_id,
|
||||
'ideas' => $serialized,
|
||||
'results' => $first,
|
||||
];
|
||||
);
|
||||
|
||||
update_option( self::QUEUE_KEY, $queue, false );
|
||||
$this->update_queue_status( $queue );
|
||||
@@ -221,7 +226,11 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
spawn_cron();
|
||||
wp_remote_post(
|
||||
site_url( 'wp-cron.php?doing_wp_cron=' . sprintf( '%.22F', microtime( true ) ) ),
|
||||
[ 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false ]
|
||||
array(
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
'sslverify' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -231,23 +240,31 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
$total = $done + count( $queue['ideas'] );
|
||||
|
||||
$current = get_transient( 'prautoblogger_generation_status' );
|
||||
set_transient( 'prautoblogger_generation_status', [
|
||||
'status' => 'running',
|
||||
'stage' => sprintf( __( 'Generating article %1$d of %2$d…', 'prautoblogger' ), $done + 1, $total ),
|
||||
'started' => is_array( $current ) ? ( $current['started'] ?? time() ) : time(),
|
||||
'last_updated' => time(),
|
||||
], 600 );
|
||||
set_transient(
|
||||
'prautoblogger_generation_status',
|
||||
array(
|
||||
'status' => 'running',
|
||||
'stage' => sprintf( __( 'Generating article %1$d of %2$d…', 'prautoblogger' ), $done + 1, $total ),
|
||||
'started' => is_array( $current ) ? ( $current['started'] ?? time() ) : time(),
|
||||
'last_updated' => time(),
|
||||
),
|
||||
600
|
||||
);
|
||||
}
|
||||
|
||||
/** Write the final "complete" status transient. */
|
||||
private function write_final_status( array $r ): void {
|
||||
set_transient( 'prautoblogger_generation_status', [
|
||||
'status' => 'complete',
|
||||
'generated' => $r['generated'],
|
||||
'published' => $r['published'],
|
||||
'rejected' => $r['rejected'],
|
||||
'cost' => $r['cost'],
|
||||
], 600 );
|
||||
set_transient(
|
||||
'prautoblogger_generation_status',
|
||||
array(
|
||||
'status' => 'complete',
|
||||
'generated' => $r['generated'],
|
||||
'published' => $r['published'],
|
||||
'rejected' => $r['rejected'],
|
||||
'cost' => $r['cost'],
|
||||
),
|
||||
600
|
||||
);
|
||||
}
|
||||
|
||||
/** Clean up a stale or empty queue. */
|
||||
@@ -265,7 +282,10 @@ class PRAutoBlogger_Pipeline_Runner {
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
sprintf(
|
||||
'Pipeline complete: %d generated, %d published, %d rejected. Cost: $%.4f',
|
||||
$r['generated'], $r['published'], $r['rejected'], $r['cost']
|
||||
$r['generated'],
|
||||
$r['published'],
|
||||
$r['rejected'],
|
||||
$r['cost']
|
||||
),
|
||||
'pipeline'
|
||||
);
|
||||
|
||||
@@ -21,12 +21,12 @@ class PRAutoBlogger_Post_Assembler {
|
||||
* @param PRAutoBlogger_Article_Idea $idea Article idea with type and keywords.
|
||||
*/
|
||||
public static function assign_taxonomy_terms( int $post_id, PRAutoBlogger_Article_Idea $idea ): void {
|
||||
$type_category_map = [
|
||||
$type_category_map = array(
|
||||
'guide' => 'Guides',
|
||||
'solution' => 'Solutions',
|
||||
'comparison' => 'Comparisons',
|
||||
'article' => 'Articles',
|
||||
];
|
||||
);
|
||||
|
||||
$category_name = $type_category_map[ $idea->get_article_type() ] ?? 'Articles';
|
||||
$category = get_term_by( 'name', $category_name, 'category' );
|
||||
@@ -34,10 +34,10 @@ class PRAutoBlogger_Post_Assembler {
|
||||
if ( ! $category ) {
|
||||
$result = wp_insert_term( $category_name, 'category' );
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
wp_set_post_categories( $post_id, [ $result['term_id'] ] );
|
||||
wp_set_post_categories( $post_id, array( $result['term_id'] ) );
|
||||
}
|
||||
} else {
|
||||
wp_set_post_categories( $post_id, [ $category->term_id ] );
|
||||
wp_set_post_categories( $post_id, array( $category->term_id ) );
|
||||
}
|
||||
|
||||
$keywords = $idea->get_target_keywords();
|
||||
@@ -133,34 +133,37 @@ class PRAutoBlogger_Post_Assembler {
|
||||
return; // No articles produced — nothing to amortize into.
|
||||
}
|
||||
|
||||
$total_cost = (float) $research_row->estimated_cost;
|
||||
$amortized_cost = $total_cost / $post_count;
|
||||
$total_prompt = (int) $research_row->prompt_tokens;
|
||||
$total_completion = (int) $research_row->completion_tokens;
|
||||
$amortized_prompt = (int) round( $total_prompt / $post_count );
|
||||
$amortized_compl = (int) round( $total_completion / $post_count );
|
||||
$total_cost = (float) $research_row->estimated_cost;
|
||||
$amortized_cost = $total_cost / $post_count;
|
||||
$total_prompt = (int) $research_row->prompt_tokens;
|
||||
$total_completion = (int) $research_row->completion_tokens;
|
||||
$amortized_prompt = (int) round( $total_prompt / $post_count );
|
||||
$amortized_compl = (int) round( $total_completion / $post_count );
|
||||
|
||||
// Insert one amortized research row per article.
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->insert( $table, [
|
||||
'post_id' => (int) $post_id,
|
||||
'run_id' => $run_id,
|
||||
'stage' => 'llm_research',
|
||||
'provider' => $research_row->provider,
|
||||
'model' => $research_row->model,
|
||||
'prompt_tokens' => $amortized_prompt,
|
||||
'completion_tokens' => $amortized_compl,
|
||||
'estimated_cost' => $amortized_cost,
|
||||
'response_status' => 'success',
|
||||
'error_message' => '',
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
] );
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
array(
|
||||
'post_id' => (int) $post_id,
|
||||
'run_id' => $run_id,
|
||||
'stage' => 'llm_research',
|
||||
'provider' => $research_row->provider,
|
||||
'model' => $research_row->model,
|
||||
'prompt_tokens' => $amortized_prompt,
|
||||
'completion_tokens' => $amortized_compl,
|
||||
'estimated_cost' => $amortized_cost,
|
||||
'response_status' => 'success',
|
||||
'error_message' => '',
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the original unlinked row — it's been replaced by per-article rows.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->delete( $table, [ 'id' => (int) $research_row->id ] );
|
||||
$wpdb->delete( $table, array( 'id' => (int) $research_row->id ) );
|
||||
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
sprintf(
|
||||
@@ -213,7 +216,13 @@ class PRAutoBlogger_Post_Assembler {
|
||||
return $author_id;
|
||||
}
|
||||
|
||||
$admins = get_users( [ 'role' => 'administrator', 'number' => 1, 'fields' => 'ID' ] );
|
||||
$admins = get_users(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
'number' => 1,
|
||||
'fields' => 'ID',
|
||||
)
|
||||
);
|
||||
return ! empty( $admins ) ? (int) $admins[0] : 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,14 +81,14 @@ class PRAutoBlogger_Publisher {
|
||||
$clean_content = PRAutoBlogger_Post_Assembler::sanitize_llm_content( $content );
|
||||
$linked_content = PRAutoBlogger_Peptide_Linker::inject_links( $clean_content );
|
||||
|
||||
$post_data = [
|
||||
$post_data = array(
|
||||
'post_title' => sanitize_text_field( $idea->get_suggested_title() ),
|
||||
'post_content' => wp_kses_post( $linked_content ),
|
||||
'post_status' => $post_status,
|
||||
'post_type' => 'post',
|
||||
'post_author' => PRAutoBlogger_Post_Assembler::get_default_author_id(),
|
||||
'meta_input' => $this->build_meta( $idea, $review, $run_id ),
|
||||
];
|
||||
);
|
||||
|
||||
/** @see class-prautoblogger.php — listeners registered in main loader. */
|
||||
$post_data = apply_filters( 'prautoblogger_filter_post_data', $post_data, $idea, $review );
|
||||
@@ -135,7 +135,7 @@ class PRAutoBlogger_Publisher {
|
||||
PRAutoBlogger_Editorial_Review $review,
|
||||
?string $run_id = null
|
||||
): array {
|
||||
$meta = [
|
||||
$meta = array(
|
||||
'_prautoblogger_generated' => '1',
|
||||
'_prautoblogger_analysis_id' => $idea->get_analysis_id(),
|
||||
'_prautoblogger_source_ids' => wp_json_encode( $idea->get_source_ids() ),
|
||||
@@ -149,7 +149,7 @@ class PRAutoBlogger_Publisher {
|
||||
'_prautoblogger_topic' => $idea->get_topic(),
|
||||
'_prautoblogger_article_type' => $idea->get_article_type(),
|
||||
'_prautoblogger_target_keywords' => wp_json_encode( $idea->get_target_keywords() ),
|
||||
];
|
||||
);
|
||||
if ( null !== $run_id && '' !== $run_id ) {
|
||||
$meta['_prautoblogger_run_id'] = $run_id;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,11 @@ class PRAutoBlogger_Research_Reaper {
|
||||
* @return array{reaped: int, deleted: int, skipped: int}
|
||||
*/
|
||||
public static function reap(): array {
|
||||
$stats = [ 'reaped' => 0, 'deleted' => 0, 'skipped' => 0 ];
|
||||
$stats = array(
|
||||
'reaped' => 0,
|
||||
'deleted' => 0,
|
||||
'skipped' => 0,
|
||||
);
|
||||
|
||||
try {
|
||||
global $wpdb;
|
||||
@@ -115,10 +119,10 @@ class PRAutoBlogger_Research_Reaper {
|
||||
if ( '' === $run_id ) {
|
||||
if ( $is_stale ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->delete( $log_table, [ 'id' => (int) $orphan->id ] );
|
||||
$stats['deleted']++;
|
||||
$wpdb->delete( $log_table, array( 'id' => (int) $orphan->id ) );
|
||||
++$stats['deleted'];
|
||||
} else {
|
||||
$stats['skipped']++;
|
||||
++$stats['skipped'];
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -130,22 +134,22 @@ class PRAutoBlogger_Research_Reaper {
|
||||
sprintf( 'Reaped research cost across %d articles for run %s.', count( $post_ids ), $run_id ),
|
||||
'cost-tracker'
|
||||
);
|
||||
$stats['reaped']++;
|
||||
++$stats['reaped'];
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $is_stale ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
$wpdb->delete( $log_table, [ 'id' => (int) $orphan->id ] );
|
||||
$wpdb->delete( $log_table, array( 'id' => (int) $orphan->id ) );
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
sprintf( 'Deleted 7-day-stale orphan research row for run %s — no articles to attribute.', $run_id ),
|
||||
'cost-tracker'
|
||||
);
|
||||
$stats['deleted']++;
|
||||
++$stats['deleted'];
|
||||
return;
|
||||
}
|
||||
|
||||
$stats['skipped']++;
|
||||
++$stats['skipped'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,15 +160,17 @@ class PRAutoBlogger_Research_Reaper {
|
||||
* @return int[] Distinct post IDs.
|
||||
*/
|
||||
private static function find_posts_for_run_id( string $run_id ): array {
|
||||
$meta_ids = get_posts( [
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'any',
|
||||
'meta_key' => '_prautoblogger_run_id',
|
||||
'meta_value' => $run_id,
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => -1,
|
||||
'suppress_filters' => true,
|
||||
] );
|
||||
$meta_ids = get_posts(
|
||||
array(
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'any',
|
||||
'meta_key' => '_prautoblogger_run_id',
|
||||
'meta_value' => $run_id,
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => -1,
|
||||
'suppress_filters' => true,
|
||||
)
|
||||
);
|
||||
if ( is_array( $meta_ids ) && ! empty( $meta_ids ) ) {
|
||||
return array_map( 'intval', $meta_ids );
|
||||
}
|
||||
@@ -178,6 +184,6 @@ class PRAutoBlogger_Research_Reaper {
|
||||
$run_id
|
||||
)
|
||||
);
|
||||
return is_array( $ids ) ? array_map( 'intval', $ids ) : [];
|
||||
return is_array( $ids ) ? array_map( 'intval', $ids ) : array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@ class PRAutoBlogger_Semantic_Dedup {
|
||||
private const SIMILARITY_THRESHOLD = 0.72;
|
||||
|
||||
/** @var float[][] Cached embeddings for recent post titles, keyed by index. */
|
||||
private array $title_embeddings = [];
|
||||
private array $title_embeddings = array();
|
||||
|
||||
/** @var string[] Recent post titles. */
|
||||
private array $recent_titles = [];
|
||||
private array $recent_titles = array();
|
||||
|
||||
/** @var float[][] Cached embeddings for accepted ideas, accumulated per-batch. */
|
||||
private array $accepted_embeddings = [];
|
||||
private array $accepted_embeddings = array();
|
||||
|
||||
/** @var bool Whether embeddings are available (false after API failure). */
|
||||
private bool $embeddings_available = true;
|
||||
@@ -158,7 +158,7 @@ class PRAutoBlogger_Semantic_Dedup {
|
||||
if ( null === $this->provider ) {
|
||||
$this->provider = new PRAutoBlogger_OpenRouter_Embedding_Provider();
|
||||
}
|
||||
$result = $this->provider->get_embeddings( [ $text ] );
|
||||
$result = $this->provider->get_embeddings( array( $text ) );
|
||||
return $result[0] ?? null;
|
||||
} catch ( \Throwable $e ) {
|
||||
// Degrade gracefully — one failed embed shouldn't kill the pipeline.
|
||||
@@ -220,18 +220,20 @@ class PRAutoBlogger_Semantic_Dedup {
|
||||
* @return string[]
|
||||
*/
|
||||
private function fetch_recent_post_titles(): array {
|
||||
$query = new \WP_Query( [
|
||||
'post_type' => 'post',
|
||||
'post_status' => [ 'publish', 'draft', 'pending' ],
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'date_query' => [ [ 'after' => '30 days ago' ] ],
|
||||
'posts_per_page' => 100,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
] );
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_type' => 'post',
|
||||
'post_status' => array( 'publish', 'draft', 'pending' ),
|
||||
'meta_key' => '_prautoblogger_generated',
|
||||
'meta_value' => '1',
|
||||
'date_query' => array( array( 'after' => '30 days ago' ) ),
|
||||
'posts_per_page' => 100,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
|
||||
$titles = [];
|
||||
$titles = array();
|
||||
foreach ( $query->posts as $post_id ) {
|
||||
$titles[] = get_the_title( $post_id );
|
||||
}
|
||||
@@ -245,27 +247,122 @@ class PRAutoBlogger_Semantic_Dedup {
|
||||
* @return string[] Unique lowercase keywords.
|
||||
*/
|
||||
private function extract_meaningful_keywords( string $text ): array {
|
||||
static $stopwords = [
|
||||
'a', 'an', 'the', 'and', 'or', 'but', 'is', 'are', 'was', 'were',
|
||||
'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'as',
|
||||
'it', 'its', 'this', 'that', 'your', 'you', 'how', 'what', 'when',
|
||||
'why', 'where', 'which', 'who', 'do', 'does', 'did', 'not', 'no',
|
||||
'can', 'will', 'should', 'could', 'would', 'may', 'might', 'have',
|
||||
'has', 'had', 'be', 'been', 'being', 'about', 'into', 'through',
|
||||
'during', 'before', 'after', 'above', 'below', 'between', 'same',
|
||||
'up', 'down', 'out', 'off', 'over', 'under', 'again', 'then',
|
||||
'here', 'there', 'all', 'each', 'every', 'both', 'few', 'more',
|
||||
'most', 'other', 'some', 'such', 'than', 'too', 'very', 'just',
|
||||
'also', 'only', 'own', 'so', 'if', 'while', 'because', 'until',
|
||||
'vs', 'versus', 'guide', 'complete', 'ultimate', 'best', 'top',
|
||||
'new', 'first', 'need', 'know', 'everything',
|
||||
];
|
||||
static $stopwords = array(
|
||||
'a',
|
||||
'an',
|
||||
'the',
|
||||
'and',
|
||||
'or',
|
||||
'but',
|
||||
'is',
|
||||
'are',
|
||||
'was',
|
||||
'were',
|
||||
'in',
|
||||
'on',
|
||||
'at',
|
||||
'to',
|
||||
'for',
|
||||
'of',
|
||||
'with',
|
||||
'by',
|
||||
'from',
|
||||
'as',
|
||||
'it',
|
||||
'its',
|
||||
'this',
|
||||
'that',
|
||||
'your',
|
||||
'you',
|
||||
'how',
|
||||
'what',
|
||||
'when',
|
||||
'why',
|
||||
'where',
|
||||
'which',
|
||||
'who',
|
||||
'do',
|
||||
'does',
|
||||
'did',
|
||||
'not',
|
||||
'no',
|
||||
'can',
|
||||
'will',
|
||||
'should',
|
||||
'could',
|
||||
'would',
|
||||
'may',
|
||||
'might',
|
||||
'have',
|
||||
'has',
|
||||
'had',
|
||||
'be',
|
||||
'been',
|
||||
'being',
|
||||
'about',
|
||||
'into',
|
||||
'through',
|
||||
'during',
|
||||
'before',
|
||||
'after',
|
||||
'above',
|
||||
'below',
|
||||
'between',
|
||||
'same',
|
||||
'up',
|
||||
'down',
|
||||
'out',
|
||||
'off',
|
||||
'over',
|
||||
'under',
|
||||
'again',
|
||||
'then',
|
||||
'here',
|
||||
'there',
|
||||
'all',
|
||||
'each',
|
||||
'every',
|
||||
'both',
|
||||
'few',
|
||||
'more',
|
||||
'most',
|
||||
'other',
|
||||
'some',
|
||||
'such',
|
||||
'than',
|
||||
'too',
|
||||
'very',
|
||||
'just',
|
||||
'also',
|
||||
'only',
|
||||
'own',
|
||||
'so',
|
||||
'if',
|
||||
'while',
|
||||
'because',
|
||||
'until',
|
||||
'vs',
|
||||
'versus',
|
||||
'guide',
|
||||
'complete',
|
||||
'ultimate',
|
||||
'best',
|
||||
'top',
|
||||
'new',
|
||||
'first',
|
||||
'need',
|
||||
'know',
|
||||
'everything',
|
||||
);
|
||||
|
||||
$text = strtolower( $text );
|
||||
$words = preg_split( '/[^a-z0-9-]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
|
||||
$words = array_filter( $words, static function ( string $w ) use ( $stopwords ): bool {
|
||||
return strlen( $w ) >= 2 && ! in_array( $w, $stopwords, true );
|
||||
} );
|
||||
$words = array_filter(
|
||||
$words,
|
||||
static function ( string $w ) use ( $stopwords ): bool {
|
||||
return strlen( $w ) >= 2 && ! in_array( $w, $stopwords, true );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( array_unique( $words ) );
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class PRAutoBlogger_Source_Collector {
|
||||
// Only fall back to reddit if the option is corrupt (non-JSON).
|
||||
// An empty array '[]' is a valid choice — the user disabled all sources.
|
||||
if ( ! is_array( $enabled ) ) {
|
||||
$enabled = [ 'reddit' ];
|
||||
$enabled = array( 'reddit' );
|
||||
}
|
||||
|
||||
$total_inserted = 0;
|
||||
@@ -64,9 +64,9 @@ class PRAutoBlogger_Source_Collector {
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $provider->collect_data( $this->get_config_for_source( $source_type ) );
|
||||
$inserted = $this->store_data( $data );
|
||||
$refreshed = count( $data ) - $inserted;
|
||||
$data = $provider->collect_data( $this->get_config_for_source( $source_type ) );
|
||||
$inserted = $this->store_data( $data );
|
||||
$refreshed = count( $data ) - $inserted;
|
||||
$total_inserted += $inserted;
|
||||
|
||||
PRAutoBlogger_Logger::instance()->info(
|
||||
@@ -117,10 +117,10 @@ class PRAutoBlogger_Source_Collector {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
return array(
|
||||
'title' => $row['title'] ?? 'Reddit Discussion',
|
||||
'selftext' => $row['content'] ?? '',
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +175,7 @@ class PRAutoBlogger_Source_Collector {
|
||||
// ON DUPLICATE KEY UPDATE returns 2 for updates, 1 for inserts.
|
||||
// Only count genuine inserts toward the "new" tally.
|
||||
if ( false !== $result && 1 === (int) $result ) {
|
||||
$inserted++;
|
||||
++$inserted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +190,10 @@ class PRAutoBlogger_Source_Collector {
|
||||
* @return PRAutoBlogger_Source_Provider_Interface|null
|
||||
*/
|
||||
private function get_provider( string $source_type ): ?PRAutoBlogger_Source_Provider_Interface {
|
||||
$providers = [
|
||||
$providers = array(
|
||||
'reddit' => PRAutoBlogger_Reddit_Provider::class,
|
||||
'llm_research' => PRAutoBlogger_LLM_Research_Provider::class,
|
||||
];
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the registered source providers.
|
||||
@@ -230,20 +230,20 @@ class PRAutoBlogger_Source_Collector {
|
||||
*/
|
||||
private function get_config_for_source( string $source_type ): array {
|
||||
if ( 'reddit' === $source_type ) {
|
||||
return [
|
||||
'subreddits' => json_decode( get_option( 'prautoblogger_target_subreddits', '[]' ), true ) ?: [],
|
||||
return array(
|
||||
'subreddits' => json_decode( get_option( 'prautoblogger_target_subreddits', '[]' ), true ) ?: array(),
|
||||
'limit' => 25,
|
||||
'time_filter' => 'day',
|
||||
'include_comments' => true,
|
||||
'comments_per_post' => 10,
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'llm_research' === $source_type ) {
|
||||
$config = [
|
||||
$config = array(
|
||||
'model' => get_option( 'prautoblogger_research_model', PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL ),
|
||||
'prompt' => get_option( 'prautoblogger_research_prompt', '' ),
|
||||
];
|
||||
);
|
||||
// Pass the pipeline's cost tracker so research costs get tagged
|
||||
// with the run_id for post-pipeline amortization across articles.
|
||||
if ( null !== $this->cost_tracker ) {
|
||||
@@ -252,6 +252,6 @@ class PRAutoBlogger_Source_Collector {
|
||||
return $config;
|
||||
}
|
||||
|
||||
return [];
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user