diff --git a/.github/mu-plugins/autoblogger-deploy-receiver.php b/.github/mu-plugins/prautoblogger-deploy-receiver.php
old mode 100755
new mode 100644
similarity index 59%
rename from .github/mu-plugins/autoblogger-deploy-receiver.php
rename to .github/mu-plugins/prautoblogger-deploy-receiver.php
index abdb06d..007ea77
--- a/.github/mu-plugins/autoblogger-deploy-receiver.php
+++ b/.github/mu-plugins/prautoblogger-deploy-receiver.php
@@ -1,19 +1,19 @@
'POST',
- 'callback' => 'autoblogger_deploy_handle',
- 'permission_callback' => 'autoblogger_deploy_check_auth',
+ 'callback' => 'prautoblogger_deploy_handle',
+ 'permission_callback' => 'prautoblogger_deploy_check_auth',
] );
// Health check endpoint (GET) — useful for monitoring.
- register_rest_route( 'autoblogger-deploy/v1', '/status', [
+ register_rest_route( 'prautoblogger-deploy/v1', '/status', [
'methods' => 'GET',
'callback' => function () {
return [
'status' => 'ready',
- 'plugin' => is_dir( WP_PLUGIN_DIR . '/autoblogger' ) ? 'installed' : 'not_installed',
- 'active' => is_plugin_active( 'autoblogger/autoblogger.php' ),
- 'version' => autoblogger_deploy_get_installed_version(),
+ 'plugin' => is_dir( WP_PLUGIN_DIR . '/prautoblogger' ) ? 'installed' : 'not_installed',
+ 'active' => is_plugin_active( 'prautoblogger/prautoblogger.php' ),
+ 'version' => prautoblogger_deploy_get_installed_version(),
];
},
- 'permission_callback' => 'autoblogger_deploy_check_auth',
+ 'permission_callback' => 'prautoblogger_deploy_check_auth',
] );
}
@@ -60,20 +60,20 @@ function autoblogger_deploy_register_route(): void {
* Validate the deploy key from the request header.
* Uses hash_equals() for timing-safe comparison.
*/
-function autoblogger_deploy_check_auth( WP_REST_Request $request ): bool {
+function prautoblogger_deploy_check_auth( WP_REST_Request $request ): bool {
$key = $request->get_header( 'X-Deploy-Key' );
if ( null === $key || '' === $key ) {
return false;
}
- return hash_equals( (string) AUTOBLOGGER_DEPLOY_KEY, $key );
+ return hash_equals( (string) PRAUTOBLOGGER_DEPLOY_KEY, $key );
}
/**
* Handle a deploy request: receive zip, validate, extract, activate.
*/
-function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response|WP_Error {
+function prautoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response|WP_Error {
// Rate limit: 1 deploy per 60 seconds.
- $lock = get_transient( 'autoblogger_deploy_lock' );
+ $lock = get_transient( 'prautoblogger_deploy_lock' );
if ( false !== $lock ) {
return new WP_Error(
'rate_limited',
@@ -81,12 +81,12 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
[ 'status' => 429 ]
);
}
- set_transient( 'autoblogger_deploy_lock', time(), 60 );
+ set_transient( 'prautoblogger_deploy_lock', time(), 60 );
// 1. Validate uploaded file exists.
$files = $request->get_file_params();
if ( empty( $files['plugin']['tmp_name'] ) || ! is_uploaded_file( $files['plugin']['tmp_name'] ) ) {
- delete_transient( 'autoblogger_deploy_lock' );
+ delete_transient( 'prautoblogger_deploy_lock' );
return new WP_Error( 'no_file', 'No plugin zip uploaded.', [ 'status' => 400 ] );
}
@@ -95,13 +95,13 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
// 2. Validate it's a real zip and contains our plugin.
$zip = new ZipArchive();
if ( true !== $zip->open( $tmp_file ) ) {
- delete_transient( 'autoblogger_deploy_lock' );
+ delete_transient( 'prautoblogger_deploy_lock' );
return new WP_Error( 'invalid_zip', 'Uploaded file is not a valid zip.', [ 'status' => 400 ] );
}
$has_main_file = false;
for ( $i = 0; $i < $zip->numFiles; $i++ ) {
- if ( 'autoblogger/autoblogger.php' === $zip->getNameIndex( $i ) ) {
+ if ( 'prautoblogger/prautoblogger.php' === $zip->getNameIndex( $i ) ) {
$has_main_file = true;
break;
}
@@ -109,40 +109,40 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
if ( ! $has_main_file ) {
$zip->close();
- delete_transient( 'autoblogger_deploy_lock' );
+ delete_transient( 'prautoblogger_deploy_lock' );
return new WP_Error(
'invalid_plugin',
- 'Zip must contain autoblogger/autoblogger.php at root.',
+ 'Zip must contain prautoblogger/prautoblogger.php at root.',
[ 'status' => 400 ]
);
}
// 3. Read version from the incoming zip before extraction.
- $incoming_main = $zip->getFromName( 'autoblogger/autoblogger.php' );
+ $incoming_main = $zip->getFromName( 'prautoblogger/prautoblogger.php' );
$incoming_version = 'unknown';
if ( $incoming_main && preg_match( '/Version:\s*([0-9]+\.[0-9]+\.[0-9]+)/', $incoming_main, $m ) ) {
$incoming_version = $m[1];
}
- $plugin_dir = WP_PLUGIN_DIR . '/autoblogger';
+ $plugin_dir = WP_PLUGIN_DIR . '/prautoblogger';
$backup_dir = null;
// 4. Deactivate plugin before file operations (prevent fatal errors).
- $was_active = is_plugin_active( 'autoblogger/autoblogger.php' );
+ $was_active = is_plugin_active( 'prautoblogger/prautoblogger.php' );
if ( $was_active ) {
- deactivate_plugins( 'autoblogger/autoblogger.php', true );
+ deactivate_plugins( 'prautoblogger/prautoblogger.php', true );
}
// 5. Backup current installation if it exists.
if ( is_dir( $plugin_dir ) ) {
- $backup_dir = WP_PLUGIN_DIR . '/autoblogger-backup-' . time();
+ $backup_dir = WP_PLUGIN_DIR . '/prautoblogger-backup-' . time();
if ( ! rename( $plugin_dir, $backup_dir ) ) {
$zip->close();
// Re-activate if we deactivated.
if ( $was_active ) {
- activate_plugin( 'autoblogger/autoblogger.php' );
+ activate_plugin( 'prautoblogger/prautoblogger.php' );
}
- delete_transient( 'autoblogger_deploy_lock' );
+ delete_transient( 'prautoblogger_deploy_lock' );
return new WP_Error( 'backup_failed', 'Could not backup current plugin.', [ 'status' => 500 ] );
}
}
@@ -151,30 +151,30 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
$extract_ok = $zip->extractTo( WP_PLUGIN_DIR );
$zip->close();
- if ( ! $extract_ok || ! file_exists( $plugin_dir . '/autoblogger.php' ) ) {
+ if ( ! $extract_ok || ! file_exists( $plugin_dir . '/prautoblogger.php' ) ) {
// Rollback: restore backup.
if ( $backup_dir && is_dir( $backup_dir ) ) {
rename( $backup_dir, $plugin_dir );
}
if ( $was_active ) {
- activate_plugin( 'autoblogger/autoblogger.php' );
+ activate_plugin( 'prautoblogger/prautoblogger.php' );
}
- delete_transient( 'autoblogger_deploy_lock' );
- error_log( '[AutoBlogger Deploy] Extraction failed. Rolled back to previous version.' );
+ delete_transient( 'prautoblogger_deploy_lock' );
+ error_log( '[PRAutoBlogger Deploy] Extraction failed. Rolled back to previous version.' );
return new WP_Error( 'extract_failed', 'Zip extraction failed. Rolled back.', [ 'status' => 500 ] );
}
// 7. Activate plugin.
- $activation_result = activate_plugin( 'autoblogger/autoblogger.php' );
+ $activation_result = activate_plugin( 'prautoblogger/prautoblogger.php' );
if ( is_wp_error( $activation_result ) ) {
// Activation failed — rollback.
- autoblogger_deploy_rmdir_recursive( $plugin_dir );
+ prautoblogger_deploy_rmdir_recursive( $plugin_dir );
if ( $backup_dir && is_dir( $backup_dir ) ) {
rename( $backup_dir, $plugin_dir );
- activate_plugin( 'autoblogger/autoblogger.php' );
+ activate_plugin( 'prautoblogger/prautoblogger.php' );
}
- delete_transient( 'autoblogger_deploy_lock' );
- error_log( '[AutoBlogger Deploy] Activation failed: ' . $activation_result->get_error_message() . '. Rolled back.' );
+ delete_transient( 'prautoblogger_deploy_lock' );
+ error_log( '[PRAutoBlogger Deploy] Activation failed: ' . $activation_result->get_error_message() . '. Rolled back.' );
return new WP_Error(
'activation_failed',
'Plugin activation failed: ' . $activation_result->get_error_message() . '. Rolled back.',
@@ -184,12 +184,12 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
// 8. Clean up backup.
if ( $backup_dir && is_dir( $backup_dir ) ) {
- autoblogger_deploy_rmdir_recursive( $backup_dir );
+ prautoblogger_deploy_rmdir_recursive( $backup_dir );
}
- $old_version = autoblogger_deploy_get_installed_version();
+ $old_version = prautoblogger_deploy_get_installed_version();
error_log( sprintf(
- '[AutoBlogger Deploy] Successfully deployed v%s (was: %s) at %s',
+ '[PRAutoBlogger Deploy] Successfully deployed v%s (was: %s) at %s',
$incoming_version,
$old_version ?: 'fresh install',
gmdate( 'Y-m-d H:i:s' )
@@ -198,15 +198,15 @@ function autoblogger_deploy_handle( WP_REST_Request $request ): WP_REST_Response
return new WP_REST_Response( [
'success' => true,
'version' => $incoming_version,
- 'message' => "AutoBlogger v{$incoming_version} deployed and activated.",
+ 'message' => "PRAutoBlogger v{$incoming_version} deployed and activated.",
], 200 );
}
/**
* Get the currently installed plugin version from the main file header.
*/
-function autoblogger_deploy_get_installed_version(): string {
- $main_file = WP_PLUGIN_DIR . '/autoblogger/autoblogger.php';
+function prautoblogger_deploy_get_installed_version(): string {
+ $main_file = WP_PLUGIN_DIR . '/prautoblogger/prautoblogger.php';
if ( ! file_exists( $main_file ) ) {
return '';
}
@@ -217,7 +217,7 @@ function autoblogger_deploy_get_installed_version(): string {
/**
* Recursively delete a directory.
*/
-function autoblogger_deploy_rmdir_recursive( string $dir ): void {
+function prautoblogger_deploy_rmdir_recursive( string $dir ): void {
if ( ! is_dir( $dir ) ) {
return;
}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index ede72f5..6a414e2 100755
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,17 +1,17 @@
-# AutoBlogger CI/CD Pipeline
+# PRAutoBlogger CI/CD Pipeline
#
# Triggers on every push to main. Runs PHP lint and coding standards checks,
# packages the plugin into a clean zip (excluding dev files), and deploys it
# to the live WordPress site via the deploy-receiver mu-plugin REST endpoint.
#
# Required GitHub Secrets:
-# DEPLOY_URL — https://peptiderepo.com/wp-json/autoblogger-deploy/v1/deploy
-# DEPLOY_KEY — The token matching AUTOBLOGGER_DEPLOY_KEY in wp-config.php
+# DEPLOY_URL — https://peptiderepo.com/wp-json/prautoblogger-deploy/v1/deploy
+# DEPLOY_KEY — The token matching PRAUTOBLOGGER_DEPLOY_KEY in wp-config.php
#
-# @see .github/mu-plugins/autoblogger-deploy-receiver.php — Server-side endpoint.
+# @see .github/mu-plugins/prautoblogger-deploy-receiver.php — Server-side endpoint.
# @see scripts/deploy.sh — Manual deploy script.
-name: Deploy AutoBlogger
+name: Deploy PRAutoBlogger
on:
push:
@@ -106,22 +106,22 @@ jobs:
- name: Read plugin version
id: version
run: |
- VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" autoblogger.php)
+ VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" prautoblogger.php)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Plugin version: $VERSION"
- name: Build plugin zip
run: |
# Create a clean build directory
- mkdir -p build/autoblogger
+ mkdir -p build/prautoblogger
# Copy only production files (exclude dev/CI artifacts)
- rsync -av --exclude-from='.distignore' ./ build/autoblogger/
+ rsync -av --exclude-from='.distignore' ./ build/prautoblogger/
# Create the zip
cd build
- zip -r "autoblogger-${{ steps.version.outputs.version }}.zip" autoblogger/
- ls -lh "autoblogger-${{ steps.version.outputs.version }}.zip"
+ zip -r "prautoblogger-${{ steps.version.outputs.version }}.zip" prautoblogger/
+ ls -lh "prautoblogger-${{ steps.version.outputs.version }}.zip"
- name: Deploy to WordPress
env:
@@ -129,9 +129,9 @@ jobs:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
VERSION="${{ steps.version.outputs.version }}"
- ZIP_PATH="build/autoblogger-${VERSION}.zip"
+ ZIP_PATH="build/prautoblogger-${VERSION}.zip"
- echo "Deploying AutoBlogger v${VERSION} to production..."
+ echo "Deploying PRAutoBlogger v${VERSION} to production..."
RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST "${DEPLOY_URL}" \
@@ -154,6 +154,6 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
- name: autoblogger-${{ steps.version.outputs.version }}
- path: build/autoblogger-${{ steps.version.outputs.version }}.zip
+ name: prautoblogger-${{ steps.version.outputs.version }}
+ path: build/prautoblogger-${{ steps.version.outputs.version }}.zip
retention-days: 30
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 4a33ab3..a02b111 100755
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,6 +1,6 @@
-# AutoBlogger — Architecture
+# PRAutoBlogger — Architecture
-AutoBlogger is a WordPress plugin that monitors social media (starting with Reddit) for recurring questions, complaints, and comparisons in a configured niche, uses LLM agents (via OpenRouter) to generate high-quality blog articles from those insights, runs them through a chief editor agent for quality review, and auto-publishes them on a configurable daily schedule. All collected data and generation metrics are stored for a self-improvement feedback loop.
+PRAutoBlogger is a WordPress plugin that monitors social media (starting with Reddit) for recurring questions, complaints, and comparisons in a configured niche, uses LLM agents (via OpenRouter) to generate high-quality blog articles from those insights, runs them through a chief editor agent for quality review, and auto-publishes them on a configurable daily schedule. All collected data and generation metrics are stored for a self-improvement feedback loop.
---
@@ -57,7 +57,7 @@ AutoBlogger is a WordPress plugin that monitors social media (starting with Redd
│ 6. Publisher │ Creates/updates WordPress post
│ │ Sets categories, tags, featured image
│ │ Stores generation metadata in post_meta
-│ │ Logs cost/tokens in `ab_generation_log` table
+│ │ Logs cost/tokens in `prab_generation_log` table
└────────────┬────────────┘
│
▼
@@ -75,8 +75,8 @@ AutoBlogger is a WordPress plugin that monitors social media (starting with Redd
## File Tree
```
-autoblogger/
-├── autoblogger.php # Plugin bootstrap — minimal, only hook registration
+prautoblogger/
+├── prautoblogger.php # Plugin bootstrap — minimal, only hook registration
├── uninstall.php # Clean removal of ALL plugin data
├── readme.txt # WordPress.org standard readme
├── composer.json # Autoloading (PSR-4) and dependencies
@@ -86,12 +86,14 @@ autoblogger/
│
├── assets/
│ ├── css/
-│ │ └── admin.css # Admin page styles (wp-admin conventions)
+│ │ ├── admin.css # Admin page styles (wp-admin conventions)
+│ │ └── posts-widget.css # Frontend posts widget styles (uses theme CSS vars)
│ └── js/
-│ └── admin.js # Admin page interactivity (vanilla JS / Alpine.js)
+│ ├── admin.js # Admin page interactivity (vanilla JS / Alpine.js)
+│ └── posts-widget.js # React component for frontend post cards (wp.element)
│
├── includes/
-│ ├── class-autoblogger.php # Main orchestrator — registers all hooks via loader
+│ ├── class-prautoblogger.php # Main orchestrator — registers all hooks via loader
│ ├── class-activator.php # Activation: create DB tables, set defaults, schedule cron
│ ├── class-deactivator.php # Deactivation: clear cron, cleanup transients
│ ├── class-autoloader.php # PSR-4-style autoloader for plugin classes
@@ -131,6 +133,9 @@ autoblogger/
│ │ ├── class-instagram-provider.php # Instagram — stub/future implementation
│ │ └── class-youtube-provider.php # YouTube — stub/future implementation
│ │
+│ ├── frontend/
+│ │ └── class-posts-widget.php # [prautoblogger_posts] shortcode + REST endpoint
+│ │
│ └── models/
│ ├── class-source-data.php # Value object: raw social media post/comment
│ ├── class-analysis-result.php # Value object: analyzed topic with scores
@@ -149,7 +154,7 @@ autoblogger/
│ └── metabox-generation-info.php # Post metabox template
│
├── languages/
-│ └── autoblogger.pot # i18n translation template
+│ └── prautoblogger.pot # i18n translation template
│
└── tests/
├── unit/ # PHPUnit tests (mocked dependencies)
@@ -158,11 +163,56 @@ autoblogger/
---
+## Frontend Widget Data Flow
+
+```
+┌──────────────────────────────┐
+│ WordPress Page/Post │ Contains [prautoblogger_posts] shortcode
+│ (e.g. Home page) │
+└────────────┬─────────────────┘
+ │ Shortcode renders:
+ │ 1.
` mount point
+
+3. Create CSS in `assets/css/{name}.css`:
+ - Use Peptide Starter theme CSS custom properties with fallbacks: `var(--color-text-primary, #f9fafb)`
+ - Prefix all classes with `prab-` to avoid collisions
+ - Include responsive breakpoints, focus-visible states, and loading skeletons
+
+4. Wire hooks in `class-prautoblogger.php`:
+ - Instantiate the PHP class in `register_frontend_hooks()`
+ - Register shortcode on `init`, REST routes on `rest_api_init`
+
+5. Add `'frontend/'` to the autoloader's `$directories` array (already done for the first component)
+
+6. Update ARCHITECTURE.md file tree and add a data flow section
+
+### REST endpoint conventions:
+- Namespace: `prautoblogger/v1`
+- Permission callback: use a filter hook for extensibility (e.g., `prautoblogger_rest_{name}_public`)
+- Sanitize all params with `sanitize_callback` in arg registration
+- Return only the fields the frontend needs — no unnecessary data
+- Set `Cache-Control` header for cacheable responses
+
+---
+
## Code Quality Rules
1. **`declare(strict_types=1);`** in every PHP file
@@ -162,7 +201,7 @@ The writing pipeline is an ordered array of stages. Each stage is a class method
4. **Every class has a 3-question preamble docblock:** What / Who triggers / Dependencies
5. **Every public method has a docblock** with `@param`, `@return`, and side effects
6. **`@see` references** at the top of files that participate in multi-class flows
-7. **All strings translatable** via `__()` / `_e()` with `autoblogger` text domain
+7. **All strings translatable** via `__()` / `_e()` with `prautoblogger` text domain
8. **No magic methods** without explicit justification documented in code
9. **No `echo` of raw data** — always escape with `esc_html()`, `esc_attr()`, etc.
10. **Nonce on every form/AJAX** — `wp_nonce_field()` / `check_ajax_referer()`
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 8ba8896..4e00cd3 100755
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -1,5 +1,5 @@
/**
- * AutoBlogger admin styles — modern SaaS-inspired UI.
+ * PRAutoBlogger admin styles — modern SaaS-inspired UI.
*
* Design language: clean cards, subtle shadows, accent color #4f46e5 (indigo),
* neutral grays from WordPress admin palette. Follows wp-admin conventions
@@ -72,13 +72,13 @@
.ab-text-danger { color: var(--ab-danger); }
/* ── Status message ──────────────────────────────────────────────────── */
-#autoblogger-status-message {
+#prautoblogger-status-message {
margin: 16px 0; padding: 12px 16px; border-radius: var(--ab-radius);
border-left: 4px solid var(--ab-success); background: var(--ab-card-bg);
font-size: 13px; box-shadow: var(--ab-shadow);
}
-#autoblogger-status-message.error { border-left-color: var(--ab-danger); }
-#autoblogger-status-message.hidden { display: none; }
+#prautoblogger-status-message.error { border-left-color: var(--ab-danger); }
+#prautoblogger-status-message.hidden { display: none; }
/* ── Layout: sidebar tabs + content ──────────────────────────────────── */
.ab-layout {
@@ -176,48 +176,48 @@ input[type="time"].ab-input { max-width: 160px; }
}
/* ── Metrics page cards (preserved from before) ──────────────────────── */
-.autoblogger-cards { display: flex; gap: 16px; margin: 20px 0; flex-wrap: wrap; }
-.autoblogger-card { background: var(--ab-card-bg); border: 1px solid var(--ab-border); border-radius: var(--ab-radius); padding: 20px; min-width: 200px; flex: 1; }
-.autoblogger-card h3 { margin: 0 0 8px; font-size: 13px; color: var(--ab-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
-.autoblogger-card-value { font-size: 28px; font-weight: 600; color: var(--ab-text); line-height: 1.2; }
-.autoblogger-card-value-small { font-size: 18px; }
-.autoblogger-card-sub { margin-top: 4px; font-size: 13px; color: var(--ab-text-muted); }
-.autoblogger-card-warning { border-left: 4px solid var(--ab-warning); }
-.autoblogger-card-danger { border-left: 4px solid var(--ab-danger); }
-.autoblogger-budget-bar { margin-top: 12px; height: 8px; background: var(--ab-bg); border-radius: 4px; overflow: hidden; }
-.autoblogger-budget-fill { height: 100%; background: var(--ab-primary); border-radius: 4px; transition: width 0.3s ease; }
-.autoblogger-card-warning .autoblogger-budget-fill { background: var(--ab-warning); }
-.autoblogger-card-danger .autoblogger-budget-fill { background: var(--ab-danger); }
+.prautoblogger-cards { display: flex; gap: 16px; margin: 20px 0; flex-wrap: wrap; }
+.prautoblogger-card { background: var(--ab-card-bg); border: 1px solid var(--ab-border); border-radius: var(--ab-radius); padding: 20px; min-width: 200px; flex: 1; }
+.prautoblogger-card h3 { margin: 0 0 8px; font-size: 13px; color: var(--ab-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
+.prautoblogger-card-value { font-size: 28px; font-weight: 600; color: var(--ab-text); line-height: 1.2; }
+.prautoblogger-card-value-small { font-size: 18px; }
+.prautoblogger-card-sub { margin-top: 4px; font-size: 13px; color: var(--ab-text-muted); }
+.prautoblogger-card-warning { border-left: 4px solid var(--ab-warning); }
+.prautoblogger-card-danger { border-left: 4px solid var(--ab-danger); }
+.prautoblogger-budget-bar { margin-top: 12px; height: 8px; background: var(--ab-bg); border-radius: 4px; overflow: hidden; }
+.prautoblogger-budget-fill { height: 100%; background: var(--ab-primary); border-radius: 4px; transition: width 0.3s ease; }
+.prautoblogger-card-warning .prautoblogger-budget-fill { background: var(--ab-warning); }
+.prautoblogger-card-danger .prautoblogger-budget-fill { background: var(--ab-danger); }
/* ── Metabox / verdict (shared) ──────────────────────────────────────── */
-.autoblogger-metabox p { margin: 6px 0; font-size: 12px; }
-.autoblogger-verdict-approved { color: var(--ab-success); font-weight: 600; }
-.autoblogger-verdict-revised { color: var(--ab-warning); font-weight: 600; }
-.autoblogger-verdict-rejected { color: var(--ab-danger); font-weight: 600; }
-.autoblogger-verdict-pending { color: var(--ab-text-muted); font-weight: 600; }
+.prautoblogger-metabox p { margin: 6px 0; font-size: 12px; }
+.prautoblogger-verdict-approved { color: var(--ab-success); font-weight: 600; }
+.prautoblogger-verdict-revised { color: var(--ab-warning); font-weight: 600; }
+.prautoblogger-verdict-rejected { color: var(--ab-danger); font-weight: 600; }
+.prautoblogger-verdict-pending { color: var(--ab-text-muted); font-weight: 600; }
/* ── Metrics tables ──────────────────────────────────────────────────── */
-.autoblogger-metrics .widefat { margin: 10px 0 30px; }
+.prautoblogger-metrics .widefat { margin: 10px 0 30px; }
/* ── Review queue ────────────────────────────────────────────────────── */
-.autoblogger-review-queue .autoblogger-queue-toolbar {
+.prautoblogger-review-queue .prautoblogger-queue-toolbar {
display: flex; align-items: center; gap: 10px;
margin: 15px 0; padding: 10px 12px; background: var(--ab-card-bg);
border: 1px solid var(--ab-border); border-radius: var(--ab-radius);
}
-.autoblogger-review-queue .autoblogger-queue-count { margin-left: auto; color: var(--ab-text-muted); font-size: 13px; }
-.autoblogger-review-queue .autoblogger-queue-table .check-column { width: 30px; padding: 10px 0 10px 10px; }
-.autoblogger-review-queue .autoblogger-queue-actions { white-space: nowrap; }
-.autoblogger-review-queue .autoblogger-queue-actions .button { margin-right: 4px; }
-.autoblogger-review-queue .autoblogger-editor-notes { margin-top: 4px; font-size: 12px; color: var(--ab-text-muted); font-style: italic; }
-.autoblogger-review-queue .autoblogger-empty-queue { margin: 40px 0; text-align: center; color: var(--ab-text-muted); font-size: 14px; }
-.autoblogger-review-queue .autoblogger-pagination { margin: 20px 0; text-align: center; }
-.autoblogger-review-queue .autoblogger-pagination .page-numbers { display: inline-block; padding: 4px 10px; margin: 0 2px; text-decoration: none; }
-.autoblogger-review-queue .autoblogger-pagination .page-numbers.current { background: var(--ab-primary); color: #fff; border-radius: 3px; }
+.prautoblogger-review-queue .prautoblogger-queue-count { margin-left: auto; color: var(--ab-text-muted); font-size: 13px; }
+.prautoblogger-review-queue .prautoblogger-queue-table .check-column { width: 30px; padding: 10px 0 10px 10px; }
+.prautoblogger-review-queue .prautoblogger-queue-actions { white-space: nowrap; }
+.prautoblogger-review-queue .prautoblogger-queue-actions .button { margin-right: 4px; }
+.prautoblogger-review-queue .prautoblogger-editor-notes { margin-top: 4px; font-size: 12px; color: var(--ab-text-muted); font-style: italic; }
+.prautoblogger-review-queue .prautoblogger-empty-queue { margin: 40px 0; text-align: center; color: var(--ab-text-muted); font-size: 14px; }
+.prautoblogger-review-queue .prautoblogger-pagination { margin: 20px 0; text-align: center; }
+.prautoblogger-review-queue .prautoblogger-pagination .page-numbers { display: inline-block; padding: 4px 10px; margin: 0 2px; text-decoration: none; }
+.prautoblogger-review-queue .prautoblogger-pagination .page-numbers.current { background: var(--ab-primary); color: #fff; border-radius: 3px; }
-#autoblogger-queue-status { margin: 10px 0; padding: 10px 15px; border-left: 4px solid var(--ab-success); background: var(--ab-card-bg); }
-#autoblogger-queue-status.error { border-left-color: var(--ab-danger); }
-#autoblogger-queue-status.hidden { display: none; }
+#prautoblogger-queue-status { margin: 10px 0; padding: 10px 15px; border-left: 4px solid var(--ab-success); background: var(--ab-card-bg); }
+#prautoblogger-queue-status.error { border-left-color: var(--ab-danger); }
+#prautoblogger-queue-status.hidden { display: none; }
/* ── Log viewer ──────────────────────────────────────────────────────── */
.ab-log-filters { display: flex; align-items: center; justify-content: space-between; margin: 16px 0; gap: 12px; flex-wrap: wrap; }
@@ -251,9 +251,9 @@ input[type="time"].ab-input { max-width: 160px; }
.ab-log-meta-toggle:hover { color: var(--ab-primary); }
.ab-log-meta { display: none; margin: 8px 0 0; padding: 10px; background: var(--ab-bg); border-radius: 4px; font-size: 12px; overflow-x: auto; max-height: 200px; }
-#autoblogger-log-status { margin: 10px 0; padding: 10px 15px; border-left: 4px solid var(--ab-success); background: var(--ab-card-bg); border-radius: 4px; }
-#autoblogger-log-status.error { border-left-color: var(--ab-danger); }
-#autoblogger-log-status.hidden { display: none; }
+#prautoblogger-log-status { margin: 10px 0; padding: 10px 15px; border-left: 4px solid var(--ab-success); background: var(--ab-card-bg); border-radius: 4px; }
+#prautoblogger-log-status.error { border-left-color: var(--ab-danger); }
+#prautoblogger-log-status.hidden { display: none; }
.ab-log-pagination { margin: 20px 0; text-align: center; }
.ab-log-pagination .page-numbers { display: inline-block; padding: 4px 10px; margin: 0 2px; text-decoration: none; }
@@ -268,6 +268,6 @@ input[type="time"].ab-input { max-width: 160px; }
.ab-panel { padding: 20px 16px; }
.ab-header { flex-direction: column; gap: 12px; align-items: flex-start; }
.ab-stats-bar { flex-direction: column; }
- .autoblogger-cards { flex-direction: column; }
- .autoblogger-review-queue .autoblogger-queue-toolbar { flex-wrap: wrap; }
+ .prautoblogger-cards { flex-direction: column; }
+ .prautoblogger-review-queue .prautoblogger-queue-toolbar { flex-wrap: wrap; }
}
diff --git a/assets/css/posts-widget.css b/assets/css/posts-widget.css
new file mode 100644
index 0000000..2e680c6
--- /dev/null
+++ b/assets/css/posts-widget.css
@@ -0,0 +1,349 @@
+/**
+ * PRAutoBlogger Posts Widget — Frontend Styles
+ *
+ * Uses CSS custom properties from the Peptide Starter theme so the widget
+ * automatically adapts to light/dark mode via data-theme on .
+ *
+ * Theme variable mapping:
+ * --color-text-primary → headings, titles
+ * --color-text-secondary → body text, excerpts
+ * --color-text-tertiary → muted text (dates, reading time)
+ * --color-bg-primary → page background
+ * --color-bg-secondary → card background
+ * --color-bg-tertiary → card hover, placeholder backgrounds
+ * --color-border-default → card borders, dividers
+ * --color-border-subtle → lighter borders
+ * --color-brand-primary → accent (links, hover states, focus rings)
+ * --radius-md → card border radius (8px)
+ * --transition-normal → hover/focus transitions (200ms)
+ *
+ * All classes prefixed with `prab-` to avoid collisions with theme/plugins.
+ *
+ * @see includes/frontend/class-posts-widget.php — PHP class that enqueues this.
+ * @see assets/js/posts-widget.js — React component that uses these classes.
+ * @see peptide-starter/style.css — Theme CSS with variable definitions.
+ */
+
+/* ===== Widget Container ===== */
+.prab-posts-widget {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: var(--spacing-4xl, 80px) var(--spacing-md, 16px);
+}
+
+.prab-widget-header {
+ text-align: center;
+ margin-bottom: var(--spacing-2xl, 48px);
+}
+
+.prab-widget-header h2 {
+ font-size: var(--text-h2, 30px);
+ font-weight: var(--text-h2-weight, 700);
+ line-height: var(--text-h2-lh, 1.25);
+ letter-spacing: var(--text-h2-ls, -0.015em);
+ color: var(--color-text-primary, #f9fafb);
+ margin-bottom: var(--spacing-sm, 8px);
+}
+
+.prab-widget-header p {
+ font-size: var(--text-body-lg, 15px);
+ color: var(--color-text-secondary, #94a3b8);
+ margin: 0;
+}
+
+/* ===== Grid Layout ===== */
+.prab-posts-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: var(--spacing-lg, 24px);
+}
+
+/* ===== Post Card ===== */
+.prab-post-card {
+ background: var(--color-bg-secondary, #1f2937);
+ border: 1px solid var(--color-border-default, #374151);
+ border-radius: var(--radius-md, 8px);
+ overflow: hidden;
+ transition: border-color var(--transition-normal, 200ms ease-out),
+ transform var(--transition-normal, 200ms ease-out),
+ box-shadow var(--transition-normal, 200ms ease-out);
+ display: flex;
+ flex-direction: column;
+ text-decoration: none;
+ color: inherit;
+}
+
+.prab-post-card:hover {
+ border-color: var(--color-text-tertiary, #9ca3af);
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-lg, 0 10px 15px rgba(0, 0, 0, 0.1));
+}
+
+.prab-post-card:focus-visible {
+ outline: var(--focus-width, 2px) solid var(--focus-color, var(--color-brand-primary, #3b82f6));
+ outline-offset: var(--focus-offset, 2px);
+ box-shadow: var(--focus-shadow, 0 0 0 4px rgba(59, 130, 246, 0.15));
+}
+
+/* Featured image area */
+.prab-post-image {
+ width: 100%;
+ height: 180px;
+ background: var(--color-bg-tertiary, #374151);
+ position: relative;
+ overflow: hidden;
+}
+
+.prab-post-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform var(--transition-slow, 300ms ease-in-out);
+}
+
+.prab-post-card:hover .prab-post-image img {
+ transform: scale(1.03);
+}
+
+/* Placeholder for posts without featured images */
+.prab-post-image--placeholder {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.prab-post-image--placeholder svg {
+ width: 48px;
+ height: 48px;
+ opacity: 0.15;
+ color: var(--color-text-primary, #f9fafb);
+}
+
+/* Card body */
+.prab-post-body {
+ padding: var(--spacing-md, 16px) var(--spacing-lg, 24px) var(--spacing-lg, 24px);
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Category badge + date row */
+.prab-post-meta {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm, 8px);
+ margin-bottom: var(--spacing-md, 16px);
+ flex-wrap: wrap;
+}
+
+.prab-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 3px 10px;
+ border-radius: var(--radius-sm, 4px);
+ font-size: var(--text-label, 11px);
+ font-weight: var(--text-label-weight, 500);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+/* Category badge colors — these are semantic and stay consistent in both modes */
+.prab-badge--guides { color: var(--color-status-success, #10b981); background: rgba(16, 185, 129, 0.12); }
+.prab-badge--solutions { color: #8b5cf6; background: rgba(139, 92, 246, 0.12); }
+.prab-badge--comparisons { color: var(--color-status-warning, #f59e0b); background: rgba(245, 158, 11, 0.12); }
+.prab-badge--articles { color: var(--color-brand-primary, #3b82f6); background: rgba(59, 130, 246, 0.12); }
+.prab-badge--default { color: var(--color-text-tertiary, #9ca3af); background: rgba(148, 163, 184, 0.12); }
+
+.prab-post-date {
+ font-size: var(--text-body-sm, 12px);
+ color: var(--color-text-tertiary, #9ca3af);
+}
+
+/* Title */
+.prab-post-title {
+ font-size: var(--text-h5, 17px);
+ font-weight: var(--text-h5-weight, 700);
+ line-height: var(--text-h5-lh, 1.4);
+ color: var(--color-text-primary, #f9fafb);
+ margin-bottom: var(--spacing-sm, 8px);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ transition: color var(--transition-normal, 200ms ease-out);
+}
+
+.prab-post-card:hover .prab-post-title {
+ color: var(--color-brand-primary, #3b82f6);
+}
+
+/* Excerpt */
+.prab-post-excerpt {
+ font-size: var(--text-body-md, 13px);
+ color: var(--color-text-secondary, #d1d5db);
+ line-height: 1.6;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ flex: 1;
+ margin: 0;
+}
+
+/* Footer: reading time + arrow */
+.prab-post-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: var(--spacing-md, 16px);
+ padding-top: var(--spacing-md, 16px);
+ border-top: 1px solid var(--color-border-default, #374151);
+}
+
+.prab-reading-time {
+ font-size: var(--text-body-sm, 12px);
+ color: var(--color-text-tertiary, #9ca3af);
+ display: flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.prab-reading-time svg {
+ width: 14px;
+ height: 14px;
+ opacity: 0.7;
+}
+
+.prab-read-more {
+ font-size: var(--text-body-md, 13px);
+ font-weight: 500;
+ color: var(--color-brand-primary, #3b82f6);
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ transition: gap var(--transition-normal, 200ms ease-out);
+}
+
+.prab-post-card:hover .prab-read-more {
+ gap: 8px;
+}
+
+.prab-read-more svg {
+ width: 16px;
+ height: 16px;
+}
+
+/* ===== View All Link ===== */
+.prab-view-all {
+ text-align: center;
+ margin-top: var(--spacing-2xl, 48px);
+}
+
+.prab-view-all a {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--spacing-sm, 8px);
+ padding: var(--spacing-md, 16px) var(--spacing-xl, 32px);
+ background: transparent;
+ border: 1px solid var(--color-border-default, #374151);
+ border-radius: var(--radius-md, 8px);
+ color: var(--color-text-primary, #f9fafb);
+ font-size: var(--text-body-md, 13px);
+ font-weight: 500;
+ text-decoration: none;
+ transition: border-color var(--transition-normal, 200ms ease-out),
+ background var(--transition-normal, 200ms ease-out);
+}
+
+.prab-view-all a:hover {
+ border-color: var(--color-brand-primary, #3b82f6);
+ background: rgba(59, 130, 246, 0.08);
+}
+
+.prab-view-all a:focus-visible {
+ outline: var(--focus-width, 2px) solid var(--focus-color, var(--color-brand-primary, #3b82f6));
+ outline-offset: var(--focus-offset, 2px);
+ box-shadow: var(--focus-shadow, 0 0 0 4px rgba(59, 130, 246, 0.15));
+}
+
+/* ===== Loading Skeleton ===== */
+.prab-skeleton {
+ background: linear-gradient(
+ 90deg,
+ var(--color-bg-secondary, #1f2937) 25%,
+ var(--color-bg-tertiary, #374151) 50%,
+ var(--color-bg-secondary, #1f2937) 75%
+ );
+ background-size: 200% 100%;
+ animation: prab-shimmer 1.5s infinite;
+ border-radius: var(--radius-sm, 4px);
+}
+
+@keyframes prab-shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+.prab-skeleton-image { height: 180px; border-radius: var(--radius-md, 8px) var(--radius-md, 8px) 0 0; }
+.prab-skeleton-badge { width: 60px; height: 20px; }
+.prab-skeleton-title { height: 22px; width: 85%; margin-bottom: 8px; }
+.prab-skeleton-title-2 { height: 22px; width: 60%; margin-bottom: 12px; }
+.prab-skeleton-text { height: 16px; width: 100%; margin-bottom: 6px; }
+.prab-skeleton-text-2 { height: 16px; width: 75%; }
+
+/* ===== Empty State ===== */
+.prab-empty-state {
+ text-align: center;
+ padding: var(--spacing-4xl, 80px) var(--spacing-md, 16px);
+ background: var(--color-bg-secondary, #1f2937);
+ border: 1px solid var(--color-border-default, #374151);
+ border-radius: var(--radius-md, 8px);
+}
+
+.prab-empty-state svg {
+ width: 64px;
+ height: 64px;
+ color: var(--color-text-tertiary, #9ca3af);
+ margin-bottom: var(--spacing-md, 16px);
+ opacity: 0.5;
+}
+
+.prab-empty-state h3 {
+ font-size: var(--text-h4, 20px);
+ font-weight: var(--text-h4-weight, 600);
+ color: var(--color-text-primary, #f9fafb);
+ margin-bottom: var(--spacing-sm, 8px);
+}
+
+.prab-empty-state p {
+ font-size: var(--text-body-md, 13px);
+ color: var(--color-text-secondary, #d1d5db);
+ margin: 0;
+}
+
+/* ===== Error State ===== */
+.prab-error-state {
+ text-align: center;
+ padding: var(--spacing-2xl, 48px) var(--spacing-md, 16px);
+ color: var(--color-text-secondary, #d1d5db);
+ font-size: var(--text-body-md, 13px);
+}
+
+/* ===== Responsive ===== */
+@media (max-width: 479px) {
+ .prab-posts-grid {
+ grid-template-columns: 1fr;
+ }
+ .prab-widget-header h2 {
+ font-size: var(--text-h3, 24px);
+ }
+ .prab-posts-widget {
+ padding: var(--spacing-2xl, 48px) var(--spacing-md, 16px);
+ }
+}
+
+@media (min-width: 480px) and (max-width: 767px) {
+ .prab-posts-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 93dc9e6..e258f44 100755
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -1,15 +1,15 @@
/**
- * AutoBlogger admin JavaScript.
+ * PRAutoBlogger admin JavaScript.
*
* Handles: tab switching, "Generate Now", "Test Connections", review queue actions.
* Uses vanilla jQuery (WordPress admin already loads it).
*
- * @see admin/class-admin-page.php — Localizes autobloggerAdmin object.
+ * @see admin/class-admin-page.php — Localizes prautobloggerAdmin object.
*/
(function ($) {
'use strict';
- var config = window.autobloggerAdmin || {};
+ var config = window.prautobloggerAdmin || {};
/*
* ── Settings page: tab switching ───────────────────────────────────
@@ -44,7 +44,7 @@
* @param {string} type 'success' or 'error'.
*/
function showStatus(message, type) {
- var $el = $('#autoblogger-status-message');
+ var $el = $('#prautoblogger-status-message');
$el.removeClass('hidden error')
.text(message);
if (type === 'error') {
@@ -55,7 +55,7 @@
/**
* Handle "Generate Now" button click.
*/
- $(document).on('click', '#autoblogger-generate-now', function () {
+ $(document).on('click', '#prautoblogger-generate-now', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
@@ -65,7 +65,7 @@
url: config.ajaxUrl,
method: 'POST',
data: {
- action: 'autoblogger_generate_now',
+ action: 'prautoblogger_generate_now',
nonce: config.generateNonce
},
timeout: 300000, // 5 minutes — generation can be slow.
@@ -100,7 +100,7 @@
/**
* Handle "Test Connections" button click.
*/
- $(document).on('click', '#autoblogger-test-connection', function () {
+ $(document).on('click', '#prautoblogger-test-connection', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
@@ -110,7 +110,7 @@
url: config.ajaxUrl,
method: 'POST',
data: {
- action: 'autoblogger_test_connection',
+ action: 'prautoblogger_test_connection',
nonce: config.testNonce,
service: 'all'
},
@@ -149,7 +149,7 @@
* @param {string} type 'success' or 'error'.
*/
function showQueueStatus(message, type) {
- var $el = $('#autoblogger-queue-status');
+ var $el = $('#prautoblogger-queue-status');
$el.removeClass('hidden error').text(message);
if (type === 'error') {
$el.addClass('error');
@@ -159,7 +159,7 @@
/**
* Handle inline "Approve" button click on review queue.
*/
- $(document).on('click', '.autoblogger-approve-btn', function () {
+ $(document).on('click', '.prautoblogger-approve-btn', function () {
var $btn = $(this);
var postId = $btn.data('post-id');
if ($btn.prop('disabled')) return;
@@ -170,7 +170,7 @@
url: config.ajaxUrl,
method: 'POST',
data: {
- action: 'autoblogger_approve_post',
+ action: 'prautoblogger_approve_post',
nonce: config.reviewNonce,
post_id: postId
},
@@ -194,7 +194,7 @@
/**
* Handle inline "Reject" button click on review queue.
*/
- $(document).on('click', '.autoblogger-reject-btn', function () {
+ $(document).on('click', '.prautoblogger-reject-btn', function () {
var $btn = $(this);
var postId = $btn.data('post-id');
if ($btn.prop('disabled')) return;
@@ -207,7 +207,7 @@
url: config.ajaxUrl,
method: 'POST',
data: {
- action: 'autoblogger_reject_post',
+ action: 'prautoblogger_reject_post',
nonce: config.reviewNonce,
post_id: postId
},
@@ -231,8 +231,8 @@
/**
* Handle "Select All" checkbox on review queue.
*/
- $(document).on('change', '#autoblogger-select-all', function () {
- $('input[name="autoblogger_post_ids[]"]').prop('checked', this.checked);
+ $(document).on('change', '#prautoblogger-select-all', function () {
+ $('input[name="prautoblogger_post_ids[]"]').prop('checked', this.checked);
});
/*
@@ -246,7 +246,7 @@
});
/** Clear old logs button. */
- $(document).on('click', '#autoblogger-clear-logs', function () {
+ $(document).on('click', '#prautoblogger-clear-logs', function () {
var $btn = $(this);
if ($btn.prop('disabled')) return;
if (!confirm('Delete log entries older than 30 days?')) return;
@@ -257,10 +257,10 @@
$.ajax({
url: config.ajaxUrl,
method: 'POST',
- data: { action: 'autoblogger_clear_logs', nonce: nonce, days: 30 },
+ data: { action: 'prautoblogger_clear_logs', nonce: nonce, days: 30 },
timeout: 15000,
success: function (response) {
- var $status = $('#autoblogger-log-status');
+ var $status = $('#prautoblogger-log-status');
if (response.success) {
$status.removeClass('hidden error').text(response.data.message);
setTimeout(function () { window.location.reload(); }, 1200);
diff --git a/assets/js/posts-widget.js b/assets/js/posts-widget.js
new file mode 100644
index 0000000..1386c3c
--- /dev/null
+++ b/assets/js/posts-widget.js
@@ -0,0 +1,350 @@
+/**
+ * PRAutoBlogger Posts Widget — React Frontend Component
+ *
+ * Uses wp.element (WordPress-bundled React) — no build step needed.
+ * Fetches posts from the plugin's REST API and renders a card grid.
+ *
+ * @see includes/frontend/class-posts-widget.php — Registers the shortcode and REST endpoint.
+ * @see assets/css/posts-widget.css — Styles for all widget states.
+ */
+
+/* global wp, prabPostsWidget */
+
+(function () {
+ 'use strict';
+
+ var el = wp.element.createElement;
+ var useState = wp.element.useState;
+ var useEffect = wp.element.useEffect;
+
+ /**
+ * SVG icon components — inline to avoid external dependencies.
+ */
+ var ClockIcon = function () {
+ return el('svg', {
+ viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2'
+ },
+ el('circle', { cx: '12', cy: '12', r: '10' }),
+ el('polyline', { points: '12 6 12 12 16 14' })
+ );
+ };
+
+ var ArrowIcon = function () {
+ return el('svg', {
+ viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2'
+ },
+ el('path', { d: 'M5 12h14M12 5l7 7-7 7' })
+ );
+ };
+
+ var BookIcon = function () {
+ return el('svg', {
+ viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5'
+ },
+ el('path', { d: 'M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25' })
+ );
+ };
+
+ var DocumentIcon = function () {
+ return el('svg', {
+ viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5'
+ },
+ el('path', { d: 'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z' })
+ );
+ };
+
+ /**
+ * Map category slug to a CSS modifier class.
+ *
+ * @param {string} categorySlug The category slug from the REST API.
+ * @return {string} CSS class suffix.
+ */
+ function getCategoryClass(categorySlug) {
+ var slug = (categorySlug || '').toLowerCase();
+ var map = {
+ guides: 'guides',
+ solutions: 'solutions',
+ comparisons: 'comparisons',
+ articles: 'articles'
+ };
+ return map[slug] || 'default';
+ }
+
+ /**
+ * Format a date string to "Mon DD, YYYY".
+ *
+ * @param {string} dateStr ISO 8601 date string.
+ * @return {string} Formatted date.
+ */
+ function formatDate(dateStr) {
+ var date = new Date(dateStr);
+ if (isNaN(date.getTime())) {
+ return 'Recent';
+ }
+ var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear();
+ }
+
+ /**
+ * Estimate reading time from word count.
+ *
+ * @param {number} wordCount Number of words.
+ * @return {number} Minutes to read (minimum 1).
+ */
+ function readingTime(wordCount) {
+ return Math.max(1, Math.round((wordCount || 0) / 250));
+ }
+
+ /**
+ * Strip HTML tags from a string.
+ *
+ * Uses a DOMParser to safely parse HTML without executing scripts.
+ * Falls back to regex stripping if DOMParser is unavailable.
+ *
+ * @param {string} html HTML string.
+ * @return {string} Plain text.
+ */
+ function stripHtml(html) {
+ if (!html) {
+ return '';
+ }
+ if (typeof DOMParser !== 'undefined') {
+ var doc = new DOMParser().parseFromString(html, 'text/html');
+ return doc.body.textContent || '';
+ }
+ // Fallback: regex strip (less accurate but safe).
+ return html.replace(/<[^>]*>/g, '');
+ }
+
+ /**
+ * Single post card component.
+ */
+ var PostCard = function (props) {
+ var post = props.post;
+ var catClass = getCategoryClass(post.category_slug);
+ var minutes = readingTime(post.word_count);
+ var excerpt = stripHtml(post.excerpt);
+
+ // Choose placeholder icon based on category
+ var PlaceholderIcon = post.category_slug === 'guides' ? BookIcon : DocumentIcon;
+
+ return el('a', {
+ href: post.url || '#',
+ className: 'prab-post-card',
+ 'aria-label': post.title
+ },
+ // Image area
+ post.featured_image
+ ? el('div', { className: 'prab-post-image' },
+ el('img', {
+ src: post.featured_image,
+ alt: post.title || '',
+ loading: 'lazy',
+ decoding: 'async'
+ })
+ )
+ : el('div', { className: 'prab-post-image prab-post-image--placeholder' },
+ el(PlaceholderIcon)
+ ),
+
+ // Body
+ el('div', { className: 'prab-post-body' },
+ // Meta row
+ el('div', { className: 'prab-post-meta' },
+ post.category_name
+ ? el('span', {
+ className: 'prab-badge prab-badge--' + catClass
+ }, post.category_name)
+ : null,
+ el('span', { className: 'prab-post-date' }, formatDate(post.date))
+ ),
+
+ // Title
+ el('h3', { className: 'prab-post-title' }, post.title),
+
+ // Excerpt
+ el('p', { className: 'prab-post-excerpt' }, excerpt),
+
+ // Footer
+ el('div', { className: 'prab-post-footer' },
+ el('span', { className: 'prab-reading-time' },
+ el(ClockIcon),
+ minutes + ' min read'
+ ),
+ el('span', { className: 'prab-read-more' },
+ 'Read article',
+ el(ArrowIcon)
+ )
+ )
+ )
+ );
+ };
+
+ /**
+ * Loading skeleton card.
+ */
+ var SkeletonCard = function () {
+ return el('div', { className: 'prab-post-card', style: { pointerEvents: 'none' } },
+ el('div', { className: 'prab-skeleton prab-skeleton-image' }),
+ el('div', { className: 'prab-post-body' },
+ el('div', { className: 'prab-post-meta' },
+ el('div', { className: 'prab-skeleton prab-skeleton-badge' })
+ ),
+ el('div', { className: 'prab-skeleton prab-skeleton-title' }),
+ el('div', { className: 'prab-skeleton prab-skeleton-title-2' }),
+ el('div', { className: 'prab-skeleton prab-skeleton-text', style: { marginTop: '8px' } }),
+ el('div', { className: 'prab-skeleton prab-skeleton-text-2' })
+ )
+ );
+ };
+
+ /**
+ * Empty state component.
+ */
+ var EmptyState = function () {
+ return el('div', { className: 'prab-empty-state' },
+ el(DocumentIcon),
+ el('h3', null, 'No articles yet'),
+ el('p', null, 'New research articles will appear here once they\'re published. Check back soon!')
+ );
+ };
+
+ /**
+ * Error state component.
+ */
+ var ErrorState = function (props) {
+ return el('div', { className: 'prab-error-state' },
+ el('p', null, props.message || 'Unable to load articles. Please try again later.')
+ );
+ };
+
+ /**
+ * Main widget component.
+ *
+ * Fetches posts from the REST API on mount and renders the appropriate state.
+ */
+ var PostsWidget = function (props) {
+ var config = props.config || {};
+ var count = config.count || 6;
+ var title = config.title || 'Latest Research & Insights';
+ var subtitle = config.subtitle || 'Evidence-based articles on peptides, protocols, and emerging research';
+ var category = config.category || '';
+ var archiveUrl = config.archiveUrl || '';
+
+ var stateArr = useState('loading');
+ var state = stateArr[0];
+ var setState = stateArr[1];
+
+ var postsArr = useState([]);
+ var posts = postsArr[0];
+ var setPosts = postsArr[1];
+
+ var errorArr = useState('');
+ var error = errorArr[0];
+ var setError = errorArr[1];
+
+ useEffect(function () {
+ if (!config.restUrl) {
+ setError('Widget misconfigured: REST URL not available.');
+ setState('error');
+ return;
+ }
+
+ var url = config.restUrl + '?per_page=' + count;
+ if (category) {
+ url += '&category=' + encodeURIComponent(category);
+ }
+
+ fetch(url)
+ .then(function (response) {
+ if (!response.ok) {
+ throw new Error('HTTP ' + response.status);
+ }
+ return response.json();
+ })
+ .then(function (data) {
+ setPosts(data);
+ setState(data.length > 0 ? 'loaded' : 'empty');
+ })
+ .catch(function (err) {
+ setError(err.message);
+ setState('error');
+ });
+ }, []); // Fetch only on mount; results persist for page lifetime.
+
+ // Build children array
+ var children = [];
+
+ // Header
+ children.push(
+ el('div', { className: 'prab-widget-header', key: 'header' },
+ el('h2', null, title),
+ el('p', null, subtitle)
+ )
+ );
+
+ // Content based on state
+ if (state === 'loading') {
+ var skeletons = [];
+ for (var i = 0; i < Math.min(count, 3); i++) {
+ skeletons.push(el(SkeletonCard, { key: 'skeleton-' + i }));
+ }
+ children.push(el('div', { className: 'prab-posts-grid', key: 'grid' }, skeletons));
+
+ } else if (state === 'empty') {
+ children.push(el(EmptyState, { key: 'empty' }));
+
+ } else if (state === 'error') {
+ children.push(el(ErrorState, { key: 'error', message: error }));
+
+ } else {
+ // Loaded
+ var cards = posts.map(function (post) {
+ return el(PostCard, { key: post.id, post: post });
+ });
+ children.push(el('div', { className: 'prab-posts-grid', key: 'grid' }, cards));
+
+ // "View all" link
+ if (archiveUrl) {
+ children.push(
+ el('div', { className: 'prab-view-all', key: 'viewall' },
+ el('a', { href: archiveUrl },
+ 'View all articles',
+ el(ArrowIcon)
+ )
+ )
+ );
+ }
+ }
+
+ return el('div', { className: 'prab-posts-widget' }, children);
+ };
+
+ /**
+ * Mount the widget into the DOM.
+ *
+ * Reads configuration from the global `prabPostsWidget` object
+ * set by wp_localize_script in the PHP class.
+ */
+ function mount() {
+ var root = document.getElementById('prab-posts-root');
+ if (!root) {
+ return;
+ }
+
+ var config = window.prabPostsWidget || {};
+ wp.element.render(
+ el(PostsWidget, { config: config }),
+ root
+ );
+ }
+
+ // Mount when DOM is ready.
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', mount);
+ } else {
+ mount();
+ }
+
+})();
diff --git a/composer.json b/composer.json
index 7d3bafb..6854dc4 100755
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
{
- "name": "peptiderepo/autoblogger",
+ "name": "peptiderepo/prautoblogger",
"description": "WordPress plugin that auto-researches and publishes blog content using AI.",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
diff --git a/includes/admin/class-admin-notices.php b/includes/admin/class-admin-notices.php
index 2ba1b37..31c691a 100755
--- a/includes/admin/class-admin-notices.php
+++ b/includes/admin/class-admin-notices.php
@@ -4,12 +4,12 @@ declare(strict_types=1);
/**
* Displays admin notices for onboarding, errors, and budget warnings.
*
- * Triggered by: Autoblogger::register_admin_hooks() on `admin_notices`.
- * Dependencies: Autoblogger_Cost_Tracker (for budget warnings).
+ * Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_notices`.
+ * Dependencies: PRAutoBlogger_Cost_Tracker (for budget warnings).
*
- * @see class-autoblogger.php — Registers the hook.
+ * @see class-prautoblogger.php — Registers the hook.
*/
-class Autoblogger_Admin_Notices {
+class PRAutoBlogger_Admin_Notices {
/**
* Display relevant admin notices.
@@ -32,16 +32,16 @@ class Autoblogger_Admin_Notices {
* @return void
*/
private function check_api_key_notice(): void {
- $api_key = get_option( 'autoblogger_openrouter_api_key', '' );
+ $api_key = get_option( 'prautoblogger_openrouter_api_key', '' );
if ( '' !== $api_key ) {
return;
}
printf(
'
',
- esc_html__( 'AutoBlogger: OpenRouter API key is not configured. Content generation is disabled.', 'autoblogger' ),
- esc_url( admin_url( 'admin.php?page=autoblogger-settings' ) ),
- esc_html__( 'Configure now', 'autoblogger' )
+ esc_html__( 'PRAutoBlogger: OpenRouter API key is not configured. Content generation is disabled.', 'prautoblogger' ),
+ esc_url( admin_url( 'admin.php?page=prautoblogger-settings' ) ),
+ esc_html__( 'Configure now', 'prautoblogger' )
);
}
@@ -51,20 +51,20 @@ class Autoblogger_Admin_Notices {
* @return void
*/
private function check_budget_notice(): void {
- $cost_tracker = new Autoblogger_Cost_Tracker();
+ $cost_tracker = new PRAutoBlogger_Cost_Tracker();
$utilization = $cost_tracker->get_budget_utilization();
if ( $utilization >= 100.0 ) {
printf(
'
',
- esc_html__( 'AutoBlogger: Monthly API budget EXCEEDED. Content generation is paused until next month or budget increase.', 'autoblogger' )
+ esc_html__( 'PRAutoBlogger: Monthly API budget EXCEEDED. Content generation is paused until next month or budget increase.', 'prautoblogger' )
);
} elseif ( $utilization >= 80.0 ) {
printf(
'
',
esc_html( sprintf(
/* translators: %s: budget utilization percentage */
- __( 'AutoBlogger: Monthly API budget is at %.0f%%. Consider increasing the budget or reducing daily article count.', 'autoblogger' ),
+ __( 'PRAutoBlogger: Monthly API budget is at %.0f%%. Consider increasing the budget or reducing daily article count.', 'prautoblogger' ),
$utilization
) )
);
@@ -77,20 +77,20 @@ class Autoblogger_Admin_Notices {
* @return void
*/
private function check_subreddit_notice(): void {
- $subreddits = json_decode( get_option( 'autoblogger_target_subreddits', '[]' ), true );
+ $subreddits = json_decode( get_option( 'prautoblogger_target_subreddits', '[]' ), true );
if ( ! empty( $subreddits ) ) {
return;
}
// Only show on our settings page.
$screen = get_current_screen();
- if ( ! $screen || 'toplevel_page_autoblogger-settings' !== $screen->id ) {
+ if ( ! $screen || 'toplevel_page_prautoblogger-settings' !== $screen->id ) {
return;
}
printf(
'
',
- esc_html__( 'AutoBlogger: No subreddits configured. Add target subreddits in Source Configuration to start collecting data.', 'autoblogger' )
+ esc_html__( 'PRAutoBlogger: No subreddits configured. Add target subreddits in Source Configuration to start collecting data.', 'prautoblogger' )
);
}
}
diff --git a/includes/admin/class-admin-page.php b/includes/admin/class-admin-page.php
index 84dbcb7..74ad0c2 100755
--- a/includes/admin/class-admin-page.php
+++ b/includes/admin/class-admin-page.php
@@ -2,28 +2,28 @@
declare(strict_types=1);
/**
- * Registers and renders the main AutoBlogger settings page in wp-admin.
+ * Registers and renders the main PRAutoBlogger settings page in wp-admin.
*
* Uses the WordPress Settings API for option registration, sanitization, and rendering.
* Settings are defined as a declarative array — adding a new setting is one array entry.
*
- * Triggered by: Autoblogger::register_admin_hooks() registers menu and settings hooks.
- * Dependencies: Autoblogger_Encryption (for API key storage), Autoblogger_Settings_Fields.
+ * Triggered by: PRAutoBlogger::register_admin_hooks() registers menu and settings hooks.
+ * Dependencies: PRAutoBlogger_Encryption (for API key storage), PRAutoBlogger_Settings_Fields.
*
- * @see class-autoblogger.php — Registers hooks that call this class.
+ * @see class-prautoblogger.php — Registers hooks that call this class.
* @see class-settings-fields.php — Defines all settings fields and sections.
* @see CONVENTIONS.md — "How To: Add a New Admin Setting".
*/
-class Autoblogger_Admin_Page {
+class PRAutoBlogger_Admin_Page {
- private const PAGE_SLUG = 'autoblogger-settings';
- private const OPTION_GROUP = 'autoblogger_settings_group';
+ private const PAGE_SLUG = 'prautoblogger-settings';
+ private const OPTION_GROUP = 'prautoblogger_settings_group';
- /** Register the top-level AutoBlogger menu item. */
+ /** Register the top-level PRAutoBlogger menu item. */
public function on_register_menu(): void {
add_menu_page(
- __( 'AutoBlogger', 'autoblogger' ),
- __( 'AutoBlogger', 'autoblogger' ),
+ __( 'PRAutoBlogger', 'prautoblogger' ),
+ __( 'PRAutoBlogger', 'prautoblogger' ),
'manage_options',
self::PAGE_SLUG,
[ $this, 'render_page' ],
@@ -34,15 +34,15 @@ class Autoblogger_Admin_Page {
/** Register all settings with the WordPress Settings API. */
public function on_register_settings(): void {
- $sections = Autoblogger_Settings_Fields::get_sections();
- $fields = Autoblogger_Settings_Fields::get_fields();
+ $sections = PRAutoBlogger_Settings_Fields::get_sections();
+ $fields = PRAutoBlogger_Settings_Fields::get_fields();
foreach ( $sections as $section_id => $section ) {
add_settings_section( $section_id, $section['title'], '__return_empty_string', self::PAGE_SLUG );
}
foreach ( $fields as $field ) {
- $section = $field['section'] ?? 'autoblogger_api';
+ $section = $field['section'] ?? 'prautoblogger_api';
register_setting( self::OPTION_GROUP, $field['id'], [
'type' => $field['wp_type'] ?? 'string',
'sanitize_callback' => [ $this, 'sanitize_field' ],
@@ -52,30 +52,30 @@ class Autoblogger_Admin_Page {
}
}
- /** Enqueue admin CSS and JS on all AutoBlogger admin pages. */
+ /** Enqueue admin CSS and JS on all PRAutoBlogger admin pages. */
public function on_enqueue_assets( string $hook_suffix ): void {
$pages = [
'toplevel_page_' . self::PAGE_SLUG,
- 'autoblogger_page_autoblogger-metrics',
- 'autoblogger_page_autoblogger-review-queue',
- 'autoblogger_page_autoblogger-logs',
+ 'prautoblogger_page_prautoblogger-metrics',
+ 'prautoblogger_page_prautoblogger-review-queue',
+ 'prautoblogger_page_prautoblogger-logs',
];
if ( ! in_array( $hook_suffix, $pages, true ) ) {
return;
}
- wp_enqueue_style( 'autoblogger-admin', AUTOBLOGGER_PLUGIN_URL . 'assets/css/admin.css', [], AUTOBLOGGER_VERSION );
- wp_enqueue_script( 'autoblogger-admin', AUTOBLOGGER_PLUGIN_URL . 'assets/js/admin.js', [ 'jquery' ], AUTOBLOGGER_VERSION, true );
+ wp_enqueue_style( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/admin.css', [], PRAUTOBLOGGER_VERSION );
+ wp_enqueue_script( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/admin.js', [ 'jquery' ], PRAUTOBLOGGER_VERSION, true );
- wp_localize_script( 'autoblogger-admin', 'autobloggerAdmin', [
+ wp_localize_script( 'prautoblogger-admin', 'prautobloggerAdmin', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
- 'generateNonce' => wp_create_nonce( 'autoblogger_generate_now' ),
- 'testNonce' => wp_create_nonce( 'autoblogger_test_connection' ),
- 'reviewNonce' => wp_create_nonce( 'autoblogger_review_queue' ),
- 'generatingText' => __( 'Generating...', 'autoblogger' ),
- 'generateText' => __( 'Generate Now', 'autoblogger' ),
- 'testingText' => __( 'Testing...', 'autoblogger' ),
- 'testText' => __( 'Test Connections', 'autoblogger' ),
+ 'generateNonce' => wp_create_nonce( 'prautoblogger_generate_now' ),
+ 'testNonce' => wp_create_nonce( 'prautoblogger_test_connection' ),
+ 'reviewNonce' => wp_create_nonce( 'prautoblogger_review_queue' ),
+ 'generatingText' => __( 'Generating...', 'prautoblogger' ),
+ 'generateText' => __( 'Generate Now', 'prautoblogger' ),
+ 'testingText' => __( 'Testing...', 'prautoblogger' ),
+ 'testText' => __( 'Test Connections', 'prautoblogger' ),
] );
}
@@ -84,9 +84,9 @@ class Autoblogger_Admin_Page {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
- $sections = Autoblogger_Settings_Fields::get_sections();
- $fields = Autoblogger_Settings_Fields::get_fields();
- include AUTOBLOGGER_PLUGIN_DIR . 'templates/admin/settings-page.php';
+ $sections = PRAutoBlogger_Settings_Fields::get_sections();
+ $fields = PRAutoBlogger_Settings_Fields::get_fields();
+ include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/settings-page.php';
}
/**
@@ -102,7 +102,7 @@ class Autoblogger_Admin_Page {
$value = get_option( $args['id'], $default );
if ( 'password' === $type && '' !== $value ) {
- $decrypted = Autoblogger_Encryption::decrypt( $value );
+ $decrypted = PRAutoBlogger_Encryption::decrypt( $value );
$value = '' !== $decrypted ? '••••••••' : '';
}
@@ -120,7 +120,7 @@ class Autoblogger_Admin_Page {
case 'password':
printf( '
',
- $id, $id, '' !== $value ? esc_attr__( 'Saved (enter new value to change)', 'autoblogger' ) : '' );
+ $id, $id, '' !== $value ? esc_attr__( 'Saved (enter new value to change)', 'prautoblogger' ) : '' );
break;
case 'textarea':
@@ -152,11 +152,11 @@ class Autoblogger_Admin_Page {
break;
case 'author_select':
- wp_dropdown_users( [ 'name' => $id, 'id' => $id, 'selected' => absint( $value ), 'show_option_none' => __( '— Auto (first admin) —', 'autoblogger' ), 'option_none_value' => '0', 'class' => 'ab-select' ] );
+ wp_dropdown_users( [ 'name' => $id, 'id' => $id, 'selected' => absint( $value ), 'show_option_none' => __( '— Auto (first admin) —', 'prautoblogger' ), 'option_none_value' => '0', 'class' => 'ab-select' ] );
break;
case 'category_select':
- wp_dropdown_categories( [ 'name' => $id, 'id' => $id, 'selected' => absint( $value ), 'show_option_none' => __( '— Auto-assign by type —', 'autoblogger' ), 'option_none_value' => '0', 'class' => 'ab-select', 'hide_empty' => false ] );
+ wp_dropdown_categories( [ 'name' => $id, 'id' => $id, 'selected' => absint( $value ), 'show_option_none' => __( '— Auto-assign by type —', 'prautoblogger' ), 'option_none_value' => '0', 'class' => 'ab-select', 'hide_empty' => false ] );
break;
}
@@ -181,13 +181,13 @@ class Autoblogger_Admin_Page {
$option_name = substr( $filter, strlen( 'sanitize_option_' ) );
}
- $encrypted = [ 'autoblogger_openrouter_api_key', 'autoblogger_reddit_client_secret', 'autoblogger_ga4_credentials_json' ];
+ $encrypted = [ 'prautoblogger_openrouter_api_key', 'prautoblogger_reddit_client_secret', 'prautoblogger_ga4_credentials_json' ];
if ( in_array( $option_name, $encrypted, true ) ) {
if ( '' === $value ) { return get_option( $option_name, '' ); }
- return Autoblogger_Encryption::encrypt( sanitize_text_field( $value ) );
+ return PRAutoBlogger_Encryption::encrypt( sanitize_text_field( $value ) );
}
- $json_fields = [ 'autoblogger_target_subreddits', 'autoblogger_topic_exclusions', 'autoblogger_enabled_sources' ];
+ $json_fields = [ 'prautoblogger_target_subreddits', 'prautoblogger_topic_exclusions', 'prautoblogger_enabled_sources' ];
if ( in_array( $option_name, $json_fields, true ) ) {
if ( is_array( $value ) ) { return wp_json_encode( array_values( array_map( 'sanitize_text_field', $value ) ) ); }
if ( is_string( $value ) && '[' !== substr( trim( $value ), 0, 1 ) ) {
@@ -197,7 +197,7 @@ class Autoblogger_Admin_Page {
return sanitize_text_field( $value );
}
- $numeric = [ 'autoblogger_daily_article_target', 'autoblogger_monthly_budget_usd', 'autoblogger_min_word_count', 'autoblogger_max_word_count', 'autoblogger_default_author', 'autoblogger_default_category' ];
+ $numeric = [ 'prautoblogger_daily_article_target', 'prautoblogger_monthly_budget_usd', 'prautoblogger_min_word_count', 'prautoblogger_max_word_count', 'prautoblogger_default_author', 'prautoblogger_default_category' ];
if ( in_array( $option_name, $numeric, true ) ) {
return is_numeric( $value ) ? $value : 0;
}
diff --git a/includes/admin/class-dashboard-widget.php b/includes/admin/class-dashboard-widget.php
index b0b075b..2d78921 100755
--- a/includes/admin/class-dashboard-widget.php
+++ b/includes/admin/class-dashboard-widget.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * WordPress Dashboard widget showing AutoBlogger generation status at a glance.
+ * WordPress Dashboard widget showing PRAutoBlogger generation status at a glance.
*
* Displays: last generation time, articles generated today, monthly cost,
* and budget utilization.
*
* Triggered by: Could be registered via wp_dashboard_setup hook.
- * Dependencies: Autoblogger_Cost_Tracker.
+ * Dependencies: PRAutoBlogger_Cost_Tracker.
*
- * @see class-autoblogger.php — Can register this on wp_dashboard_setup.
+ * @see class-prautoblogger.php — Can register this on wp_dashboard_setup.
*/
-class Autoblogger_Dashboard_Widget {
+class PRAutoBlogger_Dashboard_Widget {
/**
* Register the dashboard widget.
@@ -25,8 +25,8 @@ class Autoblogger_Dashboard_Widget {
}
wp_add_dashboard_widget(
- 'autoblogger_status',
- __( 'AutoBlogger Status', 'autoblogger' ),
+ 'prautoblogger_status',
+ __( 'PRAutoBlogger Status', 'prautoblogger' ),
[ $this, 'render_widget' ]
);
}
@@ -37,15 +37,15 @@ class Autoblogger_Dashboard_Widget {
* @return void
*/
public function render_widget(): void {
- $cost_tracker = new Autoblogger_Cost_Tracker();
+ $cost_tracker = new PRAutoBlogger_Cost_Tracker();
$monthly_spend = $cost_tracker->get_monthly_spend();
- $budget = (float) get_option( 'autoblogger_monthly_budget_usd', 50.00 );
+ $budget = (float) get_option( 'prautoblogger_monthly_budget_usd', 50.00 );
$utilization = $cost_tracker->get_budget_utilization();
- $next_run = wp_next_scheduled( 'autoblogger_daily_generation' );
+ $next_run = wp_next_scheduled( 'prautoblogger_daily_generation' );
printf( '
%s $%.2f / $%.2f (%.0f%%)
',
- esc_html__( 'Monthly Spend:', 'autoblogger' ),
+ esc_html__( 'Monthly Spend:', 'prautoblogger' ),
$monthly_spend,
$budget,
$utilization
@@ -53,15 +53,15 @@ class Autoblogger_Dashboard_Widget {
if ( false !== $next_run ) {
printf( '
%s %s
',
- esc_html__( 'Next Generation:', 'autoblogger' ),
+ esc_html__( 'Next Generation:', 'prautoblogger' ),
esc_html( wp_date( 'M j, Y g:i A', $next_run ) )
);
}
printf(
'
%s
',
- esc_url( admin_url( 'admin.php?page=autoblogger-metrics' ) ),
- esc_html__( 'View Full Metrics', 'autoblogger' )
+ esc_url( admin_url( 'admin.php?page=prautoblogger-metrics' ) ),
+ esc_html__( 'View Full Metrics', 'prautoblogger' )
);
}
}
diff --git a/includes/admin/class-log-viewer.php b/includes/admin/class-log-viewer.php
index 57cc1d7..ebf72f0 100755
--- a/includes/admin/class-log-viewer.php
+++ b/includes/admin/class-log-viewer.php
@@ -6,29 +6,29 @@ declare(strict_types=1);
*
* Provides level filtering (error/warning/info/debug), text search,
* pagination, and a one-click "Clear Old Logs" action. Reads from the
- * ab_event_log table via Autoblogger_Logger::query().
+ * prab_event_log table via PRAutoBlogger_Logger::query().
*
- * Triggered by: Autoblogger::register_admin_hooks() on `admin_menu`.
- * Dependencies: Autoblogger_Logger.
+ * Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_menu`.
+ * Dependencies: PRAutoBlogger_Logger.
*
* @see core/class-logger.php — Writes log entries and provides query().
* @see templates/admin/log-viewer.php — Renders the HTML.
- * @see class-autoblogger.php — Registers the AJAX clear action.
+ * @see class-prautoblogger.php — Registers the AJAX clear action.
*/
-class Autoblogger_Log_Viewer {
+class PRAutoBlogger_Log_Viewer {
/**
- * Register the log viewer submenu page under AutoBlogger.
+ * Register the log viewer submenu page under PRAutoBlogger.
*
* @return void
*/
public function on_register_menu(): void {
add_submenu_page(
- 'autoblogger-settings',
- __( 'Activity Log', 'autoblogger' ),
- __( 'Activity Log', 'autoblogger' ),
+ 'prautoblogger-settings',
+ __( 'Activity Log', 'prautoblogger' ),
+ __( 'Activity Log', 'prautoblogger' ),
'manage_options',
- 'autoblogger-logs',
+ 'prautoblogger-logs',
[ $this, 'render_page' ]
);
}
@@ -47,36 +47,36 @@ class Autoblogger_Log_Viewer {
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
$paged = isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1;
- $result = Autoblogger_Logger::query( $level, $search, $paged, 50 );
+ $result = PRAutoBlogger_Logger::query( $level, $search, $paged, 50 );
$rows = $result['rows'];
$total = $result['total'];
$total_pages = (int) ceil( $total / 50 );
- include AUTOBLOGGER_PLUGIN_DIR . 'templates/admin/log-viewer.php';
+ include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/log-viewer.php';
}
/**
* AJAX handler: clear logs older than 30 days.
*
- * Side effects: deletes rows from ab_event_log.
+ * Side effects: deletes rows from prab_event_log.
*
* @return void
*/
public function on_ajax_clear_logs(): void {
- check_ajax_referer( 'autoblogger_clear_logs', 'nonce' );
+ check_ajax_referer( 'prautoblogger_clear_logs', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'autoblogger' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
return;
}
$days = isset( $_POST['days'] ) ? absint( $_POST['days'] ) : 30;
- $deleted = Autoblogger_Logger::prune( $days );
+ $deleted = PRAutoBlogger_Logger::prune( $days );
wp_send_json_success( [
'message' => sprintf(
/* translators: %d: number of entries deleted */
- __( '%d log entries cleared.', 'autoblogger' ),
+ __( '%d log entries cleared.', 'prautoblogger' ),
$deleted
),
] );
diff --git a/includes/admin/class-metrics-page.php b/includes/admin/class-metrics-page.php
index d7f7e75..7e4b167 100755
--- a/includes/admin/class-metrics-page.php
+++ b/includes/admin/class-metrics-page.php
@@ -7,26 +7,26 @@ declare(strict_types=1);
* Shows: monthly spend, daily cost chart, spend by pipeline stage, budget
* utilization, top/bottom performing posts, and recent generation logs.
*
- * Triggered by: Autoblogger::register_admin_hooks() on `admin_menu`.
- * Dependencies: Autoblogger_Cost_Tracker.
+ * Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_menu`.
+ * Dependencies: PRAutoBlogger_Cost_Tracker.
*
- * @see class-autoblogger.php — Registers the hook.
+ * @see class-prautoblogger.php — Registers the hook.
* @see core/class-cost-tracker.php — Provides cost data.
*/
-class Autoblogger_Metrics_Page {
+class PRAutoBlogger_Metrics_Page {
/**
- * Register the metrics submenu page under AutoBlogger.
+ * Register the metrics submenu page under PRAutoBlogger.
*
* @return void
*/
public function on_register_menu(): void {
add_submenu_page(
- 'autoblogger-settings',
- __( 'Metrics & Costs', 'autoblogger' ),
- __( 'Metrics & Costs', 'autoblogger' ),
+ 'prautoblogger-settings',
+ __( 'Metrics & Costs', 'prautoblogger' ),
+ __( 'Metrics & Costs', 'prautoblogger' ),
'manage_options',
- 'autoblogger-metrics',
+ 'prautoblogger-metrics',
[ $this, 'render_page' ]
);
}
@@ -41,6 +41,6 @@ class Autoblogger_Metrics_Page {
return;
}
- include AUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metrics-page.php';
+ include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metrics-page.php';
}
}
diff --git a/includes/admin/class-post-metabox.php b/includes/admin/class-post-metabox.php
index 1270438..80921fb 100755
--- a/includes/admin/class-post-metabox.php
+++ b/includes/admin/class-post-metabox.php
@@ -2,18 +2,18 @@
declare(strict_types=1);
/**
- * Adds a metabox to posts showing AutoBlogger generation metadata.
+ * Adds a metabox to posts showing PRAutoBlogger generation metadata.
*
- * Only appears on posts that were generated by AutoBlogger (checked via
- * _autoblogger_generated post meta).
+ * Only appears on posts that were generated by PRAutoBlogger (checked via
+ * _prautoblogger_generated post meta).
*
- * Triggered by: Autoblogger::register_admin_hooks() on `add_meta_boxes`.
+ * Triggered by: PRAutoBlogger::register_admin_hooks() on `add_meta_boxes`.
* Dependencies: WordPress post meta API.
*
- * @see class-autoblogger.php — Registers the hook.
+ * @see class-prautoblogger.php — Registers the hook.
* @see core/class-publisher.php — Writes the metadata this class displays.
*/
-class Autoblogger_Post_Metabox {
+class PRAutoBlogger_Post_Metabox {
/**
* Register the metabox for the post editor.
@@ -27,13 +27,13 @@ class Autoblogger_Post_Metabox {
}
global $post;
- if ( ! $post || '1' !== get_post_meta( $post->ID, '_autoblogger_generated', true ) ) {
+ if ( ! $post || '1' !== get_post_meta( $post->ID, '_prautoblogger_generated', true ) ) {
return;
}
add_meta_box(
- 'autoblogger_generation_info',
- __( 'AutoBlogger Generation Info', 'autoblogger' ),
+ 'prautoblogger_generation_info',
+ __( 'PRAutoBlogger Generation Info', 'prautoblogger' ),
[ $this, 'render_metabox' ],
'post',
'side',
@@ -49,6 +49,6 @@ class Autoblogger_Post_Metabox {
* @return void
*/
public function render_metabox( \WP_Post $post ): void {
- include AUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metabox-generation-info.php';
+ include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/metabox-generation-info.php';
}
}
diff --git a/includes/admin/class-review-queue.php b/includes/admin/class-review-queue.php
index a8598ca..0ccd144 100755
--- a/includes/admin/class-review-queue.php
+++ b/includes/admin/class-review-queue.php
@@ -7,27 +7,27 @@ declare(strict_types=1);
* Provides approve (publish), edit (opens WP editor), and reject (trash)
* actions on each draft. Supports bulk approve/reject via checkboxes.
*
- * Triggered by: Autoblogger::register_admin_hooks() on `admin_menu`.
+ * Triggered by: PRAutoBlogger::register_admin_hooks() on `admin_menu`.
* Dependencies: WordPress WP_Query, post meta API.
*
- * @see class-autoblogger.php — Registers the hook and AJAX handlers.
+ * @see class-prautoblogger.php — Registers the hook and AJAX handlers.
* @see core/class-publisher.php — Creates the drafts this page displays.
* @see templates/admin/review-queue.php — Renders the HTML.
*/
-class Autoblogger_Review_Queue {
+class PRAutoBlogger_Review_Queue {
/**
- * Register the review queue submenu page under AutoBlogger.
+ * Register the review queue submenu page under PRAutoBlogger.
*
* @return void
*/
public function on_register_menu(): void {
$hook = add_submenu_page(
- 'autoblogger-settings',
- __( 'Review Queue', 'autoblogger' ),
- __( 'Review Queue', 'autoblogger' ),
+ 'prautoblogger-settings',
+ __( 'Review Queue', 'prautoblogger' ),
+ __( 'Review Queue', 'prautoblogger' ),
'manage_options',
- 'autoblogger-review-queue',
+ 'prautoblogger-review-queue',
[ $this, 'render_page' ]
);
@@ -49,7 +49,7 @@ class Autoblogger_Review_Queue {
$paged = isset( $_GET['paged'] ) ? absint( $_GET['paged'] ) : 1;
$query = $this->get_pending_posts( $paged );
- include AUTOBLOGGER_PLUGIN_DIR . 'templates/admin/review-queue.php';
+ include PRAUTOBLOGGER_PLUGIN_DIR . 'templates/admin/review-queue.php';
}
/**
@@ -67,7 +67,7 @@ class Autoblogger_Review_Queue {
'paged' => $paged,
'meta_query' => [
[
- 'key' => '_autoblogger_generated',
+ 'key' => '_prautoblogger_generated',
'value' => '1',
],
],
@@ -87,19 +87,19 @@ class Autoblogger_Review_Queue {
* @return void
*/
public function on_handle_bulk_actions(): void {
- if ( ! isset( $_POST['autoblogger_bulk_action'] ) ) {
+ if ( ! isset( $_POST['prautoblogger_bulk_action'] ) ) {
return;
}
- check_admin_referer( 'autoblogger_review_queue_bulk', 'autoblogger_review_nonce' );
+ check_admin_referer( 'prautoblogger_review_queue_bulk', 'prautoblogger_review_nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
- $action = sanitize_text_field( wp_unslash( $_POST['autoblogger_bulk_action'] ) );
- $post_ids = isset( $_POST['autoblogger_post_ids'] ) && is_array( $_POST['autoblogger_post_ids'] )
- ? array_map( 'absint', $_POST['autoblogger_post_ids'] )
+ $action = sanitize_text_field( wp_unslash( $_POST['prautoblogger_bulk_action'] ) );
+ $post_ids = isset( $_POST['prautoblogger_post_ids'] ) && is_array( $_POST['prautoblogger_post_ids'] )
+ ? array_map( 'absint', $_POST['prautoblogger_post_ids'] )
: [];
if ( empty( $post_ids ) ) {
@@ -115,7 +115,7 @@ class Autoblogger_Review_Queue {
if ( 'approve' === $action ) {
$result = wp_update_post( [ 'ID' => $post_id, 'post_status' => 'publish' ], true );
if ( ! is_wp_error( $result ) ) {
- update_post_meta( $post_id, '_autoblogger_approved_at', gmdate( 'c' ) );
+ update_post_meta( $post_id, '_prautoblogger_approved_at', gmdate( 'c' ) );
$count++;
}
} elseif ( 'reject' === $action ) {
@@ -128,9 +128,9 @@ class Autoblogger_Review_Queue {
$redirect = add_query_arg(
[
- 'page' => 'autoblogger-review-queue',
- 'autoblogger_bulk_done' => $count,
- 'autoblogger_bulk_type' => $action,
+ 'page' => 'prautoblogger-review-queue',
+ 'prautoblogger_bulk_done' => $count,
+ 'prautoblogger_bulk_type' => $action,
],
admin_url( 'admin.php' )
);
@@ -147,16 +147,16 @@ class Autoblogger_Review_Queue {
* @return void
*/
public function on_ajax_approve_post(): void {
- check_ajax_referer( 'autoblogger_review_queue', 'nonce' );
+ check_ajax_referer( 'prautoblogger_review_queue', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'autoblogger' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
return;
}
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( $post_id <= 0 ) {
- wp_send_json_error( [ 'message' => __( 'Invalid post ID.', 'autoblogger' ) ] );
+ wp_send_json_error( [ 'message' => __( 'Invalid post ID.', 'prautoblogger' ) ] );
return;
}
@@ -167,8 +167,8 @@ class Autoblogger_Review_Queue {
return;
}
- update_post_meta( $post_id, '_autoblogger_approved_at', gmdate( 'c' ) );
- wp_send_json_success( [ 'message' => __( 'Post published.', 'autoblogger' ), 'post_id' => $post_id ] );
+ update_post_meta( $post_id, '_prautoblogger_approved_at', gmdate( 'c' ) );
+ wp_send_json_success( [ 'message' => __( 'Post published.', 'prautoblogger' ), 'post_id' => $post_id ] );
}
/**
@@ -179,25 +179,25 @@ class Autoblogger_Review_Queue {
* @return void
*/
public function on_ajax_reject_post(): void {
- check_ajax_referer( 'autoblogger_review_queue', 'nonce' );
+ check_ajax_referer( 'prautoblogger_review_queue', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'autoblogger' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
return;
}
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( $post_id <= 0 ) {
- wp_send_json_error( [ 'message' => __( 'Invalid post ID.', 'autoblogger' ) ] );
+ wp_send_json_error( [ 'message' => __( 'Invalid post ID.', 'prautoblogger' ) ] );
return;
}
$result = wp_trash_post( $post_id );
if ( false === $result || null === $result ) {
- wp_send_json_error( [ 'message' => __( 'Failed to reject post.', 'autoblogger' ) ] );
+ wp_send_json_error( [ 'message' => __( 'Failed to reject post.', 'prautoblogger' ) ] );
return;
}
- wp_send_json_success( [ 'message' => __( 'Post rejected.', 'autoblogger' ), 'post_id' => $post_id ] );
+ wp_send_json_success( [ 'message' => __( 'Post rejected.', 'prautoblogger' ), 'post_id' => $post_id ] );
}
}
diff --git a/includes/admin/class-settings-fields.php b/includes/admin/class-settings-fields.php
index 52e86ff..6a3220a 100755
--- a/includes/admin/class-settings-fields.php
+++ b/includes/admin/class-settings-fields.php
@@ -2,18 +2,18 @@
declare(strict_types=1);
/**
- * Declarative settings fields and sections for the AutoBlogger admin page.
+ * Declarative settings fields and sections for the PRAutoBlogger admin page.
*
* Centralizes all field and section definitions in one place, making it trivial
* to add new settings (just add one array entry). Decoupled from page rendering logic.
*
- * Triggered by: Autoblogger_Admin_Page::on_register_settings() calls static methods here.
+ * Triggered by: PRAutoBlogger_Admin_Page::on_register_settings() calls static methods here.
* Dependencies: None — contains only data definitions and static methods.
*
* @see admin/class-admin-page.php — Calls get_sections() and get_fields() to register settings.
* @see CONVENTIONS.md — "How To: Add a New Admin Setting".
*/
-class Autoblogger_Settings_Fields {
+class PRAutoBlogger_Settings_Fields {
/**
* Get all settings sections. Each section maps to a tab in the admin UI.
@@ -22,40 +22,40 @@ class Autoblogger_Settings_Fields {
*/
public static function get_sections(): array {
return [
- 'autoblogger_api' => [
- 'title' => __( 'API Keys', 'autoblogger' ),
+ 'prautoblogger_api' => [
+ 'title' => __( 'API Keys', 'prautoblogger' ),
'icon' => 'dashicons-admin-network',
- 'description' => __( 'Connect your external services. Keys are encrypted at rest.', 'autoblogger' ),
+ 'description' => __( 'Connect your external services. Keys are encrypted at rest.', 'prautoblogger' ),
],
- 'autoblogger_models' => [
- 'title' => __( 'AI Models', 'autoblogger' ),
+ 'prautoblogger_models' => [
+ 'title' => __( 'AI Models', 'prautoblogger' ),
'icon' => 'dashicons-superhero-alt',
- 'description' => __( 'Choose which models power each stage of the pipeline.', 'autoblogger' ),
+ 'description' => __( 'Choose which models power each stage of the pipeline.', 'prautoblogger' ),
],
- 'autoblogger_content' => [
- 'title' => __( 'Content', 'autoblogger' ),
+ 'prautoblogger_content' => [
+ 'title' => __( 'Content', 'prautoblogger' ),
'icon' => 'dashicons-edit-large',
- 'description' => __( 'Control tone, length, pipeline mode, and topic guardrails.', 'autoblogger' ),
+ 'description' => __( 'Control tone, length, pipeline mode, and topic guardrails.', 'prautoblogger' ),
],
- 'autoblogger_sources' => [
- 'title' => __( 'Sources', 'autoblogger' ),
+ 'prautoblogger_sources' => [
+ 'title' => __( 'Sources', 'prautoblogger' ),
'icon' => 'dashicons-rss',
- 'description' => __( 'Configure where AutoBlogger finds trending topics.', 'autoblogger' ),
+ 'description' => __( 'Configure where PRAutoBlogger finds trending topics.', 'prautoblogger' ),
],
- 'autoblogger_schedule' => [
- 'title' => __( 'Schedule & Budget', 'autoblogger' ),
+ 'prautoblogger_schedule' => [
+ 'title' => __( 'Schedule & Budget', 'prautoblogger' ),
'icon' => 'dashicons-calendar-alt',
- 'description' => __( 'Set daily generation schedule, volume, and spending limits.', 'autoblogger' ),
+ 'description' => __( 'Set daily generation schedule, volume, and spending limits.', 'prautoblogger' ),
],
- 'autoblogger_publishing' => [
- 'title' => __( 'Publishing', 'autoblogger' ),
+ 'prautoblogger_publishing' => [
+ 'title' => __( 'Publishing', 'prautoblogger' ),
'icon' => 'dashicons-megaphone',
- 'description' => __( 'Control how generated content is published.', 'autoblogger' ),
+ 'description' => __( 'Control how generated content is published.', 'prautoblogger' ),
],
- 'autoblogger_analytics' => [
- 'title' => __( 'Analytics', 'autoblogger' ),
+ 'prautoblogger_analytics' => [
+ 'title' => __( 'Analytics', 'prautoblogger' ),
'icon' => 'dashicons-chart-area',
- 'description' => __( 'Connect Google Analytics 4 for post performance scoring.', 'autoblogger' ),
+ 'description' => __( 'Connect Google Analytics 4 for post performance scoring.', 'prautoblogger' ),
],
];
}
@@ -69,222 +69,222 @@ class Autoblogger_Settings_Fields {
return [
// ── API Keys ────────────────────────────────────────────────
[
- 'id' => 'autoblogger_openrouter_api_key',
- 'label' => __( 'OpenRouter API Key', 'autoblogger' ),
+ 'id' => 'prautoblogger_openrouter_api_key',
+ 'label' => __( 'OpenRouter API Key', 'prautoblogger' ),
'type' => 'password',
- 'section' => 'autoblogger_api',
- 'description' => __( 'Get your key at openrouter.ai/keys', 'autoblogger' ),
+ 'section' => 'prautoblogger_api',
+ 'description' => __( 'Get your key at openrouter.ai/keys', 'prautoblogger' ),
'icon' => '🔑',
],
[
- 'id' => 'autoblogger_reddit_client_id',
- 'label' => __( 'Reddit Client ID', 'autoblogger' ),
+ 'id' => 'prautoblogger_reddit_client_id',
+ 'label' => __( 'Reddit Client ID', 'prautoblogger' ),
'type' => 'text',
- 'section' => 'autoblogger_api',
- 'description' => __( 'Create a script app at reddit.com/prefs/apps', 'autoblogger' ),
+ 'section' => 'prautoblogger_api',
+ 'description' => __( 'Create a script app at reddit.com/prefs/apps', 'prautoblogger' ),
'icon' => '🤖',
],
[
- 'id' => 'autoblogger_reddit_client_secret',
- 'label' => __( 'Reddit Client Secret', 'autoblogger' ),
+ 'id' => 'prautoblogger_reddit_client_secret',
+ 'label' => __( 'Reddit Client Secret', 'prautoblogger' ),
'type' => 'password',
- 'section' => 'autoblogger_api',
+ 'section' => 'prautoblogger_api',
],
// ── AI Models ───────────────────────────────────────────────
[
- 'id' => 'autoblogger_analysis_model',
- 'label' => __( 'Analysis Model', 'autoblogger' ),
+ 'id' => 'prautoblogger_analysis_model',
+ 'label' => __( 'Analysis Model', 'prautoblogger' ),
'type' => 'text',
- 'section' => 'autoblogger_models',
- 'default' => AUTOBLOGGER_DEFAULT_ANALYSIS_MODEL,
- 'description' => __( 'Used for source analysis. Pick a cheap, fast model.', 'autoblogger' ),
- 'badge' => __( 'Low cost', 'autoblogger' ),
+ 'section' => 'prautoblogger_models',
+ 'default' => PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL,
+ 'description' => __( 'Used for source analysis. Pick a cheap, fast model.', 'prautoblogger' ),
+ 'badge' => __( 'Low cost', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_writing_model',
- 'label' => __( 'Writing Model', 'autoblogger' ),
+ 'id' => 'prautoblogger_writing_model',
+ 'label' => __( 'Writing Model', 'prautoblogger' ),
'type' => 'text',
- 'section' => 'autoblogger_models',
- 'default' => AUTOBLOGGER_DEFAULT_WRITING_MODEL,
- 'description' => __( 'Used for article generation. Quality matters here.', 'autoblogger' ),
- 'badge' => __( 'Quality', 'autoblogger' ),
+ 'section' => 'prautoblogger_models',
+ 'default' => PRAUTOBLOGGER_DEFAULT_WRITING_MODEL,
+ 'description' => __( 'Used for article generation. Quality matters here.', 'prautoblogger' ),
+ 'badge' => __( 'Quality', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_editor_model',
- 'label' => __( 'Editor Model', 'autoblogger' ),
+ 'id' => 'prautoblogger_editor_model',
+ 'label' => __( 'Editor Model', 'prautoblogger' ),
'type' => 'text',
- 'section' => 'autoblogger_models',
- 'default' => AUTOBLOGGER_DEFAULT_EDITOR_MODEL,
- 'description' => __( 'Used for the chief editor review pass.', 'autoblogger' ),
- 'badge' => __( 'Quality', 'autoblogger' ),
+ 'section' => 'prautoblogger_models',
+ 'default' => PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL,
+ 'description' => __( 'Used for the chief editor review pass.', 'prautoblogger' ),
+ 'badge' => __( 'Quality', 'prautoblogger' ),
],
// ── Content ─────────────────────────────────────────────────
[
- 'id' => 'autoblogger_niche_description',
- 'label' => __( 'Niche Description', 'autoblogger' ),
+ 'id' => 'prautoblogger_niche_description',
+ 'label' => __( 'Niche Description', 'prautoblogger' ),
'type' => 'textarea',
- 'section' => 'autoblogger_content',
- 'description' => __( 'Describe your site\'s niche. Guides content analysis and generation.', 'autoblogger' ),
+ 'section' => 'prautoblogger_content',
+ 'description' => __( 'Describe your site\'s niche. Guides content analysis and generation.', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_tone',
- 'label' => __( 'Content Tone', 'autoblogger' ),
+ 'id' => 'prautoblogger_tone',
+ 'label' => __( 'Content Tone', 'prautoblogger' ),
'type' => 'select',
- 'section' => 'autoblogger_content',
+ 'section' => 'prautoblogger_content',
'default' => 'informational',
'options' => [
- 'informational' => __( 'Informational', 'autoblogger' ),
- 'conversational' => __( 'Conversational', 'autoblogger' ),
- 'professional' => __( 'Professional', 'autoblogger' ),
- 'casual' => __( 'Casual', 'autoblogger' ),
- 'authoritative' => __( 'Authoritative', 'autoblogger' ),
+ 'informational' => __( 'Informational', 'prautoblogger' ),
+ 'conversational' => __( 'Conversational', 'prautoblogger' ),
+ 'professional' => __( 'Professional', 'prautoblogger' ),
+ 'casual' => __( 'Casual', 'prautoblogger' ),
+ 'authoritative' => __( 'Authoritative', 'prautoblogger' ),
],
],
[
- 'id' => 'autoblogger_writing_pipeline',
- 'label' => __( 'Writing Pipeline', 'autoblogger' ),
+ 'id' => 'prautoblogger_writing_pipeline',
+ 'label' => __( 'Writing Pipeline', 'prautoblogger' ),
'type' => 'select',
- 'section' => 'autoblogger_content',
+ 'section' => 'prautoblogger_content',
'default' => 'multi_step',
'options' => [
- 'multi_step' => __( 'Multi-step (outline → draft → polish)', 'autoblogger' ),
- 'single_pass' => __( 'Single-pass (one LLM call)', 'autoblogger' ),
+ 'multi_step' => __( 'Multi-step (outline → draft → polish)', 'prautoblogger' ),
+ 'single_pass' => __( 'Single-pass (one LLM call)', 'prautoblogger' ),
],
],
[
- 'id' => 'autoblogger_min_word_count',
- 'label' => __( 'Min Word Count', 'autoblogger' ),
+ 'id' => 'prautoblogger_min_word_count',
+ 'label' => __( 'Min Word Count', 'prautoblogger' ),
'type' => 'number',
- 'section' => 'autoblogger_content',
+ 'section' => 'prautoblogger_content',
'default' => 800,
'min' => 200,
],
[
- 'id' => 'autoblogger_max_word_count',
- 'label' => __( 'Max Word Count', 'autoblogger' ),
+ 'id' => 'prautoblogger_max_word_count',
+ 'label' => __( 'Max Word Count', 'prautoblogger' ),
'type' => 'number',
- 'section' => 'autoblogger_content',
+ 'section' => 'prautoblogger_content',
'default' => 2000,
'min' => 500,
],
[
- 'id' => 'autoblogger_topic_exclusions',
- 'label' => __( 'Topic Exclusions', 'autoblogger' ),
+ 'id' => 'prautoblogger_topic_exclusions',
+ 'label' => __( 'Topic Exclusions', 'prautoblogger' ),
'type' => 'textarea',
- 'section' => 'autoblogger_content',
- 'description' => __( 'Comma-separated topics to never write about.', 'autoblogger' ),
+ 'section' => 'prautoblogger_content',
+ 'description' => __( 'Comma-separated topics to never write about.', 'prautoblogger' ),
],
// ── Sources ─────────────────────────────────────────────────
[
- 'id' => 'autoblogger_target_subreddits',
- 'label' => __( 'Target Subreddits', 'autoblogger' ),
+ 'id' => 'prautoblogger_target_subreddits',
+ 'label' => __( 'Target Subreddits', 'prautoblogger' ),
'type' => 'textarea',
- 'section' => 'autoblogger_sources',
- 'description' => __( 'Comma-separated, without r/. E.g.: peptides, Nootropics, biohackers', 'autoblogger' ),
+ 'section' => 'prautoblogger_sources',
+ 'description' => __( 'Comma-separated, without r/. E.g.: peptides, Nootropics, biohackers', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_enabled_sources',
- 'label' => __( 'Enabled Sources', 'autoblogger' ),
+ 'id' => 'prautoblogger_enabled_sources',
+ 'label' => __( 'Enabled Sources', 'prautoblogger' ),
'type' => 'checkboxes',
- 'section' => 'autoblogger_sources',
+ 'section' => 'prautoblogger_sources',
'options' => [
- 'reddit' => __( 'Reddit', 'autoblogger' ),
- 'tiktok' => __( 'TikTok (coming soon)', 'autoblogger' ),
- 'instagram' => __( 'Instagram (coming soon)', 'autoblogger' ),
- 'youtube' => __( 'YouTube (coming soon)', 'autoblogger' ),
+ 'reddit' => __( 'Reddit', 'prautoblogger' ),
+ 'tiktok' => __( 'TikTok (coming soon)', 'prautoblogger' ),
+ 'instagram' => __( 'Instagram (coming soon)', 'prautoblogger' ),
+ 'youtube' => __( 'YouTube (coming soon)', 'prautoblogger' ),
],
'default' => '["reddit"]',
- 'description' => __( 'Select which platforms to monitor for topics.', 'autoblogger' ),
+ 'description' => __( 'Select which platforms to monitor for topics.', 'prautoblogger' ),
],
// ── Schedule & Budget ───────────────────────────────────────
[
- 'id' => 'autoblogger_daily_article_target',
- 'label' => __( 'Articles per Day', 'autoblogger' ),
+ 'id' => 'prautoblogger_daily_article_target',
+ 'label' => __( 'Articles per Day', 'prautoblogger' ),
'type' => 'number',
- 'section' => 'autoblogger_schedule',
+ 'section' => 'prautoblogger_schedule',
'default' => 1,
'min' => 1,
'max' => 10,
],
[
- 'id' => 'autoblogger_schedule_time',
- 'label' => __( 'Generation Time', 'autoblogger' ),
+ 'id' => 'prautoblogger_schedule_time',
+ 'label' => __( 'Generation Time', 'prautoblogger' ),
'type' => 'time',
- 'section' => 'autoblogger_schedule',
+ 'section' => 'prautoblogger_schedule',
'default' => '03:00',
- 'description' => __( 'Daily generation runs at this time (server timezone).', 'autoblogger' ),
+ 'description' => __( 'Daily generation runs at this time (server timezone).', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_monthly_budget_usd',
- 'label' => __( 'Monthly Budget (USD)', 'autoblogger' ),
+ 'id' => 'prautoblogger_monthly_budget_usd',
+ 'label' => __( 'Monthly Budget (USD)', 'prautoblogger' ),
'type' => 'number',
- 'section' => 'autoblogger_schedule',
+ 'section' => 'prautoblogger_schedule',
'default' => 50,
'min' => 0,
'step' => '0.01',
- 'description' => __( 'Hard stop when reached. 0 = unlimited.', 'autoblogger' ),
+ 'description' => __( 'Hard stop when reached. 0 = unlimited.', 'prautoblogger' ),
],
// ── Publishing ──────────────────────────────────────────────
[
- 'id' => 'autoblogger_auto_publish',
- 'label' => __( 'Auto-Publish', 'autoblogger' ),
+ 'id' => 'prautoblogger_auto_publish',
+ 'label' => __( 'Auto-Publish', 'prautoblogger' ),
'type' => 'toggle',
- 'section' => 'autoblogger_publishing',
+ 'section' => 'prautoblogger_publishing',
'default' => '0',
- 'description' => __( 'Automatically publish editor-approved posts. When off, all posts go to the Review Queue as drafts.', 'autoblogger' ),
+ 'description' => __( 'Automatically publish editor-approved posts. When off, all posts go to the Review Queue as drafts.', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_default_author',
- 'label' => __( 'Default Author', 'autoblogger' ),
+ 'id' => 'prautoblogger_default_author',
+ 'label' => __( 'Default Author', 'prautoblogger' ),
'type' => 'author_select',
- 'section' => 'autoblogger_publishing',
+ 'section' => 'prautoblogger_publishing',
'default' => 0,
- 'description' => __( 'WordPress user assigned as author of generated posts.', 'autoblogger' ),
+ 'description' => __( 'WordPress user assigned as author of generated posts.', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_default_category',
- 'label' => __( 'Default Category', 'autoblogger' ),
+ 'id' => 'prautoblogger_default_category',
+ 'label' => __( 'Default Category', 'prautoblogger' ),
'type' => 'category_select',
- 'section' => 'autoblogger_publishing',
+ 'section' => 'prautoblogger_publishing',
'default' => 0,
- 'description' => __( 'Fallback category for generated posts.', 'autoblogger' ),
+ 'description' => __( 'Fallback category for generated posts.', 'prautoblogger' ),
],
// ── Logging ─────────────────────────────────────────────────
[
- 'id' => 'autoblogger_log_level',
- 'label' => __( 'Log Level', 'autoblogger' ),
+ 'id' => 'prautoblogger_log_level',
+ 'label' => __( 'Log Level', 'prautoblogger' ),
'type' => 'select',
- 'section' => 'autoblogger_publishing',
+ 'section' => 'prautoblogger_publishing',
'default' => 'info',
'options' => [
- 'error' => __( 'Error — only failures', 'autoblogger' ),
- 'warning' => __( 'Warning — errors + warnings', 'autoblogger' ),
- 'info' => __( 'Info — key events (recommended)', 'autoblogger' ),
- 'debug' => __( 'Debug — everything (verbose)', 'autoblogger' ),
+ 'error' => __( 'Error — only failures', 'prautoblogger' ),
+ 'warning' => __( 'Warning — errors + warnings', 'prautoblogger' ),
+ 'info' => __( 'Info — key events (recommended)', 'prautoblogger' ),
+ 'debug' => __( 'Debug — everything (verbose)', 'prautoblogger' ),
],
- 'description' => __( 'Controls detail level in the Activity Log.', 'autoblogger' ),
+ 'description' => __( 'Controls detail level in the Activity Log.', 'prautoblogger' ),
],
// ── Analytics ───────────────────────────────────────────────
[
- 'id' => 'autoblogger_ga4_property_id',
- 'label' => __( 'GA4 Property ID', 'autoblogger' ),
+ 'id' => 'prautoblogger_ga4_property_id',
+ 'label' => __( 'GA4 Property ID', 'prautoblogger' ),
'type' => 'text',
- 'section' => 'autoblogger_analytics',
- 'description' => __( 'Format: properties/XXXXXXXXX. Leave blank to skip GA4.', 'autoblogger' ),
+ 'section' => 'prautoblogger_analytics',
+ 'description' => __( 'Format: properties/XXXXXXXXX. Leave blank to skip GA4.', 'prautoblogger' ),
],
[
- 'id' => 'autoblogger_ga4_credentials_json',
- 'label' => __( 'Service Account JSON', 'autoblogger' ),
+ 'id' => 'prautoblogger_ga4_credentials_json',
+ 'label' => __( 'Service Account JSON', 'prautoblogger' ),
'type' => 'password',
- 'section' => 'autoblogger_analytics',
- 'description' => __( 'Paste the full JSON key file for a service account with Analytics read access.', 'autoblogger' ),
+ 'section' => 'prautoblogger_analytics',
+ 'description' => __( 'Paste the full JSON key file for a service account with Analytics read access.', 'prautoblogger' ),
],
];
}
diff --git a/includes/class-activator.php b/includes/class-activator.php
index 56f0166..317f08a 100755
--- a/includes/class-activator.php
+++ b/includes/class-activator.php
@@ -5,13 +5,13 @@ declare(strict_types=1);
* Handles plugin activation: creates database tables, sets default options,
* and schedules the initial cron job.
*
- * Triggered by: WordPress `register_activation_hook` in autoblogger.php.
+ * Triggered by: WordPress `register_activation_hook` in prautoblogger.php.
* Dependencies: WordPress $wpdb, dbDelta().
*
* @see class-deactivator.php — Reverses transient/cron setup on deactivation.
* @see ARCHITECTURE.md — Database schema definitions.
*/
-class Autoblogger_Activator {
+class PRAutoBlogger_Activator {
/**
* Run activation tasks.
@@ -30,7 +30,7 @@ class Autoblogger_Activator {
self::schedule_cron();
// Store the DB version so we can run migrations on future updates.
- update_option( 'autoblogger_db_version', AUTOBLOGGER_DB_VERSION, false );
+ update_option( 'prautoblogger_db_version', PRAUTOBLOGGER_DB_VERSION, false );
}
/**
@@ -44,7 +44,7 @@ class Autoblogger_Activator {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
- $prefix = $wpdb->prefix . 'autoblogger_';
+ $prefix = $wpdb->prefix . 'prautoblogger_';
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
@@ -156,21 +156,21 @@ class Autoblogger_Activator {
*/
private static function set_default_options(): void {
$defaults = [
- 'autoblogger_analysis_model' => AUTOBLOGGER_DEFAULT_ANALYSIS_MODEL,
- 'autoblogger_writing_model' => AUTOBLOGGER_DEFAULT_WRITING_MODEL,
- 'autoblogger_editor_model' => AUTOBLOGGER_DEFAULT_EDITOR_MODEL,
- 'autoblogger_daily_article_target' => 1,
- 'autoblogger_writing_pipeline' => 'multi_step',
- 'autoblogger_niche_description' => '',
- 'autoblogger_target_subreddits' => '[]',
- 'autoblogger_monthly_budget_usd' => 50.00,
- 'autoblogger_tone' => 'informational',
- 'autoblogger_min_word_count' => 800,
- 'autoblogger_max_word_count' => 2000,
- 'autoblogger_topic_exclusions' => '[]',
- 'autoblogger_enabled_sources' => '["reddit"]',
- 'autoblogger_schedule_time' => '03:00',
- 'autoblogger_log_level' => 'info',
+ 'prautoblogger_analysis_model' => PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL,
+ 'prautoblogger_writing_model' => PRAUTOBLOGGER_DEFAULT_WRITING_MODEL,
+ 'prautoblogger_editor_model' => PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL,
+ 'prautoblogger_daily_article_target' => 1,
+ 'prautoblogger_writing_pipeline' => 'multi_step',
+ 'prautoblogger_niche_description' => '',
+ 'prautoblogger_target_subreddits' => '[]',
+ 'prautoblogger_monthly_budget_usd' => 50.00,
+ 'prautoblogger_tone' => 'informational',
+ 'prautoblogger_min_word_count' => 800,
+ 'prautoblogger_max_word_count' => 2000,
+ 'prautoblogger_topic_exclusions' => '[]',
+ 'prautoblogger_enabled_sources' => '["reddit"]',
+ 'prautoblogger_schedule_time' => '03:00',
+ 'prautoblogger_log_level' => 'info',
];
foreach ( $defaults as $key => $value ) {
@@ -181,8 +181,8 @@ class Autoblogger_Activator {
/**
* Schedule the daily generation cron event if not already scheduled.
*
- * The custom 'autoblogger_six_hours' schedule is normally registered via
- * the `cron_schedules` filter in class-autoblogger.php. However, activation
+ * The custom 'prautoblogger_six_hours' schedule is normally registered via
+ * the `cron_schedules` filter in class-prautoblogger.php. However, activation
* runs before `plugins_loaded`, so the filter may not be registered yet.
* We register it inline here to ensure it exists at scheduling time.
*
@@ -191,36 +191,36 @@ class Autoblogger_Activator {
* @return void
*/
private static function schedule_cron(): void {
- if ( ! wp_next_scheduled( 'autoblogger_daily_generation' ) ) {
+ if ( ! wp_next_scheduled( 'prautoblogger_daily_generation' ) ) {
// Schedule for the configured time tomorrow.
- $time_str = get_option( 'autoblogger_schedule_time', '03:00' );
+ $time_str = get_option( 'prautoblogger_schedule_time', '03:00' );
$parts = explode( ':', $time_str );
$hour = isset( $parts[0] ) ? absint( $parts[0] ) : 3;
$minute = isset( $parts[1] ) ? absint( $parts[1] ) : 0;
$timestamp = strtotime( "tomorrow {$hour}:{$minute}" );
if ( false !== $timestamp ) {
- wp_schedule_event( $timestamp, 'daily', 'autoblogger_daily_generation' );
+ wp_schedule_event( $timestamp, 'daily', 'prautoblogger_daily_generation' );
}
}
// Ensure the six-hour schedule exists before referencing it.
// Activation can fire before plugins_loaded, so the cron_schedules filter
- // in class-autoblogger.php may not be hooked yet.
+ // in class-prautoblogger.php may not be hooked yet.
$schedules = wp_get_schedules();
- if ( ! isset( $schedules['autoblogger_six_hours'] ) ) {
+ if ( ! isset( $schedules['prautoblogger_six_hours'] ) ) {
add_filter( 'cron_schedules', static function ( array $scheds ): array {
- $scheds['autoblogger_six_hours'] = [
+ $scheds['prautoblogger_six_hours'] = [
'interval' => 6 * HOUR_IN_SECONDS,
- 'display' => __( 'Every Six Hours', 'autoblogger' ),
+ 'display' => __( 'Every Six Hours', 'prautoblogger' ),
];
return $scheds;
} );
}
// Schedule a separate metrics collection job (runs every 6 hours).
- if ( ! wp_next_scheduled( 'autoblogger_collect_metrics' ) ) {
- wp_schedule_event( time() + HOUR_IN_SECONDS, 'autoblogger_six_hours', 'autoblogger_collect_metrics' );
+ if ( ! wp_next_scheduled( 'prautoblogger_collect_metrics' ) ) {
+ wp_schedule_event( time() + HOUR_IN_SECONDS, 'prautoblogger_six_hours', 'prautoblogger_collect_metrics' );
}
}
}
diff --git a/includes/class-autoloader.php b/includes/class-autoloader.php
index 3d8088d..114f444 100755
--- a/includes/class-autoloader.php
+++ b/includes/class-autoloader.php
@@ -2,17 +2,17 @@
declare(strict_types=1);
/**
- * PSR-4-inspired autoloader for AutoBlogger classes.
+ * PSR-4-inspired autoloader for PRAutoBlogger classes.
*
- * Maps class names like `Autoblogger_Content_Generator` to file paths like
+ * Maps class names like `PRAutoBlogger_Content_Generator` to file paths like
* `includes/core/class-content-generator.php` by scanning known subdirectories.
*
* Triggered by: PHP's spl_autoload when a class is first referenced.
* Dependencies: None — this is loaded before anything else.
*
- * @see autoblogger.php — Registers this autoloader at boot time.
+ * @see prautoblogger.php — Registers this autoloader at boot time.
*/
-class Autoblogger_Autoloader {
+class PRAutoBlogger_Autoloader {
/**
* Directories to scan for class files, relative to the plugin's includes/ dir.
@@ -25,6 +25,7 @@ class Autoblogger_Autoloader {
'core/',
'providers/',
'models/',
+ 'frontend/',
];
/**
@@ -39,7 +40,7 @@ class Autoblogger_Autoloader {
/**
* Attempt to load a class file.
*
- * Converts `Autoblogger_Content_Generator` → `class-content-generator.php`
+ * Converts `PRAutoBlogger_Content_Generator` → `class-content-generator.php`
* and searches each registered subdirectory under includes/.
*
* @param string $class_name Fully qualified class name.
@@ -48,12 +49,12 @@ class Autoblogger_Autoloader {
*/
public static function autoload( string $class_name ): void {
// Only handle our own classes.
- if ( 0 !== strpos( $class_name, 'Autoblogger' ) ) {
+ if ( 0 !== strpos( $class_name, 'PRAutoBlogger' ) ) {
return;
}
$file_name = self::class_to_filename( $class_name );
- $base_dir = AUTOBLOGGER_PLUGIN_DIR . 'includes/';
+ $base_dir = PRAUTOBLOGGER_PLUGIN_DIR . 'includes/';
foreach ( self::$directories as $directory ) {
$file_path = $base_dir . $directory . $file_name;
@@ -67,16 +68,16 @@ class Autoblogger_Autoloader {
/**
* Convert a class name to its expected filename.
*
- * Autoblogger_Content_Generator → class-content-generator.php
- * Autoblogger_LLM_Provider_Interface → interface-llm-provider.php
+ * PRAutoBlogger_Content_Generator → class-content-generator.php
+ * PRAutoBlogger_LLM_Provider_Interface → interface-llm-provider.php
*
* @param string $class_name The class name to convert.
*
* @return string The expected filename.
*/
private static function class_to_filename( string $class_name ): string {
- // Remove the Autoblogger_ prefix.
- $name = (string) preg_replace( '/^Autoblogger_/', '', $class_name );
+ // Remove the PRAutoBlogger_ prefix.
+ $name = (string) preg_replace( '/^PRAutoBlogger_/', '', $class_name );
// Determine file prefix: interface- or class-.
$prefix = 'class-';
diff --git a/includes/class-deactivator.php b/includes/class-deactivator.php
index 6c83b7a..be1158c 100755
--- a/includes/class-deactivator.php
+++ b/includes/class-deactivator.php
@@ -7,13 +7,13 @@ declare(strict_types=1);
* Note: does NOT delete data or options — that happens in uninstall.php.
* Deactivation should be reversible (user can reactivate and pick up where they left off).
*
- * Triggered by: WordPress `register_deactivation_hook` in autoblogger.php.
+ * Triggered by: WordPress `register_deactivation_hook` in prautoblogger.php.
* Dependencies: None.
*
* @see class-activator.php — Sets up what this class tears down.
* @see uninstall.php — Permanent data removal on plugin deletion.
*/
-class Autoblogger_Deactivator {
+class PRAutoBlogger_Deactivator {
/**
* Run deactivation tasks.
@@ -34,8 +34,8 @@ class Autoblogger_Deactivator {
*/
private static function clear_cron(): void {
$hooks = [
- 'autoblogger_daily_generation',
- 'autoblogger_collect_metrics',
+ 'prautoblogger_daily_generation',
+ 'prautoblogger_collect_metrics',
];
foreach ( $hooks as $hook ) {
@@ -55,9 +55,9 @@ class Autoblogger_Deactivator {
*/
private static function clear_transients(): void {
$transients = [
- 'autoblogger_reddit_token',
- 'autoblogger_generation_lock',
- 'autoblogger_openrouter_models',
+ 'prautoblogger_reddit_token',
+ 'prautoblogger_generation_lock',
+ 'prautoblogger_openrouter_models',
];
foreach ( $transients as $transient ) {
diff --git a/includes/class-encryption.php b/includes/class-encryption.php
index b5d0db9..8519964 100755
--- a/includes/class-encryption.php
+++ b/includes/class-encryption.php
@@ -13,7 +13,7 @@ declare(strict_types=1);
*
* @see admin/class-admin-page.php — Uses this to store/retrieve API keys.
*/
-class Autoblogger_Encryption {
+class PRAutoBlogger_Encryption {
private const METHOD = 'aes-256-cbc';
@@ -35,7 +35,7 @@ class Autoblogger_Encryption {
$ciphertext = openssl_encrypt( $plaintext, self::METHOD, $key, OPENSSL_RAW_DATA, $iv );
if ( false === $ciphertext ) {
- Autoblogger_Logger::instance()->error( 'Encryption failed: ' . openssl_error_string(), 'encryption' );
+ PRAutoBlogger_Logger::instance()->error( 'Encryption failed: ' . openssl_error_string(), 'encryption' );
return '';
}
@@ -62,7 +62,7 @@ class Autoblogger_Encryption {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$raw = base64_decode( $encrypted, true );
if ( false === $raw || strlen( $raw ) <= $iv_len ) {
- Autoblogger_Logger::instance()->error( 'Decryption failed: invalid ciphertext format.', 'encryption' );
+ PRAutoBlogger_Logger::instance()->error( 'Decryption failed: invalid ciphertext format.', 'encryption' );
return '';
}
@@ -71,7 +71,7 @@ class Autoblogger_Encryption {
$plaintext = openssl_decrypt( $ciphertext, self::METHOD, $key, OPENSSL_RAW_DATA, $iv );
if ( false === $plaintext ) {
- Autoblogger_Logger::instance()->error( 'Decryption failed: ' . openssl_error_string(), 'encryption' );
+ PRAutoBlogger_Logger::instance()->error( 'Decryption failed: ' . openssl_error_string(), 'encryption' );
return '';
}
diff --git a/includes/class-autoblogger.php b/includes/class-prautoblogger.php
old mode 100755
new mode 100644
similarity index 60%
rename from includes/class-autoblogger.php
rename to includes/class-prautoblogger.php
index dfeca06..0fde88a
--- a/includes/class-autoblogger.php
+++ b/includes/class-prautoblogger.php
@@ -2,21 +2,21 @@
declare(strict_types=1);
/**
- * Main orchestrator for the AutoBlogger plugin.
+ * Main orchestrator for the PRAutoBlogger plugin.
*
* Registers all WordPress hooks (actions and filters) and wires up dependencies.
* This is the ONLY place hooks are registered — individual classes contain only
* business logic, not hook registration.
*
- * Triggered by: autoblogger() singleton in autoblogger.php, called on `plugins_loaded`.
- * Dependencies: All other plugin classes (loaded via autoloader), Autoblogger_Pipeline_Runner.
+ * Triggered by: prautoblogger() singleton in prautoblogger.php, called on `plugins_loaded`.
+ * Dependencies: All other plugin classes (loaded via autoloader), PRAutoBlogger_Pipeline_Runner.
*
- * @see autoblogger.php — Plugin bootstrap that instantiates this class.
+ * @see prautoblogger.php — Plugin bootstrap that instantiates this class.
* @see core/class-pipeline-runner.php — Executes the generation pipeline.
* @see ARCHITECTURE.md — Data flow diagram showing how classes interact.
* @see CONVENTIONS.md — Hook naming conventions.
*/
-class Autoblogger {
+class PRAutoBlogger {
/**
* Whether the plugin has been initialized.
@@ -52,15 +52,18 @@ class Autoblogger {
// Cron hooks — always registered so WP-Cron can fire them.
$this->register_cron_hooks();
+ // Frontend hooks — shortcode and REST API for public-facing widget.
+ $this->register_frontend_hooks();
+
// AJAX hooks — always registered so admin AJAX works.
$this->register_ajax_hooks();
/**
- * Fires after AutoBlogger has finished registering all hooks.
+ * Fires after PRAutoBlogger has finished registering all hooks.
*
- * Useful for add-ons that need to hook into AutoBlogger's lifecycle.
+ * Useful for add-ons that need to hook into PRAutoBlogger's lifecycle.
*/
- do_action( 'autoblogger_loaded' );
+ do_action( 'prautoblogger_loaded' );
}
/**
@@ -69,53 +72,71 @@ class Autoblogger {
* @return void
*/
private function register_admin_hooks(): void {
- $admin_page = new Autoblogger_Admin_Page();
+ $admin_page = new PRAutoBlogger_Admin_Page();
add_action( 'admin_menu', [ $admin_page, 'on_register_menu' ] );
add_action( 'admin_init', [ $admin_page, 'on_register_settings' ] );
add_action( 'admin_enqueue_scripts', [ $admin_page, 'on_enqueue_assets' ] );
- $notices = new Autoblogger_Admin_Notices();
+ $notices = new PRAutoBlogger_Admin_Notices();
add_action( 'admin_notices', [ $notices, 'on_display_notices' ] );
- $metabox = new Autoblogger_Post_Metabox();
+ $metabox = new PRAutoBlogger_Post_Metabox();
add_action( 'add_meta_boxes', [ $metabox, 'on_register_metabox' ] );
- $metrics_page = new Autoblogger_Metrics_Page();
+ $metrics_page = new PRAutoBlogger_Metrics_Page();
add_action( 'admin_menu', [ $metrics_page, 'on_register_menu' ] );
- $dashboard_widget = new Autoblogger_Dashboard_Widget();
+ $dashboard_widget = new PRAutoBlogger_Dashboard_Widget();
add_action( 'wp_dashboard_setup', [ $dashboard_widget, 'on_register_widget' ] );
- $review_queue = new Autoblogger_Review_Queue();
+ $review_queue = new PRAutoBlogger_Review_Queue();
add_action( 'admin_menu', [ $review_queue, 'on_register_menu' ] );
- $log_viewer = new Autoblogger_Log_Viewer();
+ $log_viewer = new PRAutoBlogger_Log_Viewer();
add_action( 'admin_menu', [ $log_viewer, 'on_register_menu' ] );
+
+ // Block false update notifications from wordpress.org — our plugin name
+ // collides with a different "PRAutoBlogger" in the plugin directory.
+ add_filter( 'site_transient_update_plugins', [ $this, 'filter_block_false_updates' ] );
+ }
+
+ /**
+ * Register frontend hooks (shortcode and REST API for posts widget).
+ *
+ * The shortcode renders a mount-point div; the React component hydrates it
+ * on the client side by fetching posts from the REST endpoint.
+ *
+ * @return void
+ */
+ private function register_frontend_hooks(): void {
+ $posts_widget = new PRAutoBlogger_Posts_Widget();
+ add_action( 'init', [ $posts_widget, 'on_register_shortcode' ] );
+ add_action( 'rest_api_init', [ $posts_widget, 'on_register_rest_route' ] );
}
/** Register cron-triggered hooks for scheduled generation and metrics. */
private function register_cron_hooks(): void {
- add_action( 'autoblogger_daily_generation', [ $this, 'on_daily_generation' ] );
- add_action( 'autoblogger_collect_metrics', [ $this, 'on_collect_metrics' ] );
+ add_action( 'prautoblogger_daily_generation', [ $this, 'on_daily_generation' ] );
+ add_action( 'prautoblogger_collect_metrics', [ $this, 'on_collect_metrics' ] );
}
/** Register AJAX handlers for admin actions. */
private function register_ajax_hooks(): void {
- add_action( 'wp_ajax_autoblogger_generate_now', [ $this, 'on_ajax_generate_now' ] );
- add_action( 'wp_ajax_autoblogger_test_connection', [ $this, 'on_ajax_test_connection' ] );
+ add_action( 'wp_ajax_prautoblogger_generate_now', [ $this, 'on_ajax_generate_now' ] );
+ add_action( 'wp_ajax_prautoblogger_test_connection', [ $this, 'on_ajax_test_connection' ] );
- $review_queue = new Autoblogger_Review_Queue();
- add_action( 'wp_ajax_autoblogger_approve_post', [ $review_queue, 'on_ajax_approve_post' ] );
- add_action( 'wp_ajax_autoblogger_reject_post', [ $review_queue, 'on_ajax_reject_post' ] );
+ $review_queue = new PRAutoBlogger_Review_Queue();
+ add_action( 'wp_ajax_prautoblogger_approve_post', [ $review_queue, 'on_ajax_approve_post' ] );
+ add_action( 'wp_ajax_prautoblogger_reject_post', [ $review_queue, 'on_ajax_reject_post' ] );
- $log_viewer = new Autoblogger_Log_Viewer();
- add_action( 'wp_ajax_autoblogger_clear_logs', [ $log_viewer, 'on_ajax_clear_logs' ] );
+ $log_viewer = new PRAutoBlogger_Log_Viewer();
+ add_action( 'wp_ajax_prautoblogger_clear_logs', [ $log_viewer, 'on_ajax_clear_logs' ] );
}
/**
* Check if database needs migration after plugin update.
*
- * Compares stored DB version with current AUTOBLOGGER_DB_VERSION.
+ * Compares stored DB version with current PRAUTOBLOGGER_DB_VERSION.
* If different, re-runs activation to apply schema changes via dbDelta.
*
* Side effects: may update database schema and db_version option.
@@ -123,9 +144,9 @@ class Autoblogger {
* @return void
*/
public function on_check_db_version(): void {
- $stored_version = get_option( 'autoblogger_db_version', '0' );
- if ( version_compare( $stored_version, AUTOBLOGGER_DB_VERSION, '<' ) ) {
- Autoblogger_Activator::activate();
+ $stored_version = get_option( 'prautoblogger_db_version', '0' );
+ if ( version_compare( $stored_version, PRAUTOBLOGGER_DB_VERSION, '<' ) ) {
+ PRAutoBlogger_Activator::activate();
}
}
@@ -137,13 +158,32 @@ class Autoblogger {
* @return array
Modified schedules.
*/
public function filter_add_cron_schedules( array $schedules ): array {
- $schedules['autoblogger_six_hours'] = [
+ $schedules['prautoblogger_six_hours'] = [
'interval' => 6 * HOUR_IN_SECONDS,
- 'display' => __( 'Every Six Hours', 'autoblogger' ),
+ 'display' => __( 'Every Six Hours', 'prautoblogger' ),
];
return $schedules;
}
+ /**
+ * Block false update notifications from wordpress.org.
+ *
+ * Our plugin slug may collide with a different plugin in the directory.
+ * This filter removes any update notice that wordpress.org might push
+ * for our plugin basename, since we manage updates via our own CI/CD.
+ *
+ * @param object $transient The update_plugins transient data.
+ *
+ * @return object Modified transient with our plugin removed from updates.
+ */
+ public function filter_block_false_updates( $transient ) {
+ $plugin_basename = PRAUTOBLOGGER_PLUGIN_BASENAME;
+ if ( isset( $transient->response[ $plugin_basename ] ) ) {
+ unset( $transient->response[ $plugin_basename ] );
+ }
+ return $transient;
+ }
+
/**
* Handle the daily generation cron event.
*
@@ -155,14 +195,14 @@ class Autoblogger {
*/
public function on_daily_generation(): void {
if ( ! $this->acquire_generation_lock() ) {
- Autoblogger_Logger::instance()->info( 'Daily generation skipped: already running (lock held).', 'scheduler' );
+ PRAutoBlogger_Logger::instance()->info( 'Daily generation skipped: already running (lock held).', 'scheduler' );
return;
}
try {
- ( new Autoblogger_Pipeline_Runner() )->run();
+ ( new PRAutoBlogger_Pipeline_Runner() )->run();
} catch ( \Exception $e ) {
- Autoblogger_Logger::instance()->error( 'Daily generation FAILED: ' . $e->getMessage(), 'scheduler' );
+ PRAutoBlogger_Logger::instance()->error( 'Daily generation FAILED: ' . $e->getMessage(), 'scheduler' );
} finally {
$this->release_generation_lock();
}
@@ -177,10 +217,10 @@ class Autoblogger {
*/
public function on_collect_metrics(): void {
try {
- $metrics = new Autoblogger_Metrics_Collector();
+ $metrics = new PRAutoBlogger_Metrics_Collector();
$metrics->collect_all();
} catch ( \Exception $e ) {
- Autoblogger_Logger::instance()->error( 'Metrics collection FAILED: ' . $e->getMessage(), 'metrics' );
+ PRAutoBlogger_Logger::instance()->error( 'Metrics collection FAILED: ' . $e->getMessage(), 'metrics' );
}
}
@@ -195,23 +235,23 @@ class Autoblogger {
* @return void
*/
public function on_ajax_generate_now(): void {
- check_ajax_referer( 'autoblogger_generate_now', 'nonce' );
+ check_ajax_referer( 'prautoblogger_generate_now', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'autoblogger' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
return;
}
if ( ! $this->acquire_generation_lock() ) {
- wp_send_json_error( [ 'message' => __( 'A generation run is already in progress.', 'autoblogger' ) ] );
+ wp_send_json_error( [ 'message' => __( 'A generation run is already in progress.', 'prautoblogger' ) ] );
return;
}
try {
- $results = ( new Autoblogger_Pipeline_Runner() )->run();
+ $results = ( new PRAutoBlogger_Pipeline_Runner() )->run();
wp_send_json_success( $results );
} catch ( \Exception $e ) {
- Autoblogger_Logger::instance()->error( 'Manual generation FAILED: ' . $e->getMessage(), 'pipeline' );
+ PRAutoBlogger_Logger::instance()->error( 'Manual generation FAILED: ' . $e->getMessage(), 'pipeline' );
wp_send_json_error( [ 'message' => $e->getMessage() ] );
} finally {
$this->release_generation_lock();
@@ -220,9 +260,9 @@ class Autoblogger {
/** AJAX handler: test API connections (OpenRouter, Reddit). */
public function on_ajax_test_connection(): void {
- check_ajax_referer( 'autoblogger_test_connection', 'nonce' );
+ check_ajax_referer( 'prautoblogger_test_connection', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'autoblogger' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
return;
}
@@ -230,16 +270,16 @@ class Autoblogger {
$results = [];
if ( 'openrouter' === $service || 'all' === $service ) {
- $llm = new Autoblogger_OpenRouter_Provider();
+ $llm = new PRAutoBlogger_OpenRouter_Provider();
$results['openrouter'] = $llm->validate_credentials()
- ? [ 'status' => 'ok', 'message' => __( 'OpenRouter connected.', 'autoblogger' ) ]
- : [ 'status' => 'error', 'message' => __( 'OpenRouter connection failed. Check your API key.', 'autoblogger' ) ];
+ ? [ 'status' => 'ok', 'message' => __( 'OpenRouter connected.', 'prautoblogger' ) ]
+ : [ 'status' => 'error', 'message' => __( 'OpenRouter connection failed. Check your API key.', 'prautoblogger' ) ];
}
if ( 'reddit' === $service || 'all' === $service ) {
- $reddit = new Autoblogger_Reddit_Provider();
+ $reddit = new PRAutoBlogger_Reddit_Provider();
$results['reddit'] = $reddit->validate_credentials()
- ? [ 'status' => 'ok', 'message' => __( 'Reddit API connected.', 'autoblogger' ) ]
- : [ 'status' => 'error', 'message' => __( 'Reddit connection failed. Check client ID/secret.', 'autoblogger' ) ];
+ ? [ 'status' => 'ok', 'message' => __( 'Reddit API connected.', 'prautoblogger' ) ]
+ : [ 'status' => 'error', 'message' => __( 'Reddit connection failed. Check client ID/secret.', 'prautoblogger' ) ];
}
wp_send_json_success( $results );
}
@@ -255,7 +295,7 @@ class Autoblogger {
private function acquire_generation_lock(): bool {
global $wpdb;
- $lock_name = 'autoblogger_generation_lock';
+ $lock_name = 'prautoblogger_generation_lock';
$now = (string) time();
// Clean up expired locks older than 1 hour — prevents permanent deadlock.
@@ -290,7 +330,7 @@ class Autoblogger {
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name = %s",
- 'autoblogger_generation_lock'
+ 'prautoblogger_generation_lock'
)
);
}
diff --git a/includes/core/class-chief-editor.php b/includes/core/class-chief-editor.php
index 505e844..560cec7 100755
--- a/includes/core/class-chief-editor.php
+++ b/includes/core/class-chief-editor.php
@@ -8,21 +8,21 @@ declare(strict_types=1);
* Can approve, request revisions (and produce them), or reject content.
* Rejected content is saved as a draft for human review.
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 5).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 5).
* Dependencies: LLM_Provider_Interface, Cost_Tracker.
*
* @see core/class-content-generator.php — Produces the content we review.
* @see core/class-publisher.php — Publishes approved content.
* @see ARCHITECTURE.md — Data flow step 5.
*/
-class Autoblogger_Chief_Editor {
+class PRAutoBlogger_Chief_Editor {
- private Autoblogger_LLM_Provider_Interface $llm;
- private Autoblogger_Cost_Tracker $cost_tracker;
+ private PRAutoBlogger_LLM_Provider_Interface $llm;
+ private PRAutoBlogger_Cost_Tracker $cost_tracker;
public function __construct(
- Autoblogger_LLM_Provider_Interface $llm,
- Autoblogger_Cost_Tracker $cost_tracker
+ PRAutoBlogger_LLM_Provider_Interface $llm,
+ PRAutoBlogger_Cost_Tracker $cost_tracker
) {
$this->llm = $llm;
$this->cost_tracker = $cost_tracker;
@@ -38,13 +38,13 @@ class Autoblogger_Chief_Editor {
* Side effects: LLM API call(s), cost logging.
*
* @param string $content The generated HTML content.
- * @param Autoblogger_Article_Idea $idea The original article idea.
+ * @param PRAutoBlogger_Article_Idea $idea The original article idea.
*
- * @return Autoblogger_Editorial_Review
+ * @return PRAutoBlogger_Editorial_Review
*/
- public function review( string $content, Autoblogger_Article_Idea $idea ): Autoblogger_Editorial_Review {
- $model = get_option( 'autoblogger_editor_model', AUTOBLOGGER_DEFAULT_EDITOR_MODEL );
- $niche = get_option( 'autoblogger_niche_description', '' );
+ public function review( string $content, PRAutoBlogger_Article_Idea $idea ): PRAutoBlogger_Editorial_Review {
+ $model = get_option( 'prautoblogger_editor_model', PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL );
+ $niche = get_option( 'prautoblogger_niche_description', '' );
$system_prompt = $this->build_system_prompt( $niche );
$user_prompt = $this->build_review_prompt( $content, $idea );
@@ -114,11 +114,11 @@ class Autoblogger_Chief_Editor {
/**
* @param string $content
- * @param Autoblogger_Article_Idea $idea
+ * @param PRAutoBlogger_Article_Idea $idea
*
* @return string
*/
- private function build_review_prompt( string $content, Autoblogger_Article_Idea $idea ): string {
+ private function build_review_prompt( string $content, PRAutoBlogger_Article_Idea $idea ): string {
return sprintf(
"Review this article draft:\n\n" .
"ORIGINAL BRIEF:\n" .
@@ -142,14 +142,14 @@ class Autoblogger_Chief_Editor {
*
* @param string $content JSON response from LLM.
*
- * @return Autoblogger_Editorial_Review
+ * @return PRAutoBlogger_Editorial_Review
*/
- private function parse_review_response( string $content ): Autoblogger_Editorial_Review {
+ private function parse_review_response( string $content ): PRAutoBlogger_Editorial_Review {
$data = json_decode( $content, true );
if ( ! is_array( $data ) || ! isset( $data['verdict'] ) ) {
- Autoblogger_Logger::instance()->error( 'Chief editor response was not valid JSON. Defaulting to rejected.', 'editor' );
- return new Autoblogger_Editorial_Review( [
+ PRAutoBlogger_Logger::instance()->error( 'Chief editor response was not valid JSON. Defaulting to rejected.', 'editor' );
+ return new PRAutoBlogger_Editorial_Review( [
'verdict' => 'rejected',
'notes' => 'Editor response could not be parsed.',
'quality_score' => 0.0,
@@ -163,7 +163,7 @@ class Autoblogger_Chief_Editor {
$verdict = 'rejected';
}
- return new Autoblogger_Editorial_Review( [
+ 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,
diff --git a/includes/core/class-content-analyzer.php b/includes/core/class-content-analyzer.php
index 007310a..aa1aed6 100755
--- a/includes/core/class-content-analyzer.php
+++ b/includes/core/class-content-analyzer.php
@@ -8,7 +8,7 @@ declare(strict_types=1);
* Uses a cheap model (configurable) to keep analysis costs low.
* Batches source data into manageable chunks for the LLM context window.
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 2).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 2).
* Dependencies: LLM_Provider_Interface, Cost_Tracker, WordPress $wpdb.
*
* @see providers/interface-llm-provider.php — LLM used for analysis.
@@ -16,14 +16,14 @@ declare(strict_types=1);
* @see core/class-idea-scorer.php — Consumes analysis results.
* @see ARCHITECTURE.md — Data flow step 2.
*/
-class Autoblogger_Content_Analyzer {
+class PRAutoBlogger_Content_Analyzer {
- private Autoblogger_LLM_Provider_Interface $llm;
- private Autoblogger_Cost_Tracker $cost_tracker;
+ private PRAutoBlogger_LLM_Provider_Interface $llm;
+ private PRAutoBlogger_Cost_Tracker $cost_tracker;
public function __construct(
- Autoblogger_LLM_Provider_Interface $llm,
- Autoblogger_Cost_Tracker $cost_tracker
+ PRAutoBlogger_LLM_Provider_Interface $llm,
+ PRAutoBlogger_Cost_Tracker $cost_tracker
) {
$this->llm = $llm;
$this->cost_tracker = $cost_tracker;
@@ -37,18 +37,18 @@ class Autoblogger_Content_Analyzer {
*
* Side effects: LLM API call, database reads/writes.
*
- * @return Autoblogger_Analysis_Result[] Detected patterns.
+ * @return PRAutoBlogger_Analysis_Result[] Detected patterns.
*/
public function analyze_recent_data(): array {
$source_items = $this->fetch_recent_source_data();
if ( empty( $source_items ) ) {
- Autoblogger_Logger::instance()->info( 'No new source data to analyze.', 'analyzer' );
+ PRAutoBlogger_Logger::instance()->info( 'No new source data to analyze.', 'analyzer' );
return [];
}
- $niche = get_option( 'autoblogger_niche_description', '' );
- $model = get_option( 'autoblogger_analysis_model', AUTOBLOGGER_DEFAULT_ANALYSIS_MODEL );
+ $niche = get_option( 'prautoblogger_niche_description', '' );
+ $model = get_option( 'prautoblogger_analysis_model', PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL );
// Build the source data summary for the LLM (trim to avoid huge contexts).
$summary = $this->build_source_summary( $source_items );
@@ -98,7 +98,7 @@ class Autoblogger_Content_Analyzer {
*/
private function fetch_recent_source_data(): array {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_source_data';
+ $table = $wpdb->prefix . 'prautoblogger_source_data';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
return $wpdb->get_results(
@@ -153,7 +153,7 @@ class Autoblogger_Content_Analyzer {
*/
private function get_performance_context(): string {
global $wpdb;
- $scores_table = $wpdb->prefix . 'autoblogger_content_scores';
+ $scores_table = $wpdb->prefix . 'prautoblogger_content_scores';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$top_posts = $wpdb->get_results(
@@ -239,12 +239,12 @@ class Autoblogger_Content_Analyzer {
* @param string $content LLM response content (JSON).
* @param array> $source_items Original source data for ID mapping.
*
- * @return Autoblogger_Analysis_Result[]
+ * @return PRAutoBlogger_Analysis_Result[]
*/
private function parse_analysis_response( string $content, array $source_items ): array {
$data = json_decode( $content, true );
if ( ! isset( $data['patterns'] ) || ! is_array( $data['patterns'] ) ) {
- Autoblogger_Logger::instance()->error( 'Analysis response was not valid JSON or missing patterns key.', 'analyzer' );
+ PRAutoBlogger_Logger::instance()->error( 'Analysis response was not valid JSON or missing patterns key.', 'analyzer' );
return [];
}
@@ -252,7 +252,7 @@ class Autoblogger_Content_Analyzer {
$results = [];
foreach ( $data['patterns'] as $pattern ) {
- $results[] = new Autoblogger_Analysis_Result( [
+ $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'] ?? '' ),
@@ -276,13 +276,13 @@ class Autoblogger_Content_Analyzer {
*
* Side effects: database inserts.
*
- * @param Autoblogger_Analysis_Result[] $results
+ * @param PRAutoBlogger_Analysis_Result[] $results
*
* @return void
*/
private function store_results( array $results ): void {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_analysis_results';
+ $table = $wpdb->prefix . 'prautoblogger_analysis_results';
foreach ( $results as $result ) {
$row = $result->to_db_row();
diff --git a/includes/core/class-content-generator.php b/includes/core/class-content-generator.php
index 7b960ce..232af0a 100755
--- a/includes/core/class-content-generator.php
+++ b/includes/core/class-content-generator.php
@@ -8,21 +8,21 @@ declare(strict_types=1);
* - single_pass: One LLM call produces the complete article (cheaper, faster).
* - multi_step: Outline → Draft → Polish pipeline (higher quality, more cost).
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 4).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 4).
* Dependencies: LLM_Provider_Interface, Cost_Tracker.
*
* @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.
*/
-class Autoblogger_Content_Generator {
+class PRAutoBlogger_Content_Generator {
- private Autoblogger_LLM_Provider_Interface $llm;
- private Autoblogger_Cost_Tracker $cost_tracker;
+ private PRAutoBlogger_LLM_Provider_Interface $llm;
+ private PRAutoBlogger_Cost_Tracker $cost_tracker;
public function __construct(
- Autoblogger_LLM_Provider_Interface $llm,
- Autoblogger_Cost_Tracker $cost_tracker
+ PRAutoBlogger_LLM_Provider_Interface $llm,
+ PRAutoBlogger_Cost_Tracker $cost_tracker
) {
$this->llm = $llm;
$this->cost_tracker = $cost_tracker;
@@ -35,23 +35,23 @@ class Autoblogger_Content_Generator {
*
* Side effects: LLM API calls (1-3 depending on mode), cost logging.
*
- * @param Autoblogger_Article_Idea $idea The scored idea to generate content for.
+ * @param PRAutoBlogger_Article_Idea $idea The scored idea to generate content for.
*
* @return string Generated HTML content.
*
* @throws \RuntimeException On generation failure.
*/
- public function generate( Autoblogger_Article_Idea $idea ): string {
- $mode = get_option( 'autoblogger_writing_pipeline', 'multi_step' );
+ public function generate( PRAutoBlogger_Article_Idea $idea ): string {
+ $mode = get_option( 'prautoblogger_writing_pipeline', 'multi_step' );
- $request = new Autoblogger_Content_Request(
+ $request = new PRAutoBlogger_Content_Request(
$idea,
$mode,
- get_option( 'autoblogger_tone', 'informational' ),
- absint( get_option( 'autoblogger_min_word_count', 800 ) ),
- absint( get_option( 'autoblogger_max_word_count', 2000 ) ),
- get_option( 'autoblogger_niche_description', '' ),
- json_decode( get_option( 'autoblogger_topic_exclusions', '[]' ), true ) ?: []
+ get_option( 'prautoblogger_tone', 'informational' ),
+ 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 ) ?: []
);
if ( 'single_pass' === $mode ) {
@@ -64,12 +64,12 @@ class Autoblogger_Content_Generator {
/**
* Single-pass generation: one LLM call produces the complete article.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
*
* @return string Generated HTML content.
*/
- private function generate_single_pass( Autoblogger_Content_Request $request ): string {
- $model = get_option( 'autoblogger_writing_model', AUTOBLOGGER_DEFAULT_WRITING_MODEL );
+ 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 );
@@ -102,12 +102,12 @@ class Autoblogger_Content_Generator {
/**
* Multi-step generation: Outline → Draft → Polish.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
*
* @return string Generated HTML content.
*/
- private function generate_multi_step( Autoblogger_Content_Request $request ): string {
- $model = get_option( 'autoblogger_writing_model', AUTOBLOGGER_DEFAULT_WRITING_MODEL );
+ 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 );
@@ -124,12 +124,12 @@ class Autoblogger_Content_Generator {
/**
* Multi-step stage 1: Generate an article outline.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
* @param string $model
*
* @return string The outline text.
*/
- private function stage_outline( Autoblogger_Content_Request $request, string $model ): string {
+ private function stage_outline( PRAutoBlogger_Content_Request $request, string $model ): string {
$idea = $request->get_idea();
$prompt = sprintf(
@@ -168,13 +168,13 @@ class Autoblogger_Content_Generator {
/**
* Multi-step stage 2: Write the full draft from the outline.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
* @param string $outline
* @param string $model
*
* @return string Draft HTML content.
*/
- private function stage_draft( Autoblogger_Content_Request $request, string $outline, string $model ): string {
+ 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" .
@@ -213,13 +213,13 @@ class Autoblogger_Content_Generator {
/**
* Multi-step stage 3: Polish and refine the draft.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
* @param string $draft
* @param string $model
*
* @return string Polished HTML content.
*/
- private function stage_polish( Autoblogger_Content_Request $request, string $draft, string $model ): string {
+ 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" .
@@ -249,11 +249,11 @@ class Autoblogger_Content_Generator {
/**
* Build the system prompt shared across all writing stages.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
*
* @return string
*/
- private function build_writer_system_prompt( Autoblogger_Content_Request $request ): 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 ) {
@@ -268,11 +268,11 @@ class Autoblogger_Content_Generator {
/**
* Build the user prompt for single-pass generation.
*
- * @param Autoblogger_Content_Request $request
+ * @param PRAutoBlogger_Content_Request $request
*
* @return string
*/
- private function build_single_pass_prompt( Autoblogger_Content_Request $request ): string {
+ private function build_single_pass_prompt( PRAutoBlogger_Content_Request $request ): string {
$idea = $request->get_idea();
return sprintf(
diff --git a/includes/core/class-cost-tracker.php b/includes/core/class-cost-tracker.php
index cc82f71..fa91a9a 100755
--- a/includes/core/class-cost-tracker.php
+++ b/includes/core/class-cost-tracker.php
@@ -11,15 +11,15 @@ declare(strict_types=1);
*
* Triggered by: Every class that makes an LLM API call (Content_Analyzer,
* Content_Generator, Chief_Editor, Metrics_Collector).
- * Dependencies: WordPress $wpdb, Autoblogger_OpenRouter_Provider (for cost estimation).
+ * Dependencies: WordPress $wpdb, PRAutoBlogger_OpenRouter_Provider (for cost estimation).
*
* @see core/class-content-analyzer.php — Calls log_api_call() after analysis.
* @see core/class-content-generator.php — Calls log_api_call() after each stage.
* @see core/class-chief-editor.php — Calls log_api_call() after review.
* @see admin/class-metrics-page.php — Displays cost data from this class.
- * @see ARCHITECTURE.md — ab_generation_log table schema.
+ * @see ARCHITECTURE.md — prab_generation_log table schema.
*/
-class Autoblogger_Cost_Tracker {
+class PRAutoBlogger_Cost_Tracker {
/**
* Running cost for the current pipeline execution.
@@ -59,7 +59,7 @@ class Autoblogger_Cost_Tracker {
/**
* Log an API call with its token usage and estimated cost.
*
- * Side effects: database insert into ab_generation_log.
+ * Side effects: database insert into prab_generation_log.
*
* @param int|null $post_id WordPress post ID (null during generation, set later).
* @param string $stage Pipeline stage: 'analysis', 'outline', 'draft', 'polish', 'review'.
@@ -82,12 +82,12 @@ class Autoblogger_Cost_Tracker {
string $response_status = 'success',
string $error_message = ''
): void {
- $llm = new Autoblogger_OpenRouter_Provider();
+ $llm = new PRAutoBlogger_OpenRouter_Provider();
$cost = $llm->estimate_cost( $model, $prompt_tokens, $completion_tokens );
$this->current_run_cost += $cost;
- $log_entry = new Autoblogger_Generation_Log( [
+ $log_entry = new PRAutoBlogger_Generation_Log( [
'post_id' => $post_id,
'run_id' => $this->run_id,
'stage' => $stage,
@@ -102,13 +102,13 @@ class Autoblogger_Cost_Tracker {
] );
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_generation_log';
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->insert( $table, $log_entry->to_db_row() );
if ( false === $wpdb->insert_id ) {
- Autoblogger_Logger::instance()->error( 'Failed to log API call: ' . $wpdb->last_error, 'cost-tracker' );
+ PRAutoBlogger_Logger::instance()->error( 'Failed to log API call: ' . $wpdb->last_error, 'cost-tracker' );
}
}
@@ -121,7 +121,7 @@ class Autoblogger_Cost_Tracker {
* @return bool True if monthly spend >= configured budget.
*/
public function is_budget_exceeded(): bool {
- $budget = (float) get_option( 'autoblogger_monthly_budget_usd', 50.00 );
+ $budget = (float) get_option( 'prautoblogger_monthly_budget_usd', 50.00 );
if ( $budget <= 0 ) {
// Budget of 0 means no limit.
return false;
@@ -141,7 +141,7 @@ class Autoblogger_Cost_Tracker {
*/
public function get_monthly_spend(): float {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_generation_log';
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
$first_of_month = gmdate( 'Y-m-01 00:00:00' );
@@ -174,7 +174,7 @@ class Autoblogger_Cost_Tracker {
*/
public function get_daily_spend( int $days = 30 ): array {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_generation_log';
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
$start_date = gmdate( 'Y-m-d', time() - ( $days * DAY_IN_SECONDS ) );
@@ -209,7 +209,7 @@ class Autoblogger_Cost_Tracker {
*/
public function get_spend_by_stage( string $start_date, string $end_date ): array {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_generation_log';
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_results(
@@ -245,7 +245,7 @@ class Autoblogger_Cost_Tracker {
* @return float Percentage (0-100+). Can exceed 100 if overspent.
*/
public function get_budget_utilization(): float {
- $budget = (float) get_option( 'autoblogger_monthly_budget_usd', 50.00 );
+ $budget = (float) get_option( 'prautoblogger_monthly_budget_usd', 50.00 );
if ( $budget <= 0 ) {
return 0.0;
}
diff --git a/includes/core/class-ga4-client.php b/includes/core/class-ga4-client.php
index e0c6de1..3952b3d 100755
--- a/includes/core/class-ga4-client.php
+++ b/includes/core/class-ga4-client.php
@@ -7,13 +7,13 @@ declare(strict_types=1);
* Handles OAuth2 service account authentication and the GA4 Data API.
* Decoupled from metrics collection logic for testability and reusability.
*
- * Triggered by: Autoblogger_Metrics_Collector::fetch_ga4_data() calls methods here.
- * Dependencies: Autoblogger_Encryption (for credentials decryption), wp_remote_post/get().
+ * Triggered by: PRAutoBlogger_Metrics_Collector::fetch_ga4_data() calls methods here.
+ * Dependencies: PRAutoBlogger_Encryption (for credentials decryption), wp_remote_post/get().
*
* @see core/class-metrics-collector.php — Instantiates this class.
* @see ARCHITECTURE.md — External API integrations table.
*/
-class Autoblogger_GA4_Client {
+class PRAutoBlogger_GA4_Client {
/**
* Fetch Google Analytics 4 data for given posts.
@@ -27,17 +27,17 @@ class Autoblogger_GA4_Client {
* @return array
*/
public function fetch_data( array $posts ): array {
- $property_id = get_option( 'autoblogger_ga4_property_id', '' );
+ $property_id = get_option( 'prautoblogger_ga4_property_id', '' );
if ( '' === $property_id ) {
return [];
}
- $credentials_encrypted = get_option( 'autoblogger_ga4_credentials_json', '' );
+ $credentials_encrypted = get_option( 'prautoblogger_ga4_credentials_json', '' );
if ( '' === $credentials_encrypted ) {
return [];
}
- $credentials = Autoblogger_Encryption::decrypt( $credentials_encrypted );
+ $credentials = PRAutoBlogger_Encryption::decrypt( $credentials_encrypted );
if ( '' === $credentials ) {
return [];
}
@@ -61,7 +61,7 @@ class Autoblogger_GA4_Client {
// Using the REST API directly since we don't want a full Google SDK dependency.
$access_token = $this->get_access_token( $credentials );
if ( '' === $access_token ) {
- Autoblogger_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 AutoBlogger → Settings.', 'ga4' );
+ 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 [];
}
@@ -94,14 +94,14 @@ class Autoblogger_GA4_Client {
);
if ( is_wp_error( $response ) ) {
- Autoblogger_Logger::instance()->error( 'GA4 API request failed (WP HTTP error): ' . $response->get_error_message(), 'ga4' );
+ PRAutoBlogger_Logger::instance()->error( 'GA4 API request failed (WP HTTP error): ' . $response->get_error_message(), 'ga4' );
return [];
}
$ga4_status = wp_remote_retrieve_response_code( $response );
if ( 200 !== $ga4_status ) {
$ga4_body = wp_remote_retrieve_body( $response );
- Autoblogger_Logger::instance()->error(
+ PRAutoBlogger_Logger::instance()->error(
sprintf( 'GA4 API request failed (HTTP %d): %s', $ga4_status, substr( $ga4_body, 0, 500 ) ),
'ga4'
);
@@ -159,7 +159,7 @@ class Autoblogger_GA4_Client {
$key = openssl_pkey_get_private( $creds['private_key'] );
if ( false === $key ) {
- Autoblogger_Logger::instance()->error( 'GA4: Failed to parse private key from service account credentials. The key may be malformed or the OpenSSL extension may be misconfigured.', 'ga4' );
+ PRAutoBlogger_Logger::instance()->error( 'GA4: Failed to parse private key from service account credentials. The key may be malformed or the OpenSSL extension may be misconfigured.', 'ga4' );
return '';
}
@@ -181,7 +181,7 @@ class Autoblogger_GA4_Client {
);
if ( is_wp_error( $response ) ) {
- Autoblogger_Logger::instance()->error( 'GA4: OAuth token exchange failed (WP HTTP error): ' . $response->get_error_message(), 'ga4' );
+ PRAutoBlogger_Logger::instance()->error( 'GA4: OAuth token exchange failed (WP HTTP error): ' . $response->get_error_message(), 'ga4' );
return '';
}
@@ -190,7 +190,7 @@ class Autoblogger_GA4_Client {
if ( 200 !== $token_status || ! isset( $data['access_token'] ) ) {
$error_desc = $data['error_description'] ?? $data['error'] ?? 'unknown';
- Autoblogger_Logger::instance()->error(
+ PRAutoBlogger_Logger::instance()->error(
sprintf( 'GA4: OAuth token exchange failed (HTTP %d): %s', $token_status, $error_desc ),
'ga4'
);
diff --git a/includes/core/class-idea-scorer.php b/includes/core/class-idea-scorer.php
index 4a4ff48..03f60c3 100755
--- a/includes/core/class-idea-scorer.php
+++ b/includes/core/class-idea-scorer.php
@@ -8,25 +8,25 @@ declare(strict_types=1);
* scores ideas by relevance, frequency, and freshness, then returns
* the top N ideas matching the daily target.
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 3).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 3).
* Dependencies: WordPress $wpdb, WP_Query.
*
* @see core/class-content-analyzer.php — Produces the analysis results we consume.
* @see core/class-content-generator.php — Consumes the ranked ideas we produce.
* @see ARCHITECTURE.md — Data flow step 3.
*/
-class Autoblogger_Idea_Scorer {
+class PRAutoBlogger_Idea_Scorer {
/**
* Score, deduplicate, and rank article ideas.
*
- * @param Autoblogger_Analysis_Result[] $analysis_results Raw analysis output.
+ * @param PRAutoBlogger_Analysis_Result[] $analysis_results Raw analysis output.
* @param int $target_count Number of ideas to return.
*
- * @return Autoblogger_Article_Idea[] Top-ranked ideas, sorted by score descending.
+ * @return PRAutoBlogger_Article_Idea[] Top-ranked ideas, sorted by score descending.
*/
public function score_and_rank( array $analysis_results, int $target_count ): array {
- $exclusions = json_decode( get_option( 'autoblogger_topic_exclusions', '[]' ), true ) ?: [];
+ $exclusions = json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: [];
$ideas = [];
foreach ( $analysis_results as $result ) {
@@ -42,7 +42,7 @@ class Autoblogger_Idea_Scorer {
$metadata = $result->get_metadata() ?? [];
- $ideas[] = new Autoblogger_Article_Idea( [
+ $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(),
@@ -56,7 +56,7 @@ class Autoblogger_Idea_Scorer {
}
// Sort by score descending.
- usort( $ideas, static function ( Autoblogger_Article_Idea $a, Autoblogger_Article_Idea $b ): int {
+ usort( $ideas, static function ( PRAutoBlogger_Article_Idea $a, PRAutoBlogger_Article_Idea $b ): int {
return $b->get_score() <=> $a->get_score();
} );
@@ -68,11 +68,11 @@ class Autoblogger_Idea_Scorer {
*
* Factors: LLM relevance score (50%), frequency (30%), recency bonus (20%).
*
- * @param Autoblogger_Analysis_Result $result
+ * @param PRAutoBlogger_Analysis_Result $result
*
* @return float Score between 0 and 1.
*/
- private function compute_score( Autoblogger_Analysis_Result $result ): float {
+ private function compute_score( PRAutoBlogger_Analysis_Result $result ): float {
$relevance = min( $result->get_relevance_score(), 1.0 );
$frequency = min( $result->get_frequency() / 10.0, 1.0 );
@@ -114,7 +114,7 @@ class Autoblogger_Idea_Scorer {
$query = new \WP_Query( [
'post_type' => 'post',
'post_status' => [ 'publish', 'draft', 'pending' ],
- 'meta_key' => '_autoblogger_generated',
+ 'meta_key' => '_prautoblogger_generated',
'meta_value' => '1',
's' => $topic,
'posts_per_page' => 1,
diff --git a/includes/core/class-logger.php b/includes/core/class-logger.php
index 457729d..f887e3b 100755
--- a/includes/core/class-logger.php
+++ b/includes/core/class-logger.php
@@ -6,7 +6,7 @@ declare(strict_types=1);
*
* Writes log entries to a custom database table and optionally forwards
* to PHP's error_log() for server-level monitoring. The log level is
- * user-configurable via Settings → AutoBlogger → General.
+ * user-configurable via Settings → PRAutoBlogger → General.
*
* Levels (ascending verbosity): error → warning → info → debug.
* Setting the level to "info" captures error, warning, and info — not debug.
@@ -15,10 +15,10 @@ declare(strict_types=1);
* Dependencies: WordPress $wpdb.
*
* @see admin/class-log-viewer.php — Reads and displays log entries.
- * @see class-activator.php — Creates the ab_event_log table.
+ * @see class-activator.php — Creates the prab_event_log table.
* @see ARCHITECTURE.md — Logging architecture section.
*/
-class Autoblogger_Logger {
+class PRAutoBlogger_Logger {
/** Log levels, lower = more severe. */
public const LEVEL_ERROR = 0;
@@ -47,7 +47,7 @@ class Autoblogger_Logger {
private int $threshold;
private function __construct() {
- $setting = get_option( 'autoblogger_log_level', 'info' );
+ $setting = get_option( 'prautoblogger_log_level', 'info' );
$this->threshold = self::LEVEL_MAP[ $setting ] ?? self::LEVEL_INFO;
}
@@ -122,11 +122,11 @@ class Autoblogger_Logger {
// Always forward errors to PHP error_log for server-level monitoring.
if ( $level <= self::LEVEL_WARNING ) {
- error_log( sprintf( '[AutoBlogger][%s] %s — %s', strtoupper( $label ), $context, $message ) );
+ error_log( sprintf( '[PRAutoBlogger][%s] %s — %s', strtoupper( $label ), $context, $message ) );
}
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_event_log';
+ $table = $wpdb->prefix . 'prautoblogger_event_log';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->insert( $table, [
@@ -155,7 +155,7 @@ class Autoblogger_Logger {
int $per_page = 50
): array {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_event_log';
+ $table = $wpdb->prefix . 'prautoblogger_event_log';
$where = [];
$params = [];
@@ -207,7 +207,7 @@ class Autoblogger_Logger {
*/
public static function prune( int $days = 30 ): int {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_event_log';
+ $table = $wpdb->prefix . 'prautoblogger_event_log';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
return (int) $wpdb->query(
diff --git a/includes/core/class-metrics-collector.php b/includes/core/class-metrics-collector.php
index 7257961..7932b08 100755
--- a/includes/core/class-metrics-collector.php
+++ b/includes/core/class-metrics-collector.php
@@ -5,21 +5,21 @@ declare(strict_types=1);
* Collects post performance metrics from WordPress native data and Google Analytics 4.
*
* Runs on a separate cron schedule (every 6 hours) to gather pageviews, bounce rate,
- * time on page, and comments for AutoBlogger-generated posts. Computes a composite
+ * time on page, and comments for PRAutoBlogger-generated posts. Computes a composite
* "content score" that feeds back into the analysis engine for self-improvement.
*
- * Triggered by: WP-Cron hook `autoblogger_collect_metrics` (every 6 hours).
- * Dependencies: WordPress $wpdb, Autoblogger_GA4_Client (for GA4 API calls).
+ * Triggered by: WP-Cron hook `prautoblogger_collect_metrics` (every 6 hours).
+ * Dependencies: WordPress $wpdb, PRAutoBlogger_GA4_Client (for GA4 API calls).
*
* @see core/class-ga4-client.php — Handles GA4 API authentication and requests.
* @see core/class-content-analyzer.php — Reads scores for self-improvement loop.
* @see admin/class-metrics-page.php — Displays collected metrics.
* @see ARCHITECTURE.md — Data flow step 7.
*/
-class Autoblogger_Metrics_Collector {
+class PRAutoBlogger_Metrics_Collector {
/**
- * Collect metrics for all AutoBlogger-generated posts.
+ * Collect metrics for all PRAutoBlogger-generated posts.
*
* Gathers WordPress native metrics (comments) and GA4 data (if configured),
* then computes composite scores.
@@ -34,7 +34,7 @@ class Autoblogger_Metrics_Collector {
return 0;
}
- $ga4_client = new Autoblogger_GA4_Client();
+ $ga4_client = new PRAutoBlogger_GA4_Client();
$ga4_data = $ga4_client->fetch_data( $posts );
$scored = 0;
@@ -54,12 +54,12 @@ class Autoblogger_Metrics_Collector {
$scored++;
}
- Autoblogger_Logger::instance()->info( sprintf( 'Metrics collected for %d posts.', $scored ), 'metrics' );
+ PRAutoBlogger_Logger::instance()->info( sprintf( 'Metrics collected for %d posts.', $scored ), 'metrics' );
return $scored;
}
/**
- * Get all AutoBlogger-generated published posts.
+ * Get all PRAutoBlogger-generated published posts.
*
* @return \WP_Post[]
*/
@@ -67,7 +67,7 @@ class Autoblogger_Metrics_Collector {
$query = new \WP_Query( [
'post_type' => 'post',
'post_status' => 'publish',
- 'meta_key' => '_autoblogger_generated',
+ 'meta_key' => '_prautoblogger_generated',
'meta_value' => '1',
'posts_per_page' => 100,
'orderby' => 'date',
@@ -89,7 +89,7 @@ class Autoblogger_Metrics_Collector {
// Post may have been deleted between the WP_Query and this call.
if ( null === $post ) {
- Autoblogger_Logger::instance()->debug( sprintf( 'Metrics: Post ID %d no longer exists, skipping.', $post_id ), 'metrics' );
+ PRAutoBlogger_Logger::instance()->debug( sprintf( 'Metrics: Post ID %d no longer exists, skipping.', $post_id ), 'metrics' );
return null;
}
@@ -160,9 +160,9 @@ class Autoblogger_Metrics_Collector {
*/
private function store_score( int $post_id, array $wp_metrics, array $ga_metrics, array $score_data ): void {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_content_scores';
+ $table = $wpdb->prefix . 'prautoblogger_content_scores';
- $entry = new Autoblogger_Content_Score( [
+ $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,
diff --git a/includes/core/class-pipeline-runner.php b/includes/core/class-pipeline-runner.php
index f8e7046..9cb47a4 100755
--- a/includes/core/class-pipeline-runner.php
+++ b/includes/core/class-pipeline-runner.php
@@ -7,14 +7,14 @@ declare(strict_types=1);
* Orchestrates: Budget check → Collect source data → Analyze patterns →
* Score ideas → Generate content → Editorial review → Publish.
*
- * Triggered by: Autoblogger::on_daily_generation() and Autoblogger::on_ajax_generate_now().
+ * Triggered by: PRAutoBlogger::on_daily_generation() and PRAutoBlogger::on_ajax_generate_now().
* Dependencies: All core content classes (Collector, Analyzer, Scorer, Generator, Editor, Publisher).
*
- * @see class-autoblogger.php — Instantiates this class and calls run().
+ * @see class-prautoblogger.php — Instantiates this class and calls run().
* @see core/class-cost-tracker.php — Tracks cumulative API costs throughout the run.
* @see ARCHITECTURE.md — Data flow diagram showing pipeline steps.
*/
-class Autoblogger_Pipeline_Runner {
+class PRAutoBlogger_Pipeline_Runner {
/**
* Execute the full content generation pipeline.
@@ -29,7 +29,7 @@ class Autoblogger_Pipeline_Runner {
* @throws \RuntimeException If budget is exceeded or critical step fails.
*/
public function run(): array {
- $cost_tracker = new Autoblogger_Cost_Tracker();
+ $cost_tracker = new PRAutoBlogger_Cost_Tracker();
// Tag all log entries in this run with a unique ID so link_generation_logs()
// can accurately attribute costs to the correct post in batch runs.
@@ -38,35 +38,35 @@ class Autoblogger_Pipeline_Runner {
// Hard stop if monthly budget is exceeded.
if ( $cost_tracker->is_budget_exceeded() ) {
throw new \RuntimeException(
- __( 'Monthly API budget exceeded. Generation halted.', 'autoblogger' )
+ __( 'Monthly API budget exceeded. Generation halted.', 'prautoblogger' )
);
}
- $target_count = absint( get_option( 'autoblogger_daily_article_target', 1 ) );
+ $target_count = absint( get_option( 'prautoblogger_daily_article_target', 1 ) );
// Step 1: Collect source data.
- $collector = new Autoblogger_Source_Collector();
+ $collector = new PRAutoBlogger_Source_Collector();
$collector->collect_from_all_sources();
// Step 2: Analyze collected data for patterns.
- $llm = new Autoblogger_OpenRouter_Provider();
- $analyzer = new Autoblogger_Content_Analyzer( $llm, $cost_tracker );
+ $llm = new PRAutoBlogger_OpenRouter_Provider();
+ $analyzer = new PRAutoBlogger_Content_Analyzer( $llm, $cost_tracker );
$analysis = $analyzer->analyze_recent_data();
// Step 3: Score and deduplicate ideas.
- $scorer = new Autoblogger_Idea_Scorer();
+ $scorer = new PRAutoBlogger_Idea_Scorer();
$ideas = $scorer->score_and_rank( $analysis, $target_count );
if ( empty( $ideas ) ) {
- Autoblogger_Logger::instance()->info( 'No viable article ideas found. Skipping generation.', 'pipeline' );
+ PRAutoBlogger_Logger::instance()->info( 'No viable article ideas found. Skipping generation.', 'pipeline' );
return [ 'generated' => 0, 'published' => 0, 'rejected' => 0, 'cost' => 0.0 ];
}
// Step 4 & 5: Generate content and run through editor for each idea.
- $generator = new Autoblogger_Content_Generator( $llm, $cost_tracker );
- $editor = new Autoblogger_Chief_Editor( $llm, $cost_tracker );
- $publisher = new Autoblogger_Publisher();
- $auto_publish = in_array( get_option( 'autoblogger_auto_publish', '0' ), [ '1', 'yes' ], true );
+ $generator = new PRAutoBlogger_Content_Generator( $llm, $cost_tracker );
+ $editor = new PRAutoBlogger_Chief_Editor( $llm, $cost_tracker );
+ $publisher = new PRAutoBlogger_Publisher();
+ $auto_publish = in_array( get_option( 'prautoblogger_auto_publish', '0' ), [ '1', 'yes' ], true );
$generated = 0;
$published = 0;
@@ -75,7 +75,7 @@ class Autoblogger_Pipeline_Runner {
foreach ( $ideas as $idea ) {
// Re-check budget before each article — stop early if we hit the limit.
if ( $cost_tracker->is_budget_exceeded() ) {
- Autoblogger_Logger::instance()->warning( 'Budget limit reached during generation. Stopping early.', 'pipeline' );
+ PRAutoBlogger_Logger::instance()->warning( 'Budget limit reached during generation. Stopping early.', 'pipeline' );
break;
}
@@ -103,17 +103,17 @@ class Autoblogger_Pipeline_Runner {
} else {
$publisher->save_as_draft( $content, $idea, $review, $cost_tracker->get_run_id() );
$rejected++;
- Autoblogger_Logger::instance()->info( 'Article rejected by editor: ' . $idea->get_topic(), 'pipeline' );
+ PRAutoBlogger_Logger::instance()->info( 'Article rejected by editor: ' . $idea->get_topic(), 'pipeline' );
}
} catch ( \Exception $e ) {
- Autoblogger_Logger::instance()->error( 'Failed to generate article for topic "' . $idea->get_topic() . '": ' . $e->getMessage(), 'pipeline' );
+ PRAutoBlogger_Logger::instance()->error( 'Failed to generate article for topic "' . $idea->get_topic() . '": ' . $e->getMessage(), 'pipeline' );
// Continue with next idea — don't let one failure stop the batch.
}
}
$total_cost = $cost_tracker->get_current_run_cost();
- Autoblogger_Logger::instance()->info(
+ PRAutoBlogger_Logger::instance()->info(
sprintf( 'Pipeline complete: %d generated, %d published, %d rejected. Cost: $%.4f', $generated, $published, $rejected, $total_cost ),
'pipeline'
);
diff --git a/includes/core/class-publisher.php b/includes/core/class-publisher.php
index 2eae3a9..9c2fe5c 100755
--- a/includes/core/class-publisher.php
+++ b/includes/core/class-publisher.php
@@ -7,14 +7,14 @@ declare(strict_types=1);
* Handles both publishing (editor-approved) and saving as draft (editor-rejected).
* Stores all generation metadata as post_meta for transparency and auditability.
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 6).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 6).
* Dependencies: WordPress wp_insert_post(), post meta API.
*
* @see core/class-chief-editor.php — Produces the editorial review we consume.
* @see core/class-content-generator.php — Produces the content we publish.
* @see ARCHITECTURE.md — Data flow step 6.
*/
-class Autoblogger_Publisher {
+class PRAutoBlogger_Publisher {
/**
* Publish an editor-approved article.
@@ -25,8 +25,8 @@ class Autoblogger_Publisher {
* Side effects: Creates a WordPress post, writes post_meta.
*
* @param string $content The final HTML content.
- * @param Autoblogger_Article_Idea $idea The original article idea.
- * @param Autoblogger_Editorial_Review $review The editor's review.
+ * @param PRAutoBlogger_Article_Idea $idea The original article idea.
+ * @param PRAutoBlogger_Editorial_Review $review The editor's review.
* @param string|null $run_id Pipeline run ID for log linking.
*
* @return int The created post ID.
@@ -35,8 +35,8 @@ class Autoblogger_Publisher {
*/
public function publish(
string $content,
- Autoblogger_Article_Idea $idea,
- Autoblogger_Editorial_Review $review,
+ PRAutoBlogger_Article_Idea $idea,
+ PRAutoBlogger_Editorial_Review $review,
?string $run_id = null
): int {
return $this->create_post( $content, $idea, $review, 'publish', $run_id );
@@ -46,8 +46,8 @@ class Autoblogger_Publisher {
* Save an editor-rejected article as a draft for human review.
*
* @param string $content The generated HTML content (pre-revision).
- * @param Autoblogger_Article_Idea $idea The original article idea.
- * @param Autoblogger_Editorial_Review $review The editor's review with rejection notes.
+ * @param PRAutoBlogger_Article_Idea $idea The original article idea.
+ * @param PRAutoBlogger_Editorial_Review $review The editor's review with rejection notes.
* @param string|null $run_id Pipeline run ID for log linking.
*
* @return int The created post ID.
@@ -56,8 +56,8 @@ class Autoblogger_Publisher {
*/
public function save_as_draft(
string $content,
- Autoblogger_Article_Idea $idea,
- Autoblogger_Editorial_Review $review,
+ PRAutoBlogger_Article_Idea $idea,
+ PRAutoBlogger_Editorial_Review $review,
?string $run_id = null
): int {
return $this->create_post( $content, $idea, $review, 'draft', $run_id );
@@ -67,8 +67,8 @@ class Autoblogger_Publisher {
* Create a WordPress post with generation metadata.
*
* @param string $content HTML content.
- * @param Autoblogger_Article_Idea $idea Article idea.
- * @param Autoblogger_Editorial_Review $review Editorial review.
+ * @param PRAutoBlogger_Article_Idea $idea Article idea.
+ * @param PRAutoBlogger_Editorial_Review $review Editorial review.
* @param string $post_status 'publish' or 'draft'.
* @param string|null $run_id Pipeline run ID for log linking.
*
@@ -78,8 +78,8 @@ class Autoblogger_Publisher {
*/
private function create_post(
string $content,
- Autoblogger_Article_Idea $idea,
- Autoblogger_Editorial_Review $review,
+ PRAutoBlogger_Article_Idea $idea,
+ PRAutoBlogger_Editorial_Review $review,
string $post_status,
?string $run_id = null
): int {
@@ -93,15 +93,15 @@ class Autoblogger_Publisher {
];
/**
- * Fires before an AutoBlogger post is created.
+ * Fires before an PRAutoBlogger post is created.
*
- * Listeners registered in: class-autoblogger.php (main loader).
+ * Listeners registered in: class-prautoblogger.php (main loader).
*
* @param array $post_data Post data array.
- * @param Autoblogger_Article_Idea $idea The article idea.
- * @param Autoblogger_Editorial_Review $review The editorial review.
+ * @param PRAutoBlogger_Article_Idea $idea The article idea.
+ * @param PRAutoBlogger_Editorial_Review $review The editorial review.
*/
- $post_data = apply_filters( 'autoblogger_filter_post_data', $post_data, $idea, $review );
+ $post_data = apply_filters( 'prautoblogger_filter_post_data', $post_data, $idea, $review );
$post_id = wp_insert_post( $post_data, true );
@@ -109,7 +109,7 @@ class Autoblogger_Publisher {
throw new \RuntimeException(
sprintf(
/* translators: %s: WordPress error message */
- __( 'Failed to create post: %s', 'autoblogger' ),
+ __( 'Failed to create post: %s', 'prautoblogger' ),
$post_id->get_error_message()
)
);
@@ -121,20 +121,20 @@ class Autoblogger_Publisher {
// Update generation log entries to reference this post.
$this->link_generation_logs( $post_id, $run_id );
- Autoblogger_Logger::instance()->info(
+ PRAutoBlogger_Logger::instance()->info(
sprintf( 'Post created: ID=%d, status=%s, title="%s"', $post_id, $post_status, $idea->get_suggested_title() ),
'publisher'
);
/**
- * Fires after an AutoBlogger post is successfully created.
+ * Fires after an PRAutoBlogger post is successfully created.
*
* @param int $post_id The new post ID.
* @param string $post_status 'publish' or 'draft'.
- * @param Autoblogger_Article_Idea $idea The article idea.
- * @param Autoblogger_Editorial_Review $review The editorial review.
+ * @param PRAutoBlogger_Article_Idea $idea The article idea.
+ * @param PRAutoBlogger_Editorial_Review $review The editorial review.
*/
- do_action( 'autoblogger_post_created', $post_id, $post_status, $idea, $review );
+ do_action( 'prautoblogger_post_created', $post_id, $post_status, $idea, $review );
return $post_id;
}
@@ -142,32 +142,32 @@ class Autoblogger_Publisher {
/**
* Build post_meta array for generation metadata.
*
- * @param Autoblogger_Article_Idea $idea
- * @param Autoblogger_Editorial_Review $review
+ * @param PRAutoBlogger_Article_Idea $idea
+ * @param PRAutoBlogger_Editorial_Review $review
*
* @return array
*/
private function build_meta(
- Autoblogger_Article_Idea $idea,
- Autoblogger_Editorial_Review $review
+ PRAutoBlogger_Article_Idea $idea,
+ PRAutoBlogger_Editorial_Review $review
): array {
- $pipeline_mode = get_option( 'autoblogger_writing_pipeline', 'multi_step' );
- $model = get_option( 'autoblogger_writing_model', AUTOBLOGGER_DEFAULT_WRITING_MODEL );
+ $pipeline_mode = get_option( 'prautoblogger_writing_pipeline', 'multi_step' );
+ $model = get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL );
return [
- '_autoblogger_generated' => '1',
- '_autoblogger_analysis_id' => $idea->get_analysis_id(),
- '_autoblogger_source_ids' => wp_json_encode( $idea->get_source_ids() ),
- '_autoblogger_model_used' => $model,
- '_autoblogger_pipeline_mode' => $pipeline_mode,
- '_autoblogger_editor_verdict' => $review->get_verdict(),
- '_autoblogger_editor_notes' => $review->get_notes(),
- '_autoblogger_quality_score' => $review->get_quality_score(),
- '_autoblogger_seo_score' => $review->get_seo_score(),
- '_autoblogger_generated_at' => gmdate( 'c' ),
- '_autoblogger_topic' => $idea->get_topic(),
- '_autoblogger_article_type' => $idea->get_article_type(),
- '_autoblogger_target_keywords' => wp_json_encode( $idea->get_target_keywords() ),
+ '_prautoblogger_generated' => '1',
+ '_prautoblogger_analysis_id' => $idea->get_analysis_id(),
+ '_prautoblogger_source_ids' => wp_json_encode( $idea->get_source_ids() ),
+ '_prautoblogger_model_used' => $model,
+ '_prautoblogger_pipeline_mode' => $pipeline_mode,
+ '_prautoblogger_editor_verdict' => $review->get_verdict(),
+ '_prautoblogger_editor_notes' => $review->get_notes(),
+ '_prautoblogger_quality_score' => $review->get_quality_score(),
+ '_prautoblogger_seo_score' => $review->get_seo_score(),
+ '_prautoblogger_generated_at' => gmdate( 'c' ),
+ '_prautoblogger_topic' => $idea->get_topic(),
+ '_prautoblogger_article_type' => $idea->get_article_type(),
+ '_prautoblogger_target_keywords' => wp_json_encode( $idea->get_target_keywords() ),
];
}
@@ -175,11 +175,11 @@ class Autoblogger_Publisher {
* Assign category and tags to a generated post.
*
* @param int $post_id
- * @param Autoblogger_Article_Idea $idea
+ * @param PRAutoBlogger_Article_Idea $idea
*
* @return void
*/
- private function assign_taxonomy_terms( int $post_id, Autoblogger_Article_Idea $idea ): void {
+ private function assign_taxonomy_terms( int $post_id, PRAutoBlogger_Article_Idea $idea ): void {
$type_category_map = [
'guide' => 'Guides',
'solution' => 'Solutions',
@@ -222,7 +222,7 @@ class Autoblogger_Publisher {
*/
private function link_generation_logs( int $post_id, ?string $run_id = null ): void {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_generation_log';
+ $table = $wpdb->prefix . 'prautoblogger_generation_log';
if ( null !== $run_id && '' !== $run_id ) {
// Precise linking via run_id — no risk of cross-article misattribution.
@@ -253,7 +253,7 @@ class Autoblogger_Publisher {
* @return int WordPress user ID.
*/
private function get_default_author_id(): int {
- $author_id = absint( get_option( 'autoblogger_default_author', 0 ) );
+ $author_id = absint( get_option( 'prautoblogger_default_author', 0 ) );
if ( $author_id > 0 ) {
return $author_id;
}
diff --git a/includes/core/class-source-collector.php b/includes/core/class-source-collector.php
index 42151e2..6c7d004 100755
--- a/includes/core/class-source-collector.php
+++ b/includes/core/class-source-collector.php
@@ -8,14 +8,14 @@ declare(strict_types=1);
* collect_data() method, and stores results in the ab_source_data table.
* Handles deduplication via UNIQUE index on (source_type, source_id).
*
- * Triggered by: Autoblogger::run_generation_pipeline() (step 1).
+ * Triggered by: PRAutoBlogger::run_generation_pipeline() (step 1).
* Dependencies: Source provider implementations, WordPress $wpdb.
*
* @see providers/interface-source-provider.php — Interface providers implement.
* @see providers/class-reddit-provider.php — Primary source provider.
* @see ARCHITECTURE.md — Data flow step 1.
*/
-class Autoblogger_Source_Collector {
+class PRAutoBlogger_Source_Collector {
/**
* Collect data from all enabled source providers and store in database.
@@ -25,7 +25,7 @@ class Autoblogger_Source_Collector {
* @return int Total number of new records inserted.
*/
public function collect_from_all_sources(): int {
- $enabled = json_decode( get_option( 'autoblogger_enabled_sources', '["reddit"]' ), true );
+ $enabled = json_decode( get_option( 'prautoblogger_enabled_sources', '["reddit"]' ), true );
if ( ! is_array( $enabled ) ) {
$enabled = [ 'reddit' ];
}
@@ -35,7 +35,7 @@ class Autoblogger_Source_Collector {
foreach ( $enabled as $source_type ) {
$provider = $this->get_provider( $source_type );
if ( null === $provider ) {
- Autoblogger_Logger::instance()->warning( "No provider found for source type: {$source_type}", 'collector' );
+ PRAutoBlogger_Logger::instance()->warning( "No provider found for source type: {$source_type}", 'collector' );
continue;
}
@@ -44,12 +44,12 @@ class Autoblogger_Source_Collector {
$inserted = $this->store_data( $data );
$total_inserted += $inserted;
- Autoblogger_Logger::instance()->info(
+ PRAutoBlogger_Logger::instance()->info(
sprintf( 'Collected %d items from %s (%d new).', count( $data ), $source_type, $inserted ),
'collector'
);
} catch ( \Exception $e ) {
- Autoblogger_Logger::instance()->error( "Collection from {$source_type} FAILED: " . $e->getMessage(), 'collector' );
+ PRAutoBlogger_Logger::instance()->error( "Collection from {$source_type} FAILED: " . $e->getMessage(), 'collector' );
// Continue with other sources — one failure shouldn't stop everything.
}
}
@@ -64,13 +64,13 @@ class Autoblogger_Source_Collector {
*
* Side effects: database inserts.
*
- * @param Autoblogger_Source_Data[] $items
+ * @param PRAutoBlogger_Source_Data[] $items
*
* @return int Number of new records inserted.
*/
private function store_data( array $items ): int {
global $wpdb;
- $table = $wpdb->prefix . 'autoblogger_source_data';
+ $table = $wpdb->prefix . 'prautoblogger_source_data';
$inserted = 0;
foreach ( $items as $item ) {
@@ -110,11 +110,11 @@ class Autoblogger_Source_Collector {
*
* @param string $source_type Source type identifier.
*
- * @return Autoblogger_Source_Provider_Interface|null
+ * @return PRAutoBlogger_Source_Provider_Interface|null
*/
- private function get_provider( string $source_type ): ?Autoblogger_Source_Provider_Interface {
+ private function get_provider( string $source_type ): ?PRAutoBlogger_Source_Provider_Interface {
$providers = [
- 'reddit' => Autoblogger_Reddit_Provider::class,
+ 'reddit' => PRAutoBlogger_Reddit_Provider::class,
// Future providers: 'tiktok', 'instagram', 'youtube'
];
@@ -123,7 +123,7 @@ class Autoblogger_Source_Collector {
*
* @param array $providers Map of source_type => class name.
*/
- $providers = apply_filters( 'autoblogger_filter_source_providers', $providers );
+ $providers = apply_filters( 'prautoblogger_filter_source_providers', $providers );
if ( ! isset( $providers[ $source_type ] ) ) {
return null;
@@ -134,7 +134,7 @@ class Autoblogger_Source_Collector {
// Guard against unloadable provider classes (e.g. stub providers not yet
// autoloadable, or classes removed during development).
if ( ! class_exists( $class ) ) {
- Autoblogger_Logger::instance()->error(
+ PRAutoBlogger_Logger::instance()->error(
sprintf( 'Provider class "%s" for source type "%s" does not exist.', $class, $source_type ),
'collector'
);
@@ -154,7 +154,7 @@ class Autoblogger_Source_Collector {
private function get_config_for_source( string $source_type ): array {
if ( 'reddit' === $source_type ) {
return [
- 'subreddits' => json_decode( get_option( 'autoblogger_target_subreddits', '[]' ), true ) ?: [],
+ 'subreddits' => json_decode( get_option( 'prautoblogger_target_subreddits', '[]' ), true ) ?: [],
'limit' => 25,
'time_filter' => 'day',
'include_comments' => true,
diff --git a/includes/frontend/class-posts-widget.php b/includes/frontend/class-posts-widget.php
new file mode 100644
index 0000000..9c1e6f5
--- /dev/null
+++ b/includes/frontend/class-posts-widget.php
@@ -0,0 +1,256 @@
+ \WP_REST_Server::READABLE,
+ 'callback' => [ $this, 'handle_rest_request' ],
+ 'permission_callback' => function () {
+ /**
+ * Filter whether the posts widget REST endpoint is publicly accessible.
+ *
+ * By default, this is true — the endpoint only serves published posts.
+ * Return false to require authentication (e.g., for private/staging sites).
+ *
+ * @param bool $is_public Whether the endpoint is publicly accessible.
+ */
+ return (bool) apply_filters( 'prautoblogger_rest_posts_public', true );
+ },
+ 'args' => [
+ 'per_page' => [
+ 'type' => 'integer',
+ 'default' => 6,
+ 'minimum' => 1,
+ 'maximum' => self::MAX_POSTS_PER_REQUEST,
+ 'sanitize_callback' => 'absint',
+ ],
+ 'category' => [
+ 'type' => 'string',
+ 'default' => '',
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Render the shortcode output.
+ *
+ * Outputs a mount-point div and enqueues the React script + CSS.
+ * The React component hydrates the div on the client side.
+ *
+ * Shortcode attributes:
+ * count — Number of posts to display (default 6, max 12).
+ * category — Filter by category slug (default empty = all).
+ * title — Widget heading (default "Latest Research & Insights").
+ * subtitle — Widget subheading.
+ *
+ * @param array|string $atts Shortcode attributes.
+ *
+ * @return string HTML mount-point div.
+ */
+ public function render_shortcode( $atts ): string {
+ $atts = shortcode_atts(
+ [
+ 'count' => '6',
+ 'category' => '',
+ 'title' => __( 'Latest Research & Insights', 'prautoblogger' ),
+ 'subtitle' => __( 'Evidence-based articles on peptides, protocols, and emerging research', 'prautoblogger' ),
+ ],
+ $atts,
+ 'prautoblogger_posts'
+ );
+
+ $count = min( absint( $atts['count'] ), self::MAX_POSTS_PER_REQUEST );
+ if ( $count < 1 ) {
+ $count = 6;
+ }
+
+ $this->enqueue_frontend_assets( $count, $atts );
+
+ return '
';
+ }
+
+ /**
+ * Enqueue frontend CSS and JS for the widget.
+ *
+ * Uses wp.element (WordPress-bundled React) as a dependency — no build step needed.
+ * Passes configuration to JS via wp_localize_script.
+ *
+ * Side effects: enqueues styles and scripts.
+ *
+ * @param int $count Number of posts to fetch.
+ * @param array $atts Shortcode attributes.
+ *
+ * @return void
+ */
+ private function enqueue_frontend_assets( int $count, array $atts ): void {
+ $version = defined( 'PRAUTOBLOGGER_VERSION' ) ? PRAUTOBLOGGER_VERSION : '0.1.0';
+
+ wp_enqueue_style(
+ 'prautoblogger-posts-widget',
+ PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/posts-widget.css',
+ [],
+ $version
+ );
+
+ wp_enqueue_script(
+ 'prautoblogger-posts-widget',
+ PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/posts-widget.js',
+ [ 'wp-element' ], // WordPress-bundled React.
+ $version,
+ true // Load in footer for performance.
+ );
+
+ wp_localize_script(
+ 'prautoblogger-posts-widget',
+ 'prabPostsWidget',
+ [
+ 'restUrl' => esc_url_raw( rest_url( self::REST_NAMESPACE . self::REST_ROUTE ) ),
+ 'count' => $count,
+ 'category' => sanitize_text_field( $atts['category'] ),
+ 'title' => sanitize_text_field( $atts['title'] ),
+ 'subtitle' => sanitize_text_field( $atts['subtitle'] ),
+ 'archiveUrl' => esc_url( get_post_type_archive_link( 'post' ) ?: '' ),
+ ]
+ );
+ }
+
+ /**
+ * Handle the REST API request for posts.
+ *
+ * Returns a slim JSON array of published posts generated by PRAutoBlogger.
+ * Each post includes only the fields the frontend needs — no unnecessary data.
+ *
+ * @param \WP_REST_Request $request The incoming REST request.
+ *
+ * @return \WP_REST_Response JSON response with post data.
+ */
+ public function handle_rest_request( \WP_REST_Request $request ): \WP_REST_Response {
+ $per_page = min( absint( $request->get_param( 'per_page' ) ), self::MAX_POSTS_PER_REQUEST );
+ if ( $per_page < 1 ) {
+ $per_page = 6;
+ }
+ $category = sanitize_text_field( $request->get_param( 'category' ) ?? '' );
+
+ $query_args = [
+ 'post_type' => 'post',
+ 'post_status' => 'publish',
+ 'posts_per_page' => $per_page,
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ 'meta_query' => [
+ [
+ 'key' => '_prautoblogger_generated',
+ 'value' => '1',
+ ],
+ ],
+ ];
+
+ // Optional category filter.
+ if ( ! empty( $category ) ) {
+ $query_args['category_name'] = $category;
+ }
+
+ $query = new \WP_Query( $query_args );
+ $posts = [];
+
+ foreach ( $query->posts as $post ) {
+ $posts[] = $this->format_post_for_response( $post );
+ }
+
+ $response = new \WP_REST_Response( $posts, 200 );
+
+ // Cache for 5 minutes — posts don't change frequently.
+ $response->header( 'Cache-Control', 'public, max-age=300' );
+
+ return $response;
+ }
+
+ /**
+ * Format a single post for the REST API response.
+ *
+ * Returns only the fields the React component needs to render a card.
+ *
+ * @param \WP_Post $post WordPress post object.
+ *
+ * @return array Formatted post data.
+ */
+ private function format_post_for_response( \WP_Post $post ): array {
+ $categories = get_the_category( $post->ID );
+ $category_name = ! empty( $categories ) ? $categories[0]->name : '';
+ $category_slug = ! empty( $categories ) ? $categories[0]->slug : '';
+ $featured_image = get_the_post_thumbnail_url( $post->ID, 'medium_large' );
+ $word_count = str_word_count( wp_strip_all_tags( $post->post_content ) );
+
+ return [
+ 'id' => $post->ID,
+ 'title' => get_the_title( $post ),
+ 'excerpt' => get_the_excerpt( $post ),
+ 'url' => get_permalink( $post ),
+ 'date' => get_the_date( 'c', $post ),
+ 'category_name' => $category_name,
+ 'category_slug' => $category_slug,
+ 'featured_image' => $featured_image ?: '',
+ 'word_count' => $word_count,
+ ];
+ }
+}
diff --git a/includes/models/class-analysis-result.php b/includes/models/class-analysis-result.php
index 0c05eb5..eab87c2 100755
--- a/includes/models/class-analysis-result.php
+++ b/includes/models/class-analysis-result.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
* @see core/class-content-analyzer.php — Creates these from source data + LLM analysis.
* @see core/class-idea-scorer.php — Ranks these into article ideas.
*/
-class Autoblogger_Analysis_Result {
+class PRAutoBlogger_Analysis_Result {
private int $id;
private string $analysis_type;
diff --git a/includes/models/class-article-idea.php b/includes/models/class-article-idea.php
index 795416b..d6f38cb 100755
--- a/includes/models/class-article-idea.php
+++ b/includes/models/class-article-idea.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
* @see core/class-idea-scorer.php — Creates and ranks these.
* @see core/class-content-generator.php — Uses these as generation input.
*/
-class Autoblogger_Article_Idea {
+class PRAutoBlogger_Article_Idea {
private string $topic;
private string $article_type;
diff --git a/includes/models/class-content-request.php b/includes/models/class-content-request.php
index c00e77c..6f9ce71 100755
--- a/includes/models/class-content-request.php
+++ b/includes/models/class-content-request.php
@@ -12,9 +12,9 @@ declare(strict_types=1);
*
* @see core/class-content-generator.php — Creates and consumes these.
*/
-class Autoblogger_Content_Request {
+class PRAutoBlogger_Content_Request {
- private Autoblogger_Article_Idea $idea;
+ private PRAutoBlogger_Article_Idea $idea;
private string $pipeline_mode;
private string $tone;
private int $min_word_count;
@@ -23,7 +23,7 @@ class Autoblogger_Content_Request {
private array $topic_exclusions;
public function __construct(
- Autoblogger_Article_Idea $idea,
+ PRAutoBlogger_Article_Idea $idea,
string $pipeline_mode,
string $tone,
int $min_word_count,
@@ -40,7 +40,7 @@ class Autoblogger_Content_Request {
$this->topic_exclusions = $topic_exclusions;
}
- public function get_idea(): Autoblogger_Article_Idea {
+ public function get_idea(): PRAutoBlogger_Article_Idea {
return $this->idea;
}
diff --git a/includes/models/class-content-score.php b/includes/models/class-content-score.php
index 387e574..6260dee 100755
--- a/includes/models/class-content-score.php
+++ b/includes/models/class-content-score.php
@@ -10,7 +10,7 @@ declare(strict_types=1);
* @see core/class-metrics-collector.php — Creates these from WP + GA4 data.
* @see core/class-content-analyzer.php — Reads past scores to refine analysis.
*/
-class Autoblogger_Content_Score {
+class PRAutoBlogger_Content_Score {
private int $id;
private int $post_id;
diff --git a/includes/models/class-editorial-review.php b/includes/models/class-editorial-review.php
index b3929da..7c433d0 100755
--- a/includes/models/class-editorial-review.php
+++ b/includes/models/class-editorial-review.php
@@ -10,7 +10,7 @@ declare(strict_types=1);
* @see core/class-chief-editor.php — Creates these after reviewing content.
* @see core/class-publisher.php — Uses verdict to decide publish vs. draft.
*/
-class Autoblogger_Editorial_Review {
+class PRAutoBlogger_Editorial_Review {
private string $verdict;
private string $notes;
diff --git a/includes/models/class-generation-log.php b/includes/models/class-generation-log.php
index 83e5338..d78b68a 100755
--- a/includes/models/class-generation-log.php
+++ b/includes/models/class-generation-log.php
@@ -7,10 +7,10 @@ declare(strict_types=1);
* Triggered by: Cost_Tracker creates these; Metrics_Page displays them.
* Dependencies: None — pure data object.
*
- * @see core/class-cost-tracker.php — Writes these to ab_generation_log table.
+ * @see core/class-cost-tracker.php — Writes these to prab_generation_log table.
* @see admin/class-metrics-page.php — Reads and displays these.
*/
-class Autoblogger_Generation_Log {
+class PRAutoBlogger_Generation_Log {
private int $id;
private ?int $post_id;
diff --git a/includes/models/class-source-data.php b/includes/models/class-source-data.php
index 654a93d..61fa407 100755
--- a/includes/models/class-source-data.php
+++ b/includes/models/class-source-data.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
* @see providers/interface-source-provider.php — Providers return arrays of these.
* @see core/class-content-analyzer.php — Consumes these for pattern detection.
*/
-class Autoblogger_Source_Data {
+class PRAutoBlogger_Source_Data {
private int $id;
private string $source_type;
diff --git a/includes/providers/class-instagram-provider.php b/includes/providers/class-instagram-provider.php
index 0b3f83b..f00b5c5 100755
--- a/includes/providers/class-instagram-provider.php
+++ b/includes/providers/class-instagram-provider.php
@@ -12,16 +12,16 @@ declare(strict_types=1);
*
* @see interface-source-provider.php — Interface this class implements.
*/
-class Autoblogger_Instagram_Provider implements Autoblogger_Source_Provider_Interface {
+class PRAutoBlogger_Instagram_Provider implements PRAutoBlogger_Source_Provider_Interface {
/**
* @param array $config
- * @return Autoblogger_Source_Data[]
+ * @return PRAutoBlogger_Source_Data[]
* @throws \RuntimeException Always — not yet implemented.
*/
public function collect_data( array $config ): array {
throw new \RuntimeException(
- __( 'Instagram source provider is not yet implemented. Remove "instagram" from enabled sources.', 'autoblogger' )
+ __( 'Instagram source provider is not yet implemented. Remove "instagram" from enabled sources.', 'prautoblogger' )
);
}
diff --git a/includes/providers/class-openrouter-pricing.php b/includes/providers/class-openrouter-pricing.php
index aeddc35..5431d5b 100755
--- a/includes/providers/class-openrouter-pricing.php
+++ b/includes/providers/class-openrouter-pricing.php
@@ -7,13 +7,13 @@ declare(strict_types=1);
* Manages pricing tables, model lists, and cost estimation. Decoupled from
* the request logic for easy updates and testing.
*
- * Triggered by: Autoblogger_OpenRouter_Provider calls pricing methods.
+ * Triggered by: PRAutoBlogger_OpenRouter_Provider calls pricing methods.
* Dependencies: None — pure data + calculation, no external calls by default.
*
* @see providers/class-openrouter-provider.php — Instantiates this class.
* @see ARCHITECTURE.md — Model selection flow.
*/
-class Autoblogger_OpenRouter_Pricing {
+class PRAutoBlogger_OpenRouter_Pricing {
/**
* Known model pricing (per million tokens, in USD).
@@ -41,7 +41,7 @@ class Autoblogger_OpenRouter_Pricing {
* @return array
*/
public function get_available_models(): array {
- $cached = get_transient( 'autoblogger_openrouter_models' );
+ $cached = get_transient( 'prautoblogger_openrouter_models' );
if ( false !== $cached && is_array( $cached ) ) {
return $cached;
}
@@ -62,7 +62,7 @@ class Autoblogger_OpenRouter_Pricing {
);
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
- Autoblogger_Logger::instance()->warning( 'Failed to fetch OpenRouter models list.', 'openrouter' );
+ PRAutoBlogger_Logger::instance()->warning( 'Failed to fetch OpenRouter models list.', 'openrouter' );
return [];
}
@@ -83,7 +83,7 @@ class Autoblogger_OpenRouter_Pricing {
}
}
- set_transient( 'autoblogger_openrouter_models', $models, DAY_IN_SECONDS );
+ set_transient( 'prautoblogger_openrouter_models', $models, DAY_IN_SECONDS );
return $models;
}
@@ -114,7 +114,7 @@ class Autoblogger_OpenRouter_Pricing {
if ( null === $pricing ) {
// Unknown model — use a conservative estimate.
- Autoblogger_Logger::instance()->warning( "Unknown model pricing for '{$model}'. Using conservative estimate.", 'openrouter' );
+ PRAutoBlogger_Logger::instance()->warning( "Unknown model pricing for '{$model}'. Using conservative estimate.", 'openrouter' );
$pricing = [ 'prompt' => 10.0, 'completion' => 30.0 ];
}
@@ -128,10 +128,10 @@ class Autoblogger_OpenRouter_Pricing {
* @return string Decrypted API key, or empty string if not set.
*/
private function get_api_key(): string {
- $encrypted = get_option( 'autoblogger_openrouter_api_key', '' );
+ $encrypted = get_option( 'prautoblogger_openrouter_api_key', '' );
if ( '' === $encrypted ) {
return '';
}
- return Autoblogger_Encryption::decrypt( $encrypted );
+ return PRAutoBlogger_Encryption::decrypt( $encrypted );
}
}
diff --git a/includes/providers/class-openrouter-provider.php b/includes/providers/class-openrouter-provider.php
index 8ea4104..53cf240 100755
--- a/includes/providers/class-openrouter-provider.php
+++ b/includes/providers/class-openrouter-provider.php
@@ -9,14 +9,14 @@ declare(strict_types=1);
* separate provider integrations.
*
* Triggered by: Content_Analyzer, Content_Generator, Chief_Editor, Metrics_Collector.
- * Dependencies: Autoblogger_Encryption (for API key decryption), wp_remote_post(), Autoblogger_OpenRouter_Pricing.
+ * Dependencies: PRAutoBlogger_Encryption (for API key decryption), wp_remote_post(), PRAutoBlogger_OpenRouter_Pricing.
*
* @see interface-llm-provider.php — Interface this class implements.
* @see class-openrouter-pricing.php — Pricing and model list lookups.
* @see class-cost-tracker.php — Called after every request to log token usage.
* @see ARCHITECTURE.md — Data flow diagram showing where this fits.
*/
-class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interface {
+class PRAutoBlogger_OpenRouter_Provider implements PRAutoBlogger_LLM_Provider_Interface {
private const API_BASE_URL = 'https://openrouter.ai/api/v1';
@@ -51,7 +51,7 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
$api_key = $this->get_api_key();
if ( '' === $api_key ) {
throw new \RuntimeException(
- __( 'OpenRouter API key is not configured. Go to AutoBlogger → Settings.', 'autoblogger' )
+ __( 'OpenRouter API key is not configured. Go to PRAutoBlogger → Settings.', 'prautoblogger' )
);
}
@@ -72,16 +72,16 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
$last_error = '';
- for ( $attempt = 1; $attempt <= AUTOBLOGGER_MAX_RETRIES; $attempt++ ) {
+ for ( $attempt = 1; $attempt <= PRAUTOBLOGGER_MAX_RETRIES; $attempt++ ) {
$response = wp_remote_post(
self::API_BASE_URL . '/chat/completions',
[
- 'timeout' => AUTOBLOGGER_API_TIMEOUT_SECONDS,
+ 'timeout' => PRAUTOBLOGGER_API_TIMEOUT_SECONDS,
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
'HTTP-Referer' => home_url(),
- 'X-Title' => 'AutoBlogger WordPress Plugin',
+ 'X-Title' => 'PRAutoBlogger WordPress Plugin',
],
'body' => wp_json_encode( $body ),
]
@@ -89,13 +89,13 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
if ( is_wp_error( $response ) ) {
$last_error = $response->get_error_message();
- Autoblogger_Logger::instance()->warning(
- sprintf( 'OpenRouter request failed (attempt %d/%d): %s', $attempt, AUTOBLOGGER_MAX_RETRIES, $last_error ),
+ PRAutoBlogger_Logger::instance()->warning(
+ sprintf( 'OpenRouter request failed (attempt %d/%d): %s', $attempt, PRAUTOBLOGGER_MAX_RETRIES, $last_error ),
'openrouter'
);
- if ( $attempt < AUTOBLOGGER_MAX_RETRIES ) {
- $delay = AUTOBLOGGER_RETRY_BASE_DELAY_SECONDS * pow( 2, $attempt - 1 );
+ if ( $attempt < PRAUTOBLOGGER_MAX_RETRIES ) {
+ $delay = PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS * pow( 2, $attempt - 1 );
sleep( (int) $delay );
}
continue;
@@ -108,17 +108,17 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
// Rate limited or server error — retry.
if ( $status_code >= 429 || $status_code >= 500 ) {
$last_error = sprintf( 'HTTP %d: %s', $status_code, $body_raw );
- Autoblogger_Logger::instance()->warning(
- sprintf( 'OpenRouter HTTP %d (attempt %d/%d): %s', $status_code, $attempt, AUTOBLOGGER_MAX_RETRIES, substr( $body_raw, 0, 500 ) ),
+ PRAutoBlogger_Logger::instance()->warning(
+ sprintf( 'OpenRouter HTTP %d (attempt %d/%d): %s', $status_code, $attempt, PRAUTOBLOGGER_MAX_RETRIES, substr( $body_raw, 0, 500 ) ),
'openrouter'
);
- if ( $attempt < AUTOBLOGGER_MAX_RETRIES ) {
+ if ( $attempt < PRAUTOBLOGGER_MAX_RETRIES ) {
// Respect Retry-After header if present.
$retry_after = wp_remote_retrieve_header( $response, 'retry-after' );
$delay = $retry_after
? min( (int) $retry_after, 60 )
- : AUTOBLOGGER_RETRY_BASE_DELAY_SECONDS * pow( 2, $attempt - 1 );
+ : PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS * pow( 2, $attempt - 1 );
sleep( (int) $delay );
}
continue;
@@ -132,7 +132,7 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
throw new \RuntimeException(
sprintf(
/* translators: %d: HTTP status code, %s: error message */
- __( 'OpenRouter API error (HTTP %1$d): %2$s', 'autoblogger' ),
+ __( 'OpenRouter API error (HTTP %1$d): %2$s', 'prautoblogger' ),
$status_code,
$error_msg
)
@@ -142,7 +142,7 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
// Success — parse response.
if ( ! isset( $data['choices'][0]['message']['content'] ) ) {
throw new \RuntimeException(
- __( 'OpenRouter returned unexpected response format.', 'autoblogger' )
+ __( 'OpenRouter returned unexpected response format.', 'prautoblogger' )
);
}
@@ -162,8 +162,8 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
throw new \RuntimeException(
sprintf(
/* translators: %d: max retries, %s: last error message */
- __( 'OpenRouter API failed after %1$d attempts. Last error: %2$s', 'autoblogger' ),
- AUTOBLOGGER_MAX_RETRIES,
+ __( 'OpenRouter API failed after %1$d attempts. Last error: %2$s', 'prautoblogger' ),
+ PRAUTOBLOGGER_MAX_RETRIES,
$last_error
)
);
@@ -179,7 +179,7 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
* @return array
*/
public function get_available_models(): array {
- $pricing = new Autoblogger_OpenRouter_Pricing();
+ $pricing = new PRAutoBlogger_OpenRouter_Pricing();
return $pricing->get_available_models();
}
@@ -195,7 +195,7 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
* @return float Estimated cost in USD.
*/
public function estimate_cost( string $model, int $prompt_tokens, int $completion_tokens ): float {
- $pricing = new Autoblogger_OpenRouter_Pricing();
+ $pricing = new PRAutoBlogger_OpenRouter_Pricing();
return $pricing->estimate_cost( $model, $prompt_tokens, $completion_tokens );
}
@@ -244,10 +244,10 @@ class Autoblogger_OpenRouter_Provider implements Autoblogger_LLM_Provider_Interf
* @return string Decrypted API key, or empty string if not set.
*/
private function get_api_key(): string {
- $encrypted = get_option( 'autoblogger_openrouter_api_key', '' );
+ $encrypted = get_option( 'prautoblogger_openrouter_api_key', '' );
if ( '' === $encrypted ) {
return '';
}
- return Autoblogger_Encryption::decrypt( $encrypted );
+ return PRAutoBlogger_Encryption::decrypt( $encrypted );
}
}
diff --git a/includes/providers/class-reddit-api-client.php b/includes/providers/class-reddit-api-client.php
index 557cfc8..7d6008a 100755
--- a/includes/providers/class-reddit-api-client.php
+++ b/includes/providers/class-reddit-api-client.php
@@ -7,17 +7,17 @@ declare(strict_types=1);
* Encapsulates OAuth2 token management, rate limit tracking, and HTTP methods
* for the Reddit API. Decoupled from collection logic for testability.
*
- * Triggered by: Autoblogger_Reddit_Provider calls fetch methods here.
- * Dependencies: Autoblogger_Encryption (for client secret), wp_remote_post/get().
+ * Triggered by: PRAutoBlogger_Reddit_Provider calls fetch methods here.
+ * Dependencies: PRAutoBlogger_Encryption (for client secret), wp_remote_post/get().
*
* @see providers/class-reddit-provider.php — Instantiates this class.
* @see ARCHITECTURE.md — External API integrations table.
*/
-class Autoblogger_Reddit_API_Client {
+class PRAutoBlogger_Reddit_API_Client {
private const AUTH_URL = 'https://www.reddit.com/api/v1/access_token';
private const API_BASE = 'https://oauth.reddit.com';
- private const USER_AGENT = 'AutoBlogger/0.1.0 (WordPress Plugin)';
+ private const USER_AGENT = 'PRAutoBlogger/0.1.0 (WordPress Plugin)';
/**
* Get an OAuth2 access token, using cached transient if available.
@@ -32,15 +32,15 @@ class Autoblogger_Reddit_API_Client {
*/
public function get_access_token( bool $force_refresh = false ): string {
if ( ! $force_refresh ) {
- $cached = get_transient( 'autoblogger_reddit_token' );
+ $cached = get_transient( 'prautoblogger_reddit_token' );
if ( false !== $cached && is_string( $cached ) ) {
return $cached;
}
}
- $client_id = get_option( 'autoblogger_reddit_client_id', '' );
- $client_secret = Autoblogger_Encryption::decrypt(
- get_option( 'autoblogger_reddit_client_secret', '' )
+ $client_id = get_option( 'prautoblogger_reddit_client_id', '' );
+ $client_secret = PRAutoBlogger_Encryption::decrypt(
+ get_option( 'prautoblogger_reddit_client_secret', '' )
);
if ( '' === $client_id || '' === $client_secret ) {
@@ -63,25 +63,25 @@ class Autoblogger_Reddit_API_Client {
);
if ( is_wp_error( $response ) ) {
- Autoblogger_Logger::instance()->error( 'Reddit OAuth failed: ' . $response->get_error_message(), 'reddit' );
+ PRAutoBlogger_Logger::instance()->error( 'Reddit OAuth failed: ' . $response->get_error_message(), 'reddit' );
return '';
}
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
- Autoblogger_Logger::instance()->error( 'Reddit OAuth HTTP ' . wp_remote_retrieve_response_code( $response ), 'reddit' );
+ PRAutoBlogger_Logger::instance()->error( 'Reddit OAuth HTTP ' . wp_remote_retrieve_response_code( $response ), 'reddit' );
return '';
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! isset( $data['access_token'] ) ) {
- Autoblogger_Logger::instance()->error( 'Reddit OAuth response missing access_token.', 'reddit' );
+ PRAutoBlogger_Logger::instance()->error( 'Reddit OAuth response missing access_token.', 'reddit' );
return '';
}
$token = $data['access_token'];
$expires = isset( $data['expires_in'] ) ? (int) $data['expires_in'] - 60 : 3540;
- set_transient( 'autoblogger_reddit_token', $token, $expires );
+ set_transient( 'prautoblogger_reddit_token', $token, $expires );
return $token;
}
@@ -179,7 +179,7 @@ class Autoblogger_Reddit_API_Client {
);
if ( is_wp_error( $response ) ) {
- Autoblogger_Logger::instance()->error( 'Reddit API GET failed: ' . $response->get_error_message(), 'reddit' );
+ PRAutoBlogger_Logger::instance()->error( 'Reddit API GET failed: ' . $response->get_error_message(), 'reddit' );
return null;
}
@@ -189,7 +189,7 @@ class Autoblogger_Reddit_API_Client {
$rate_reset = wp_remote_retrieve_header( $response, 'x-ratelimit-reset' );
if ( '' !== $rate_remaining ) {
- set_transient( 'autoblogger_reddit_rate_limit', [
+ set_transient( 'prautoblogger_reddit_rate_limit', [
'remaining' => (int) $rate_remaining,
'limit' => (int) $rate_limit + (int) $rate_remaining,
'resets_at' => gmdate( 'Y-m-d H:i:s', time() + (int) $rate_reset ),
@@ -198,7 +198,7 @@ class Autoblogger_Reddit_API_Client {
$status_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $status_code ) {
- Autoblogger_Logger::instance()->error( sprintf( 'Reddit API HTTP %d: %s', $status_code, substr( wp_remote_retrieve_body( $response ), 0, 300 ) ), 'reddit' );
+ PRAutoBlogger_Logger::instance()->error( sprintf( 'Reddit API HTTP %d: %s', $status_code, substr( wp_remote_retrieve_body( $response ), 0, 300 ) ), 'reddit' );
return null;
}
diff --git a/includes/providers/class-reddit-provider.php b/includes/providers/class-reddit-provider.php
index f335ffd..b6b86aa 100755
--- a/includes/providers/class-reddit-provider.php
+++ b/includes/providers/class-reddit-provider.php
@@ -9,14 +9,14 @@ declare(strict_types=1);
* storing them in the ab_source_data table.
*
* Triggered by: Source_Collector calls collect_data() on each enabled provider.
- * Dependencies: Autoblogger_Reddit_API_Client (for HTTP calls and auth).
+ * Dependencies: PRAutoBlogger_Reddit_API_Client (for HTTP calls and auth).
*
* @see interface-source-provider.php — Interface this class implements.
* @see providers/class-reddit-api-client.php — HTTP client for Reddit API.
* @see core/class-source-collector.php — Orchestrates collection from all providers.
* @see ARCHITECTURE.md — External API integrations table.
*/
-class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interface {
+class PRAutoBlogger_Reddit_Provider implements PRAutoBlogger_Source_Provider_Interface {
/**
* Collect posts and comments from configured subreddits.
@@ -34,16 +34,16 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
* comments_per_post?: int,
* } $config Collection configuration.
*
- * @return Autoblogger_Source_Data[]
+ * @return PRAutoBlogger_Source_Data[]
*
* @throws \RuntimeException On authentication failure or API error.
*/
public function collect_data( array $config ): array {
- $api_client = new Autoblogger_Reddit_API_Client();
+ $api_client = new PRAutoBlogger_Reddit_API_Client();
$token = $api_client->get_access_token();
if ( '' === $token ) {
throw new \RuntimeException(
- __( 'Reddit authentication failed. Check client ID and secret in settings.', 'autoblogger' )
+ __( 'Reddit authentication failed. Check client ID and secret in settings.', 'prautoblogger' )
);
}
@@ -62,7 +62,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
$posts = $api_client->fetch_posts( $token, $subreddit, $limit, $time_filter );
foreach ( $posts as $post ) {
- $collected[] = new Autoblogger_Source_Data( [
+ $collected[] = new PRAutoBlogger_Source_Data( [
'source_type' => 'reddit',
'source_id' => 't3_' . $post['id'],
'subreddit' => $subreddit,
@@ -92,7 +92,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
);
foreach ( $comments as $comment ) {
- $collected[] = new Autoblogger_Source_Data( [
+ $collected[] = new PRAutoBlogger_Source_Data( [
'source_type' => 'reddit',
'source_id' => 't1_' . $comment['id'],
'subreddit' => $subreddit,
@@ -115,7 +115,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
}
} catch ( \Exception $e ) {
// Log but continue with other subreddits.
- Autoblogger_Logger::instance()->error(
+ PRAutoBlogger_Logger::instance()->error(
sprintf( 'Reddit: Failed to collect from r/%s: %s', $subreddit, $e->getMessage() ),
'reddit'
);
@@ -140,7 +140,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
* @return bool
*/
public function validate_credentials(): bool {
- $api_client = new Autoblogger_Reddit_API_Client();
+ $api_client = new PRAutoBlogger_Reddit_API_Client();
$token = $api_client->get_access_token( true );
return '' !== $token;
}
@@ -153,7 +153,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
public function get_rate_limit_status(): array {
// Reddit rate limits are tracked per-response via headers.
// We store the last seen values in a transient.
- $status = get_transient( 'autoblogger_reddit_rate_limit' );
+ $status = get_transient( 'prautoblogger_reddit_rate_limit' );
if ( is_array( $status ) ) {
return $status;
}
@@ -171,7 +171,7 @@ class Autoblogger_Reddit_Provider implements Autoblogger_Source_Provider_Interfa
* @return string[]
*/
private function get_configured_subreddits(): array {
- $json = get_option( 'autoblogger_target_subreddits', '[]' );
+ $json = get_option( 'prautoblogger_target_subreddits', '[]' );
$list = json_decode( $json, true );
return is_array( $list ) ? $list : [];
}
diff --git a/includes/providers/class-tiktok-provider.php b/includes/providers/class-tiktok-provider.php
index 8d7367b..90fa786 100755
--- a/includes/providers/class-tiktok-provider.php
+++ b/includes/providers/class-tiktok-provider.php
@@ -13,16 +13,16 @@ declare(strict_types=1);
* @see interface-source-provider.php — Interface this class implements.
* @see ARCHITECTURE.md — Planned source providers.
*/
-class Autoblogger_Tiktok_Provider implements Autoblogger_Source_Provider_Interface {
+class PRAutoBlogger_Tiktok_Provider implements PRAutoBlogger_Source_Provider_Interface {
/**
* @param array $config
- * @return Autoblogger_Source_Data[]
+ * @return PRAutoBlogger_Source_Data[]
* @throws \RuntimeException Always — not yet implemented.
*/
public function collect_data( array $config ): array {
throw new \RuntimeException(
- __( 'TikTok source provider is not yet implemented. Remove "tiktok" from enabled sources.', 'autoblogger' )
+ __( 'TikTok source provider is not yet implemented. Remove "tiktok" from enabled sources.', 'prautoblogger' )
);
}
diff --git a/includes/providers/class-youtube-provider.php b/includes/providers/class-youtube-provider.php
index c57948d..42301d2 100755
--- a/includes/providers/class-youtube-provider.php
+++ b/includes/providers/class-youtube-provider.php
@@ -12,16 +12,16 @@ declare(strict_types=1);
*
* @see interface-source-provider.php — Interface this class implements.
*/
-class Autoblogger_Youtube_Provider implements Autoblogger_Source_Provider_Interface {
+class PRAutoBlogger_Youtube_Provider implements PRAutoBlogger_Source_Provider_Interface {
/**
* @param array $config
- * @return Autoblogger_Source_Data[]
+ * @return PRAutoBlogger_Source_Data[]
* @throws \RuntimeException Always — not yet implemented.
*/
public function collect_data( array $config ): array {
throw new \RuntimeException(
- __( 'YouTube source provider is not yet implemented. Remove "youtube" from enabled sources.', 'autoblogger' )
+ __( 'YouTube source provider is not yet implemented. Remove "youtube" from enabled sources.', 'prautoblogger' )
);
}
diff --git a/includes/providers/interface-llm-provider.php b/includes/providers/interface-llm-provider.php
index 998e127..c575bc9 100755
--- a/includes/providers/interface-llm-provider.php
+++ b/includes/providers/interface-llm-provider.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
* @see CONVENTIONS.md — "How To: Add a New LLM Provider".
* @see ARCHITECTURE.md — External API integrations table.
*/
-interface Autoblogger_LLM_Provider_Interface {
+interface PRAutoBlogger_LLM_Provider_Interface {
/**
* Send a chat completion request to the LLM.
diff --git a/includes/providers/interface-source-provider.php b/includes/providers/interface-source-provider.php
index e4eb2bb..4c2bf14 100755
--- a/includes/providers/interface-source-provider.php
+++ b/includes/providers/interface-source-provider.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
* @see core/class-source-collector.php — Orchestrates collection from all providers.
* @see CONVENTIONS.md — "How To: Add a New Source Provider".
*/
-interface Autoblogger_Source_Provider_Interface {
+interface PRAutoBlogger_Source_Provider_Interface {
/**
* Collect posts/comments from this social platform.
@@ -23,7 +23,7 @@ interface Autoblogger_Source_Provider_Interface {
* keywords?: string[],
* } $config Source-specific configuration.
*
- * @return Autoblogger_Source_Data[] Array of collected data objects.
+ * @return PRAutoBlogger_Source_Data[] Array of collected data objects.
*
* @throws \RuntimeException On API error after retries exhausted.
*/
diff --git a/autoblogger.php b/prautoblogger.php
old mode 100755
new mode 100644
similarity index 55%
rename from autoblogger.php
rename to prautoblogger.php
index 47146cb..5bb9c7a
--- a/autoblogger.php
+++ b/prautoblogger.php
@@ -2,11 +2,11 @@
declare(strict_types=1);
/**
- * AutoBlogger — Automated Content Research & Publishing
+ * PRAutoBlogger — Automated Content Research & Publishing
*
* @wordpress-plugin
- * Plugin Name: AutoBlogger
- * Plugin URI: https://peptiderepo.com/autoblogger
+ * 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.1.0
* Requires at least: 6.0
@@ -15,7 +15,7 @@ declare(strict_types=1);
* Author URI: https://peptiderepo.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
- * Text Domain: autoblogger
+ * Text Domain: prautoblogger
* Domain Path: /languages
*
* @see ARCHITECTURE.md — Full data flow and file tree
@@ -35,23 +35,23 @@ if ( ! defined( 'ABSPATH' ) ) {
| and limits without magic strings.
*/
-define( 'AUTOBLOGGER_VERSION', '0.1.0' );
-define( 'AUTOBLOGGER_DB_VERSION', '1.1.0' );
-define( 'AUTOBLOGGER_PLUGIN_FILE', __FILE__ );
-define( 'AUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
-define( 'AUTOBLOGGER_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
-define( 'AUTOBLOGGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
+define( 'PRAUTOBLOGGER_VERSION', '0.1.0' );
+define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
+define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
+define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+define( 'PRAUTOBLOGGER_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
+define( 'PRAUTOBLOGGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
// API and retry limits — named constants instead of magic numbers.
-define( 'AUTOBLOGGER_MAX_RETRIES', 3 );
-define( 'AUTOBLOGGER_RETRY_BASE_DELAY_SECONDS', 2 );
-define( 'AUTOBLOGGER_API_TIMEOUT_SECONDS', 120 );
-define( 'AUTOBLOGGER_CACHE_TTL_SECONDS', 3600 );
+define( 'PRAUTOBLOGGER_MAX_RETRIES', 3 );
+define( 'PRAUTOBLOGGER_RETRY_BASE_DELAY_SECONDS', 2 );
+define( 'PRAUTOBLOGGER_API_TIMEOUT_SECONDS', 120 );
+define( 'PRAUTOBLOGGER_CACHE_TTL_SECONDS', 3600 );
// Default models for OpenRouter (user can override in settings).
-define( 'AUTOBLOGGER_DEFAULT_ANALYSIS_MODEL', 'anthropic/claude-3.5-haiku' );
-define( 'AUTOBLOGGER_DEFAULT_WRITING_MODEL', 'anthropic/claude-sonnet-4' );
-define( 'AUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'anthropic/claude-sonnet-4' );
+define( 'PRAUTOBLOGGER_DEFAULT_ANALYSIS_MODEL', 'anthropic/claude-3.5-haiku' );
+define( 'PRAUTOBLOGGER_DEFAULT_WRITING_MODEL', 'anthropic/claude-sonnet-4' );
+define( 'PRAUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'anthropic/claude-sonnet-4' );
/*
|--------------------------------------------------------------------------
@@ -59,8 +59,13 @@ define( 'AUTOBLOGGER_DEFAULT_EDITOR_MODEL', 'anthropic/claude-sonnet-4' );
|--------------------------------------------------------------------------
*/
-require_once AUTOBLOGGER_PLUGIN_DIR . 'includes/class-autoloader.php';
-Autoblogger_Autoloader::register();
+require_once PRAUTOBLOGGER_PLUGIN_DIR . 'includes/class-autoloader.php';
+PRAutoBlogger_Autoloader::register();
+
+// The main orchestrator class is loaded explicitly because its CamelCase name
+// (PRAutoBlogger) doesn't map cleanly to a filename via the autoloader's
+// kebab-case convention (it would look for class-pr-auto-blogger.php).
+require_once PRAUTOBLOGGER_PLUGIN_DIR . 'includes/class-prautoblogger.php';
/*
|--------------------------------------------------------------------------
@@ -68,8 +73,8 @@ Autoblogger_Autoloader::register();
|--------------------------------------------------------------------------
*/
-register_activation_hook( __FILE__, [ 'Autoblogger_Activator', 'activate' ] );
-register_deactivation_hook( __FILE__, [ 'Autoblogger_Deactivator', 'deactivate' ] );
+register_activation_hook( __FILE__, [ 'PRAutoBlogger_Activator', 'activate' ] );
+register_deactivation_hook( __FILE__, [ 'PRAutoBlogger_Deactivator', 'deactivate' ] );
/*
|--------------------------------------------------------------------------
@@ -82,17 +87,17 @@ register_deactivation_hook( __FILE__, [ 'Autoblogger_Deactivator', 'deactivate'
/**
* Returns the singleton instance of the main plugin class.
*
- * @return Autoblogger
+ * @return PRAutoBlogger
*/
-function autoblogger(): Autoblogger {
+function prautoblogger(): PRAutoBlogger {
static $instance = null;
if ( null === $instance ) {
- $instance = new Autoblogger();
+ $instance = new PRAutoBlogger();
}
return $instance;
}
// Initialize on plugins_loaded so all dependencies are available.
add_action( 'plugins_loaded', static function (): void {
- autoblogger()->run();
+ prautoblogger()->run();
} );
diff --git a/readme.txt b/readme.txt
index 218592b..60d4bd8 100755
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-=== AutoBlogger ===
+=== PRAutoBlogger ===
Contributors: peptiderepo
Tags: ai, content generation, blogging, seo, automation
Requires at least: 6.0
@@ -12,7 +12,7 @@ Automatically researches trending topics from social media and generates SEO-fri
== Description ==
-AutoBlogger monitors social media platforms (starting with Reddit) for recurring questions, complaints, and product comparisons in your niche. It uses AI to analyze these patterns, generate well-structured blog articles, run them through an editorial review agent, and publish them on a configurable daily schedule.
+PRAutoBlogger monitors social media platforms (starting with Reddit) for recurring questions, complaints, and product comparisons in your niche. It uses AI to analyze these patterns, generate well-structured blog articles, run them through an editorial review agent, and publish them on a configurable daily schedule.
**Key Features:**
@@ -30,9 +30,9 @@ Uses OpenRouter for access to models from Anthropic, OpenAI, Google, Meta, and m
== Installation ==
-1. Upload the `autoblogger` folder to the `/wp-content/plugins/` directory.
+1. Upload the `prautoblogger` folder to the `/wp-content/plugins/` directory.
2. Activate the plugin through the 'Plugins' menu in WordPress.
-3. Go to AutoBlogger > Settings to configure your API keys and preferences.
+3. Go to PRAutoBlogger > Settings to configure your API keys and preferences.
4. Add your OpenRouter API key and Reddit app credentials.
5. Configure your target subreddits and content preferences.
6. Click "Test Connections" to verify setup.
@@ -50,7 +50,7 @@ By default, articles go through the chief editor agent. Articles that pass revie
= How do I set up Reddit API access? =
-Go to reddit.com/prefs/apps, create a "script" type application. Copy the client ID and secret into the AutoBlogger settings page.
+Go to reddit.com/prefs/apps, create a "script" type application. Copy the client ID and secret into the PRAutoBlogger settings page.
== Changelog ==
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 8aa6e55..a18d03a 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# ============================================================================
-# AutoBlogger Manual Deploy Script
+# PRAutoBlogger Manual Deploy Script
#
# Packages the plugin into a clean zip and deploys it to WordPress via the
# deploy-receiver REST endpoint. Use this for hotfixes or local deploys when
@@ -16,7 +16,7 @@
# If not set, the script will look for a .env file in the project root.
#
# @see .github/workflows/deploy.yml — Automated pipeline (same deploy step).
-# @see .github/mu-plugins/autoblogger-deploy-receiver.php — Server endpoint.
+# @see ARCHITECTURE.md — Deploy architecture (Code Snippets snippet #8 on server).
# ============================================================================
set -euo pipefail
@@ -53,8 +53,8 @@ fi
# ── Read version ────────────────────────────────────────────────────────────
-VERSION=$(grep -oP 'Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$PROJECT_ROOT/autoblogger.php" || echo "0.0.0")
-info "AutoBlogger v${VERSION}"
+VERSION=$(grep -oP 'Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$PROJECT_ROOT/prautoblogger.php" || echo "0.0.0")
+info "PRAutoBlogger v${VERSION}"
# ── PHP lint check ──────────────────────────────────────────────────────────
@@ -77,8 +77,8 @@ info "Lint passed."
# ── Package ─────────────────────────────────────────────────────────────────
BUILD_DIR=$(mktemp -d)
-PLUGIN_DIR="$BUILD_DIR/autoblogger"
-ZIP_PATH="$BUILD_DIR/autoblogger-${VERSION}.zip"
+PLUGIN_DIR="$BUILD_DIR/prautoblogger"
+ZIP_PATH="$BUILD_DIR/prautoblogger-${VERSION}.zip"
info "Packaging plugin..."
@@ -110,7 +110,7 @@ rsync -a \
"$PROJECT_ROOT/" "$PLUGIN_DIR/"
cd "$BUILD_DIR"
-zip -rq "$ZIP_PATH" autoblogger/
+zip -rq "$ZIP_PATH" prautoblogger/
ZIP_SIZE=$(du -h "$ZIP_PATH" | cut -f1)
info "Built ${ZIP_PATH} (${ZIP_SIZE})"
@@ -157,4 +157,4 @@ fi
# ── Cleanup ─────────────────────────────────────────────────────────────────
rm -rf "$BUILD_DIR"
-info "Done. AutoBlogger v${VERSION} is live."
+info "Done. PRAutoBlogger v${VERSION} is live."
diff --git a/templates/admin/log-viewer.php b/templates/admin/log-viewer.php
index ee3b5d0..348a24e 100755
--- a/templates/admin/log-viewer.php
+++ b/templates/admin/log-viewer.php
@@ -2,7 +2,7 @@
/**
* Log viewer template — displays structured application log entries.
*
- * Variables from Autoblogger_Log_Viewer::render_page():
+ * Variables from PRAutoBlogger_Log_Viewer::render_page():
* $rows — array of log entry rows.
* $total — total matching entries.
* $total_pages — total pages.
@@ -17,30 +17,30 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-$base_url = admin_url( 'admin.php?page=autoblogger-logs' );
+$base_url = admin_url( 'admin.php?page=prautoblogger-logs' );
$level_counts = [
- 'all' => __( 'All', 'autoblogger' ),
- 'error' => __( 'Errors', 'autoblogger' ),
- 'warning' => __( 'Warnings', 'autoblogger' ),
- 'info' => __( 'Info', 'autoblogger' ),
- 'debug' => __( 'Debug', 'autoblogger' ),
+ 'all' => __( 'All', 'prautoblogger' ),
+ 'error' => __( 'Errors', 'prautoblogger' ),
+ 'warning' => __( 'Warnings', 'prautoblogger' ),
+ 'info' => __( 'Info', 'prautoblogger' ),
+ 'debug' => __( 'Debug', 'prautoblogger' ),
];
?>
-
+
@@ -54,10 +54,10 @@ $level_counts = [
@@ -66,7 +66,7 @@ $level_counts = [
' . esc_html( number_format( $total ) ) . ''
);
?>
@@ -75,16 +75,16 @@ $level_counts = [
-
-
-
-
+
+
+
+
@@ -105,7 +105,7 @@ $level_counts = [
-
+
diff --git a/templates/admin/metabox-generation-info.php b/templates/admin/metabox-generation-info.php
index 5a9a58e..b9778eb 100755
--- a/templates/admin/metabox-generation-info.php
+++ b/templates/admin/metabox-generation-info.php
@@ -1,6 +1,6 @@
get_post_meta( $post->ID, '_autoblogger_topic', true ),
- 'type' => get_post_meta( $post->ID, '_autoblogger_article_type', true ),
- 'model' => get_post_meta( $post->ID, '_autoblogger_model_used', true ),
- 'pipeline' => get_post_meta( $post->ID, '_autoblogger_pipeline_mode', true ),
- 'verdict' => get_post_meta( $post->ID, '_autoblogger_editor_verdict', true ),
- 'editor_notes' => get_post_meta( $post->ID, '_autoblogger_editor_notes', true ),
- 'quality_score' => get_post_meta( $post->ID, '_autoblogger_quality_score', true ),
- 'seo_score' => get_post_meta( $post->ID, '_autoblogger_seo_score', true ),
- 'generated_at' => get_post_meta( $post->ID, '_autoblogger_generated_at', true ),
- 'keywords' => get_post_meta( $post->ID, '_autoblogger_target_keywords', true ),
+ 'topic' => get_post_meta( $post->ID, '_prautoblogger_topic', true ),
+ 'type' => get_post_meta( $post->ID, '_prautoblogger_article_type', true ),
+ 'model' => get_post_meta( $post->ID, '_prautoblogger_model_used', true ),
+ 'pipeline' => get_post_meta( $post->ID, '_prautoblogger_pipeline_mode', true ),
+ 'verdict' => get_post_meta( $post->ID, '_prautoblogger_editor_verdict', true ),
+ 'editor_notes' => get_post_meta( $post->ID, '_prautoblogger_editor_notes', true ),
+ 'quality_score' => get_post_meta( $post->ID, '_prautoblogger_quality_score', true ),
+ 'seo_score' => get_post_meta( $post->ID, '_prautoblogger_seo_score', true ),
+ 'generated_at' => get_post_meta( $post->ID, '_prautoblogger_generated_at', true ),
+ 'keywords' => get_post_meta( $post->ID, '_prautoblogger_target_keywords', true ),
];
?>
-
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
+
%
|
-
+
%
-
+
-
+
diff --git a/templates/admin/metrics-page.php b/templates/admin/metrics-page.php
index 03c3273..d580b27 100755
--- a/templates/admin/metrics-page.php
+++ b/templates/admin/metrics-page.php
@@ -9,9 +9,9 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-$cost_tracker = new Autoblogger_Cost_Tracker();
+$cost_tracker = new PRAutoBlogger_Cost_Tracker();
$monthly_spend = $cost_tracker->get_monthly_spend();
-$budget = (float) get_option( 'autoblogger_monthly_budget_usd', 50.00 );
+$budget = (float) get_option( 'prautoblogger_monthly_budget_usd', 50.00 );
$utilization = $cost_tracker->get_budget_utilization();
$daily_spend = $cost_tracker->get_daily_spend( 30 );
@@ -19,64 +19,64 @@ $first_of_month = gmdate( 'Y-m-01' );
$today = gmdate( 'Y-m-d' );
$stage_breakdown = $cost_tracker->get_spend_by_stage( $first_of_month, $today );
?>
-
-
+
+
-
-
-
-
+
+
+
+
$
-
-
-
-
+
+
+
%
-
-
-
-
-
+
-
-
-
-
+
+
+
+
-
+
$data ) : ?>
@@ -91,17 +91,17 @@ $stage_breakdown = $cost_tracker->get_spend_by_stage( $first_of_month, $today );
-
+
-
-
+
+
-
+
$cost ) : ?>
diff --git a/templates/admin/review-queue.php b/templates/admin/review-queue.php
index 38dd446..04b31be 100755
--- a/templates/admin/review-queue.php
+++ b/templates/admin/review-queue.php
@@ -2,7 +2,7 @@
/**
* Review queue template — lists generated drafts awaiting editorial approval.
*
- * Variables available from Autoblogger_Review_Queue::render_page():
+ * Variables available from PRAutoBlogger_Review_Queue::render_page():
* $query (WP_Query) — the pending draft posts.
*
* @see admin/class-review-queue.php — Renders this template.
@@ -12,11 +12,11 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-$bulk_done = isset( $_GET['autoblogger_bulk_done'] ) ? absint( $_GET['autoblogger_bulk_done'] ) : 0;
-$bulk_type = isset( $_GET['autoblogger_bulk_type'] ) ? sanitize_text_field( wp_unslash( $_GET['autoblogger_bulk_type'] ) ) : '';
+$bulk_done = isset( $_GET['prautoblogger_bulk_done'] ) ? absint( $_GET['prautoblogger_bulk_done'] ) : 0;
+$bulk_type = isset( $_GET['prautoblogger_bulk_type'] ) ? sanitize_text_field( wp_unslash( $_GET['prautoblogger_bulk_type'] ) ) : '';
?>
-
-
+
+
0 && '' !== $bulk_type ) : ?>
@@ -25,13 +25,13 @@ $bulk_type = isset( $_GET['autoblogger_bulk_type'] ) ? sanitize_text_field( wp_u
if ( 'approve' === $bulk_type ) {
printf(
/* translators: %d: number of posts */
- esc_html( _n( '%d post published.', '%d posts published.', $bulk_done, 'autoblogger' ) ),
+ esc_html( _n( '%d post published.', '%d posts published.', $bulk_done, 'prautoblogger' ) ),
$bulk_done
);
} else {
printf(
/* translators: %d: number of posts */
- esc_html( _n( '%d post rejected.', '%d posts rejected.', $bulk_done, 'autoblogger' ) ),
+ esc_html( _n( '%d post rejected.', '%d posts rejected.', $bulk_done, 'prautoblogger' ) ),
$bulk_done
);
}
@@ -40,71 +40,71 @@ $bulk_type = isset( $_GET['autoblogger_bulk_type'] ) ? sanitize_text_field( wp_u
-
+
have_posts() ) : ?>
-