v0.6.1: peptide auto-linking + extract Content_Prompts class
When any peptide from the PR Core database is mentioned in a generated article, the first mention is now automatically linked to its /peptides/ page. Gracefully degrades if PR Core is not active. Extracted all prompt construction into Content_Prompts (252 lines), slimming Content_Generator from 324 → 162 lines. Both under 300. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,7 @@ prautoblogger/
|
||||
│ │ ├── class-idea-scorer.php # Ranks and deduplicates article ideas (delegates to Semantic_Dedup)
|
||||
│ │ ├── class-semantic-dedup.php # Embedding-based cosine similarity dedup with keyword fallback
|
||||
│ │ ├── class-content-generator.php# Writer agent — manages the generation pipeline
|
||||
│ │ ├── class-content-prompts.php # Builds all LLM prompts for content generation (system, stage, linking rules)
|
||||
│ │ ├── class-chief-editor.php # Editor agent — LLM-powered editorial review
|
||||
│ │ ├── class-publisher.php # Creates WordPress posts from approved content
|
||||
│ │ ├── class-post-assembler.php # Post-creation helpers: taxonomy, log linking, images, sanitization
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -5,6 +5,24 @@ All notable changes to PRAutoBlogger will be documented in this file.
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project uses [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.6.1] — 2026-04-20
|
||||
|
||||
### Added
|
||||
- **Peptide auto-linking.** When any peptide name from the PR Core database is
|
||||
mentioned in a generated article, the first mention is automatically linked to
|
||||
the corresponding `/peptides/{slug}/` page. The system prompt now includes a
|
||||
complete peptide database reference list alongside the existing article links.
|
||||
Gracefully degrades if PR Core is not active (`post_type_exists()` guard).
|
||||
|
||||
### Changed
|
||||
- **Extracted prompt builders into Content_Prompts class.** Moved all prompt
|
||||
construction (system prompt, stage prompts, linking rules) from
|
||||
`Content_Generator` into a new `Content_Prompts` static helper. Generator
|
||||
dropped from 324 → 162 lines; prompts class is 252 lines. Both under 300.
|
||||
- **Internal link rules strengthened.** Linking rules section now explicitly
|
||||
instructs the model to link every peptide's first mention and never fabricate
|
||||
URLs, with real published article and peptide page URLs provided.
|
||||
|
||||
## [0.6.0] — 2026-04-20
|
||||
|
||||
### Added
|
||||
|
||||
@@ -9,11 +9,12 @@ declare(strict_types=1);
|
||||
* - multi_step: Outline → Draft → Polish pipeline (higher quality, more cost).
|
||||
*
|
||||
* Triggered by: PRAutoBlogger::run_generation_pipeline() (step 4).
|
||||
* Dependencies: LLM_Provider_Interface, Cost_Tracker.
|
||||
* Dependencies: LLM_Provider_Interface, Cost_Tracker, Content_Prompts.
|
||||
*
|
||||
* @see core/class-chief-editor.php — Reviews output of this class.
|
||||
* @see core/class-content-prompts.php — Builds all LLM prompts used here.
|
||||
* @see core/class-chief-editor.php — Reviews output of this class.
|
||||
* @see providers/interface-llm-provider.php — LLM used for generation.
|
||||
* @see ARCHITECTURE.md — Data flow step 4.
|
||||
* @see ARCHITECTURE.md — Data flow step 4.
|
||||
*/
|
||||
class PRAutoBlogger_Content_Generator {
|
||||
|
||||
@@ -66,31 +67,20 @@ class PRAutoBlogger_Content_Generator {
|
||||
* @return string Generated HTML content.
|
||||
*/
|
||||
private function generate_single_pass( PRAutoBlogger_Content_Request $request ): string {
|
||||
$model = get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL );
|
||||
$idea = $request->get_idea();
|
||||
|
||||
$system = $this->build_writer_system_prompt( $request );
|
||||
$user = $this->build_single_pass_prompt( $request );
|
||||
$model = get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => $user ],
|
||||
[ 'role' => 'system', 'content' => PRAutoBlogger_Content_Prompts::build_system( $request ) ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_single_pass( $request ) ],
|
||||
],
|
||||
$model,
|
||||
[
|
||||
'temperature' => 0.7,
|
||||
'max_tokens' => 4000,
|
||||
]
|
||||
[ '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'];
|
||||
@@ -100,13 +90,8 @@ class PRAutoBlogger_Content_Generator {
|
||||
private function generate_multi_step( PRAutoBlogger_Content_Request $request ): string {
|
||||
$model = get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL );
|
||||
|
||||
// Step 1: Generate outline.
|
||||
$outline = $this->stage_outline( $request, $model );
|
||||
|
||||
// Step 2: Generate draft from outline.
|
||||
$draft = $this->stage_draft( $request, $outline, $model );
|
||||
|
||||
// Step 3: Polish the draft.
|
||||
$outline = $this->stage_outline( $request, $model );
|
||||
$draft = $this->stage_draft( $request, $outline, $model );
|
||||
$polished = $this->stage_polish( $request, $draft, $model );
|
||||
|
||||
return $polished;
|
||||
@@ -114,29 +99,12 @@ class PRAutoBlogger_Content_Generator {
|
||||
|
||||
/** Multi-step stage 1: Generate an article outline. */
|
||||
private function stage_outline( PRAutoBlogger_Content_Request $request, string $model ): string {
|
||||
$idea = $request->get_idea();
|
||||
|
||||
$prompt = sprintf(
|
||||
"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. " .
|
||||
"Word count target: %d-%d words.\n\n" .
|
||||
"Plan the structure to satisfy EVERY requirement in your system prompt style guide.",
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
implode( "\n- ", $idea->get_key_points() ),
|
||||
implode( ', ', $idea->get_target_keywords() ),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count()
|
||||
);
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $this->build_writer_system_prompt( $request ) ],
|
||||
[ 'role' => 'user', 'content' => $prompt ],
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_outline( $request ) ],
|
||||
],
|
||||
$model,
|
||||
[ 'temperature' => 0.5, 'max_tokens' => 1500 ]
|
||||
@@ -152,29 +120,12 @@ class PRAutoBlogger_Content_Generator {
|
||||
|
||||
/** Multi-step stage 2: Write the full draft from the outline. */
|
||||
private function stage_draft( PRAutoBlogger_Content_Request $request, string $outline, string $model ): string {
|
||||
$prompt = sprintf(
|
||||
"Using this outline, write the full blog post in HTML format.\n\n" .
|
||||
"OUTLINE:\n%s\n\n" .
|
||||
"Requirements:\n" .
|
||||
"- Write in a %s tone\n" .
|
||||
"- Target %d-%d words\n" .
|
||||
"- Use proper HTML headings (h2, h3), paragraphs, and lists\n" .
|
||||
"- Include an engaging introduction and strong conclusion\n" .
|
||||
"- 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",
|
||||
$outline,
|
||||
$request->get_tone(),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count(),
|
||||
implode( ', ', $request->get_idea()->get_target_keywords() )
|
||||
);
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $this->build_writer_system_prompt( $request ) ],
|
||||
[ 'role' => 'user', 'content' => $prompt ],
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_draft( $request, $outline ) ],
|
||||
],
|
||||
$model,
|
||||
[ 'temperature' => 0.7, 'max_tokens' => 4000 ]
|
||||
@@ -190,23 +141,12 @@ class PRAutoBlogger_Content_Generator {
|
||||
|
||||
/** Multi-step stage 3: Polish and refine the draft. */
|
||||
private function stage_polish( PRAutoBlogger_Content_Request $request, string $draft, string $model ): string {
|
||||
$prompt = "Review and polish this blog post draft. Improve:\n" .
|
||||
"1. Flow and readability\n" .
|
||||
"2. SEO optimization (headings, keyword placement)\n" .
|
||||
"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 " .
|
||||
"guide is satisfied in the final output.\n\n" .
|
||||
"Return the polished HTML content only. Do not add commentary.\n\n" .
|
||||
"DRAFT:\n" . $draft;
|
||||
$system = PRAutoBlogger_Content_Prompts::build_system( $request );
|
||||
|
||||
$response = $this->llm->send_chat_completion(
|
||||
[
|
||||
[ 'role' => 'system', 'content' => $this->build_writer_system_prompt( $request ) ],
|
||||
[ 'role' => 'user', 'content' => $prompt ],
|
||||
[ 'role' => 'system', 'content' => $system ],
|
||||
[ 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_polish( $draft ) ],
|
||||
],
|
||||
$model,
|
||||
[ 'temperature' => 0.4, 'max_tokens' => 4000 ]
|
||||
@@ -219,65 +159,4 @@ class PRAutoBlogger_Content_Generator {
|
||||
|
||||
return $response['content'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the system prompt shared across all writing stages.
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request Content request with settings.
|
||||
* @return string
|
||||
*/
|
||||
private function build_writer_system_prompt( PRAutoBlogger_Content_Request $request ): string {
|
||||
$niche = $request->get_niche_description();
|
||||
$prompt = "You are an expert blog writer";
|
||||
if ( '' !== $niche ) {
|
||||
$prompt .= " specializing in {$niche}";
|
||||
}
|
||||
$prompt .= ". Write well-researched, engaging, SEO-friendly content. ";
|
||||
$prompt .= "Use a {$request->get_tone()} tone. ";
|
||||
$prompt .= "Output HTML content only — no markdown, no code fences, no commentary.";
|
||||
|
||||
// Append user-defined writing instructions as a mandatory style guide.
|
||||
// Framed prominently to ensure the model treats them as requirements,
|
||||
// not optional suggestions — "Additional instructions" was too weak.
|
||||
$instructions = trim( $request->get_writing_instructions() );
|
||||
if ( '' !== $instructions ) {
|
||||
$prompt .= "\n\n--- MANDATORY STYLE GUIDE ---\n";
|
||||
$prompt .= "You MUST follow every requirement below. These override any conflicting defaults:\n\n";
|
||||
$prompt .= $instructions;
|
||||
$prompt .= "\n--- END STYLE GUIDE ---";
|
||||
}
|
||||
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the user prompt for single-pass generation.
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_single_pass_prompt( PRAutoBlogger_Content_Request $request ): string {
|
||||
$idea = $request->get_idea();
|
||||
|
||||
return sprintf(
|
||||
"Write a complete blog post in HTML format.\n\n" .
|
||||
"Title: %s\nTopic: %s\nType: %s\n\nKey points:\n- %s\n\n" .
|
||||
"Keywords: %s\n\n" .
|
||||
"Requirements:\n" .
|
||||
"- %d-%d words\n" .
|
||||
"- Proper HTML (h2, h3, p, ul/li)\n" .
|
||||
"- 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",
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
implode( "\n- ", $idea->get_key_points() ),
|
||||
implode( ', ', $idea->get_target_keywords() ),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
252
includes/core/class-content-prompts.php
Normal file
252
includes/core/class-content-prompts.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Builds all LLM prompts used by the content generation pipeline.
|
||||
*
|
||||
* Centralises prompt construction so Content_Generator stays lean and
|
||||
* prompt logic (system prompt, stage prompts, linking rules) can evolve
|
||||
* independently of the generation orchestration.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger_Content_Generator (called for every stage).
|
||||
* Dependencies: WordPress query functions (get_posts, get_the_title, get_permalink).
|
||||
*
|
||||
* @see core/class-content-generator.php — Consumer of these prompts.
|
||||
* @see models/class-content-request.php — Data bag passed to every builder.
|
||||
*/
|
||||
class PRAutoBlogger_Content_Prompts {
|
||||
|
||||
/**
|
||||
* Build the system prompt shared across all writing stages.
|
||||
*
|
||||
* Includes niche, tone, mandatory style guide, internal link reference,
|
||||
* peptide database links, and the "never fabricate URLs" rule.
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request Content request with settings.
|
||||
* @return string Complete system prompt.
|
||||
*/
|
||||
public static function build_system( PRAutoBlogger_Content_Request $request ): string {
|
||||
$niche = $request->get_niche_description();
|
||||
$prompt = 'You are an expert blog writer';
|
||||
if ( '' !== $niche ) {
|
||||
$prompt .= " specializing in {$niche}";
|
||||
}
|
||||
$prompt .= '. Write well-researched, engaging, SEO-friendly content. ';
|
||||
$prompt .= "Use a {$request->get_tone()} tone. ";
|
||||
$prompt .= 'Output HTML content only — no markdown, no code fences, no commentary.';
|
||||
|
||||
// Append user-defined writing instructions as a mandatory style guide.
|
||||
$instructions = trim( $request->get_writing_instructions() );
|
||||
if ( '' !== $instructions ) {
|
||||
$prompt .= "\n\n--- MANDATORY STYLE GUIDE ---\n";
|
||||
$prompt .= "You MUST follow every requirement below. These override any conflicting defaults:\n\n";
|
||||
$prompt .= $instructions;
|
||||
$prompt .= "\n--- END STYLE GUIDE ---";
|
||||
}
|
||||
|
||||
$prompt .= "\n\n" . self::build_linking_rules();
|
||||
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the user prompt for single-pass generation.
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request Content request with settings.
|
||||
* @return string User prompt.
|
||||
*/
|
||||
public static function build_single_pass( PRAutoBlogger_Content_Request $request ): string {
|
||||
$idea = $request->get_idea();
|
||||
|
||||
return sprintf(
|
||||
"Write a complete blog post in HTML format.\n\n" .
|
||||
"Title: %s\nTopic: %s\nType: %s\n\nKey points:\n- %s\n\n" .
|
||||
"Keywords: %s\n\n" .
|
||||
"Requirements:\n" .
|
||||
"- %d-%d words\n" .
|
||||
"- Proper HTML (h2, h3, p, ul/li)\n" .
|
||||
"- 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",
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
implode( "\n- ", $idea->get_key_points() ),
|
||||
implode( ', ', $idea->get_target_keywords() ),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the outline stage prompt (multi-step stage 1).
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request Content request with settings.
|
||||
* @return string Outline prompt.
|
||||
*/
|
||||
public static function build_outline( PRAutoBlogger_Content_Request $request ): string {
|
||||
$idea = $request->get_idea();
|
||||
|
||||
return sprintf(
|
||||
"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. " .
|
||||
"Word count target: %d-%d words.\n\n" .
|
||||
"Plan the structure to satisfy EVERY requirement in your system prompt style guide.",
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
implode( "\n- ", $idea->get_key_points() ),
|
||||
implode( ', ', $idea->get_target_keywords() ),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the draft stage prompt (multi-step stage 2).
|
||||
*
|
||||
* @param PRAutoBlogger_Content_Request $request Content request with settings.
|
||||
* @param string $outline The outline from stage 1.
|
||||
* @return string Draft prompt.
|
||||
*/
|
||||
public static function build_draft( PRAutoBlogger_Content_Request $request, string $outline ): string {
|
||||
return sprintf(
|
||||
"Using this outline, write the full blog post in HTML format.\n\n" .
|
||||
"OUTLINE:\n%s\n\n" .
|
||||
"Requirements:\n" .
|
||||
"- Write in a %s tone\n" .
|
||||
"- Target %d-%d words\n" .
|
||||
"- Use proper HTML headings (h2, h3), paragraphs, and lists\n" .
|
||||
"- Include an engaging introduction and strong conclusion\n" .
|
||||
"- 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",
|
||||
$outline,
|
||||
$request->get_tone(),
|
||||
$request->get_min_word_count(),
|
||||
$request->get_max_word_count(),
|
||||
implode( ', ', $request->get_idea()->get_target_keywords() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the polish stage prompt (multi-step stage 3).
|
||||
*
|
||||
* @param string $draft The draft from stage 2.
|
||||
* @return string Polish prompt.
|
||||
*/
|
||||
public static function build_polish( string $draft ): string {
|
||||
return "Review and polish this blog post draft. Improve:\n" .
|
||||
"1. Flow and readability\n" .
|
||||
"2. SEO optimization (headings, keyword placement)\n" .
|
||||
"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 " .
|
||||
"guide is satisfied in the final output.\n\n" .
|
||||
"Return the polished HTML content only. Do not add commentary.\n\n" .
|
||||
"DRAFT:\n" . $draft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the linking rules section (internal articles + peptide database).
|
||||
*
|
||||
* Fetches published blog posts and peptide pages, formats them as a
|
||||
* reference list so the model uses real URLs instead of fabricating them.
|
||||
*
|
||||
* @return string Complete linking rules block.
|
||||
*/
|
||||
private static function build_linking_rules(): string {
|
||||
$rules = "--- LINKING RULES ---\n";
|
||||
$rules .= "NEVER fabricate or invent URLs. You do NOT have access to external sources.\n";
|
||||
$rules .= "Only use the internal links listed below when linking within the article.\n";
|
||||
$rules .= "If no listed page is relevant to a section, do not insert a link.\n";
|
||||
$rules .= "IMPORTANT: Whenever ANY peptide name from the peptide database list below\n";
|
||||
$rules .= "is mentioned in the article, ALWAYS link the FIRST mention to its database page.\n\n";
|
||||
|
||||
$rules .= self::build_article_links();
|
||||
$rules .= "\n";
|
||||
$rules .= self::build_peptide_links();
|
||||
|
||||
$rules .= "--- END LINKING RULES ---";
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a reference list of published blog posts for internal linking.
|
||||
*
|
||||
* @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',
|
||||
] );
|
||||
|
||||
if ( empty( $posts ) ) {
|
||||
return "No published articles available for internal linking yet.\n";
|
||||
}
|
||||
|
||||
$lines = [ "Available article links (use where topically relevant):\n" ];
|
||||
foreach ( $posts as $post_id ) {
|
||||
$title = get_the_title( $post_id );
|
||||
$url = get_permalink( $post_id );
|
||||
if ( $title && $url ) {
|
||||
$lines[] = "- {$title}: {$url}";
|
||||
}
|
||||
}
|
||||
|
||||
return implode( "\n", $lines ) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a reference list of all published peptide database pages.
|
||||
*
|
||||
* Every peptide mentioned in an article must link to its database page
|
||||
* on first mention. This gives the model the real URLs to use.
|
||||
*
|
||||
* @return string Formatted peptide link list, or empty if CPT unavailable.
|
||||
*/
|
||||
private static function build_peptide_links(): string {
|
||||
// PR Core may not be active — guard against missing post type.
|
||||
if ( ! post_type_exists( 'pr_peptide' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$peptides = get_posts( [
|
||||
'post_type' => 'pr_peptide',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 200,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC',
|
||||
'fields' => 'ids',
|
||||
] );
|
||||
|
||||
if ( empty( $peptides ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$lines = [ "Peptide database pages — ALWAYS link the first mention of each peptide:\n" ];
|
||||
foreach ( $peptides as $peptide_id ) {
|
||||
$title = get_the_title( $peptide_id );
|
||||
$url = get_permalink( $peptide_id );
|
||||
if ( $title && $url ) {
|
||||
$lines[] = "- {$title}: {$url}";
|
||||
}
|
||||
}
|
||||
|
||||
return implode( "\n", $lines ) . "\n";
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ declare(strict_types=1);
|
||||
* Plugin Name: 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.
|
||||
* Version: 0.6.0
|
||||
* Version: 0.6.1
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 7.4
|
||||
* Author: PeptideRepo
|
||||
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
| and limits without magic strings.
|
||||
*/
|
||||
|
||||
define( 'PRAUTOBLOGGER_VERSION', '0.6.0' );
|
||||
define( 'PRAUTOBLOGGER_VERSION', '0.6.1' );
|
||||
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
|
||||
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
|
||||
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
|
||||
Reference in New Issue
Block a user