style(phpcs): autofix WordPress-Core violations via phpcbf

Run phpcbf --standard=WordPress-Core across includes/, prautoblogger.php,
and uninstall.php. Fixed 1,362 formatting violations in 75 files:
  - Short array syntax: [] → array()
  - Line breaking for long function calls
  - Consistent indentation
  - Function call parameter alignment

No logic changes; purely mechanical formatting.
This commit is contained in:
peptiderepo
2026-04-23 02:42:48 +00:00
parent 7cd65b145f
commit f3eacd0d8d
75 changed files with 2148 additions and 1412 deletions

View File

@@ -62,11 +62,13 @@ class PRAutoBlogger_Admin_Notices {
} elseif ( $utilization >= 80.0 ) {
printf(
'<div class="notice notice-warning"><p>%s</p></div>',
esc_html( sprintf(
esc_html(
sprintf(
/* translators: %s: budget utilization percentage */
__( 'PRAutoBlogger: Monthly API budget is at %.0f%%. Consider increasing the budget or reducing daily article count.', 'prautoblogger' ),
$utilization
) )
__( 'PRAutoBlogger: Monthly API budget is at %.0f%%. Consider increasing the budget or reducing daily article count.', 'prautoblogger' ),
$utilization
)
)
);
}
}

View File

@@ -26,7 +26,7 @@ class PRAutoBlogger_Admin_Page {
__( 'PRAutoBlogger', 'prautoblogger' ),
'manage_options',
self::PAGE_SLUG,
[ $this, 'render_page' ],
array( $this, 'render_page' ),
'dashicons-edit-page',
30
);
@@ -43,54 +43,62 @@ class PRAutoBlogger_Admin_Page {
foreach ( $fields as $field ) {
$section = $field['section'] ?? 'prautoblogger_api';
register_setting( self::OPTION_GROUP, $field['id'], [
'type' => $field['wp_type'] ?? 'string',
'sanitize_callback' => [ $this, 'sanitize_field' ],
'default' => $field['default'] ?? '',
] );
add_settings_field( $field['id'], $field['label'], [ $this, 'render_field' ], self::PAGE_SLUG, $section, $field );
register_setting(
self::OPTION_GROUP,
$field['id'],
array(
'type' => $field['wp_type'] ?? 'string',
'sanitize_callback' => array( $this, 'sanitize_field' ),
'default' => $field['default'] ?? '',
)
);
add_settings_field( $field['id'], $field['label'], array( $this, 'render_field' ), self::PAGE_SLUG, $section, $field );
}
}
/** Enqueue admin CSS and JS on all PRAutoBlogger admin pages. */
public function on_enqueue_assets( string $hook_suffix ): void {
$pages = [
$pages = array(
'toplevel_page_' . self::PAGE_SLUG,
'prautoblogger_page_prautoblogger-metrics',
'prautoblogger_page_prautoblogger-review-queue',
'prautoblogger_page_prautoblogger-ideas',
'prautoblogger_page_prautoblogger-logs',
];
);
if ( ! in_array( $hook_suffix, $pages, true ) ) {
return;
}
wp_enqueue_style( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/admin.css', [], PRAUTOBLOGGER_VERSION );
wp_enqueue_style( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/model-picker.css', [ 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION );
wp_enqueue_script( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/admin.js', [ 'jquery' ], PRAUTOBLOGGER_VERSION, true );
wp_enqueue_script( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/model-picker.js', [ 'jquery', 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION, true );
wp_enqueue_style( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/admin.css', array(), PRAUTOBLOGGER_VERSION );
wp_enqueue_style( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/css/model-picker.css', array( 'prautoblogger-admin' ), PRAUTOBLOGGER_VERSION );
wp_enqueue_script( 'prautoblogger-admin', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), PRAUTOBLOGGER_VERSION, true );
wp_enqueue_script( 'prautoblogger-model-picker', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/model-picker.js', array( 'jquery', 'prautoblogger-admin' ), PRAUTOBLOGGER_VERSION, true );
wp_localize_script( 'prautoblogger-admin', 'prautobloggerAdmin', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'adminUrl' => admin_url(),
'siteUrl' => home_url( '/' ),
'generateNonce' => wp_create_nonce( 'prautoblogger_generate_now' ),
'imageNonce' => wp_create_nonce( 'prautoblogger_generate_image' ),
'testNonce' => wp_create_nonce( 'prautoblogger_test_connection' ),
'modelsNonce' => wp_create_nonce( 'prautoblogger_get_models' ),
'reviewNonce' => wp_create_nonce( 'prautoblogger_review_queue' ),
'ideaGenNonce' => wp_create_nonce( 'prautoblogger_idea_gen' ),
'generatingText' => __( 'Generating...', 'prautoblogger' ),
'generateText' => __( 'Generate Now', 'prautoblogger' ),
'testingText' => __( 'Testing...', 'prautoblogger' ),
'testText' => __( 'Test Connections', 'prautoblogger' ),
// Image models for the picker — defined in the settings class to keep this file short.
'imageModels' => PRAutoBlogger_Settings_Fields_Extended::get_image_models(),
] );
wp_localize_script(
'prautoblogger-admin',
'prautobloggerAdmin',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'adminUrl' => admin_url(),
'siteUrl' => home_url( '/' ),
'generateNonce' => wp_create_nonce( 'prautoblogger_generate_now' ),
'imageNonce' => wp_create_nonce( 'prautoblogger_generate_image' ),
'testNonce' => wp_create_nonce( 'prautoblogger_test_connection' ),
'modelsNonce' => wp_create_nonce( 'prautoblogger_get_models' ),
'reviewNonce' => wp_create_nonce( 'prautoblogger_review_queue' ),
'ideaGenNonce' => wp_create_nonce( 'prautoblogger_idea_gen' ),
'generatingText' => __( 'Generating...', 'prautoblogger' ),
'generateText' => __( 'Generate Now', 'prautoblogger' ),
'testingText' => __( 'Testing...', 'prautoblogger' ),
'testText' => __( 'Test Connections', 'prautoblogger' ),
// Image models for the picker — defined in the settings class to keep this file short.
'imageModels' => PRAutoBlogger_Settings_Fields_Extended::get_image_models(),
)
);
// Ideas browser: generate-from-idea JS (only on the Ideas page).
if ( 'prautoblogger_page_prautoblogger-ideas' === $hook_suffix ) {
wp_enqueue_script( 'prautoblogger-ideas-browser', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/ideas-browser.js', [ 'jquery', 'prautoblogger-admin' ], PRAUTOBLOGGER_VERSION, true );
wp_enqueue_script( 'prautoblogger-ideas-browser', PRAUTOBLOGGER_PLUGIN_URL . 'assets/js/ideas-browser.js', array( 'jquery', 'prautoblogger-admin' ), PRAUTOBLOGGER_VERSION, true );
}
}
@@ -127,15 +135,22 @@ class PRAutoBlogger_Admin_Page {
case 'url':
case 'time':
$attrs = '';
if ( isset( $args['min'] ) ) { $attrs .= ' min="' . esc_attr( (string) $args['min'] ) . '"'; }
if ( isset( $args['max'] ) ) { $attrs .= ' max="' . esc_attr( (string) $args['max'] ) . '"'; }
if ( isset( $args['step'] ) ) { $attrs .= ' step="' . esc_attr( (string) $args['step'] ) . '"'; }
if ( isset( $args['min'] ) ) {
$attrs .= ' min="' . esc_attr( (string) $args['min'] ) . '"'; }
if ( isset( $args['max'] ) ) {
$attrs .= ' max="' . esc_attr( (string) $args['max'] ) . '"'; }
if ( isset( $args['step'] ) ) {
$attrs .= ' step="' . esc_attr( (string) $args['step'] ) . '"'; }
printf( '<input type="%s" id="%s" name="%s" value="%s" class="ab-input" %s />', esc_attr( $type ), $id, $id, esc_attr( (string) $value ), $attrs );
break;
case 'password':
printf( '<input type="password" id="%s" name="%s" value="" class="ab-input" placeholder="%s" autocomplete="off" />',
$id, $id, '' !== $value ? esc_attr__( 'Saved (enter new value to change)', 'prautoblogger' ) : '' );
printf(
'<input type="password" id="%s" name="%s" value="" class="ab-input" placeholder="%s" autocomplete="off" />',
$id,
$id,
'' !== $value ? esc_attr__( 'Saved (enter new value to change)', 'prautoblogger' ) : ''
);
break;
case 'textarea':
@@ -144,30 +159,40 @@ class PRAutoBlogger_Admin_Page {
case 'select':
printf( '<select id="%s" name="%s" class="ab-select">', $id, $id );
foreach ( ( $args['options'] ?? [] ) as $v => $label ) {
foreach ( ( $args['options'] ?? array() ) as $v => $label ) {
printf( '<option value="%s"%s>%s</option>', esc_attr( (string) $v ), selected( $value, (string) $v, false ), esc_html( $label ) );
}
echo '</select>';
break;
case 'toggle':
$checked = in_array( $value, [ '1', 'yes', true ], true );
printf( '<label class="ab-toggle"><input type="hidden" name="%s" value="0" /><input type="checkbox" name="%s" value="1" %s /><span class="ab-toggle-slider"></span></label>',
$id, $id, checked( $checked, true, false ) );
$checked = in_array( $value, array( '1', 'yes', true ), true );
printf(
'<label class="ab-toggle"><input type="hidden" name="%s" value="0" /><input type="checkbox" name="%s" value="1" %s /><span class="ab-toggle-slider"></span></label>',
$id,
$id,
checked( $checked, true, false )
);
break;
case 'checkboxes':
$current = json_decode( (string) $value, true ) ?: [];
$current = json_decode( (string) $value, true ) ?: array();
// Hidden input ensures an empty value is POSTed when all boxes
// are unchecked — without this, the browser omits the field
// entirely, so WordPress never fires the sanitize callback and
// the old value persists in wp_options.
printf( '<input type="hidden" name="%s" value="" />', $id );
foreach ( ( $args['options'] ?? [] ) as $v => $label ) {
foreach ( ( $args['options'] ?? array() ) as $v => $label ) {
$is_checked = in_array( $v, $current, true );
$disabled = strpos( $label, 'coming soon' ) !== false ? ' disabled' : '';
printf( '<label class="ab-checkbox-label"><input type="checkbox" name="%s[]" value="%s" %s%s /> %s</label>',
$id, esc_attr( $v ), checked( $is_checked, true, false ), $disabled, esc_html( $label ) );
printf(
'<label class="ab-checkbox-label"><input type="checkbox" name="%s[]" value="%s" %s%s /> %s</label>',
$id,
esc_attr( $v ),
checked( $is_checked, true, false ),
$disabled,
esc_html( $label )
);
}
break;
@@ -180,11 +205,30 @@ class PRAutoBlogger_Admin_Page {
break;
case 'author_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' ] );
wp_dropdown_users(
array(
'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 —', 'prautoblogger' ), 'option_none_value' => '0', 'class' => 'ab-select', 'hide_empty' => false ] );
wp_dropdown_categories(
array(
'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;
}
@@ -231,7 +275,7 @@ class PRAutoBlogger_Admin_Page {
$option_name = substr( $filter, strlen( 'sanitize_option_' ) );
}
$encrypted = [ 'prautoblogger_openrouter_api_key', 'prautoblogger_ga4_credentials_json', 'prautoblogger_runware_api_key' ];
$encrypted = array( 'prautoblogger_openrouter_api_key', 'prautoblogger_ga4_credentials_json', 'prautoblogger_runware_api_key' );
if ( in_array( $option_name, $encrypted, true ) ) {
// Empty value means password field wasn't touched — keep existing.
if ( '' === $value ) {
@@ -251,7 +295,7 @@ class PRAutoBlogger_Admin_Page {
return PRAutoBlogger_Encryption::encrypt( sanitize_text_field( $value ) );
}
$json_fields = [ 'prautoblogger_target_subreddits', 'prautoblogger_topic_exclusions', 'prautoblogger_enabled_sources' ];
$json_fields = array( 'prautoblogger_target_subreddits', 'prautoblogger_topic_exclusions', 'prautoblogger_enabled_sources' );
if ( in_array( $option_name, $json_fields, true ) ) {
// PHP array from checkboxes — sanitize each item and re-encode.
if ( is_array( $value ) ) {
@@ -275,7 +319,7 @@ class PRAutoBlogger_Admin_Page {
return wp_json_encode( array_values( array_map( 'sanitize_text_field', $items ) ) );
}
$numeric = [ 'prautoblogger_daily_article_target', 'prautoblogger_monthly_budget_usd', 'prautoblogger_min_word_count', 'prautoblogger_max_word_count', 'prautoblogger_default_author', 'prautoblogger_default_category', 'prautoblogger_pullpush_cache_ttl', 'prautoblogger_reddit_posts_per_subreddit' ];
$numeric = array( 'prautoblogger_daily_article_target', 'prautoblogger_monthly_budget_usd', 'prautoblogger_min_word_count', 'prautoblogger_max_word_count', 'prautoblogger_default_author', 'prautoblogger_default_category', 'prautoblogger_pullpush_cache_ttl', 'prautoblogger_reddit_posts_per_subreddit' );
if ( in_array( $option_name, $numeric, true ) ) {
return is_numeric( $value ) ? $value : 0;
}

View File

@@ -27,7 +27,7 @@ class PRAutoBlogger_Dashboard_Widget {
wp_add_dashboard_widget(
'prautoblogger_status',
__( 'PRAutoBlogger Status', 'prautoblogger' ),
[ $this, 'render_widget' ]
array( $this, 'render_widget' )
);
}
@@ -44,7 +44,8 @@ class PRAutoBlogger_Dashboard_Widget {
$next_run = wp_next_scheduled( 'prautoblogger_daily_generation' );
printf( '<p><strong>%s</strong> $%.2f / $%.2f (%.0f%%)</p>',
printf(
'<p><strong>%s</strong> $%.2f / $%.2f (%.0f%%)</p>',
esc_html__( 'Monthly Spend:', 'prautoblogger' ),
$monthly_spend,
$budget,
@@ -52,7 +53,8 @@ class PRAutoBlogger_Dashboard_Widget {
);
if ( false !== $next_run ) {
printf( '<p><strong>%s</strong> %s</p>',
printf(
'<p><strong>%s</strong> %s</p>',
esc_html__( 'Next Generation:', 'prautoblogger' ),
esc_html( wp_date( 'M j, Y g:i A', $next_run ) )
);

View File

@@ -28,7 +28,7 @@ class PRAutoBlogger_Ideas_Browser {
__( 'Ideas', 'prautoblogger' ),
'manage_options',
'prautoblogger-ideas',
[ $this, 'render_page' ]
array( $this, 'render_page' )
);
}
@@ -62,43 +62,51 @@ class PRAutoBlogger_Ideas_Browser {
public function on_ajax_generate_from_idea(): void {
check_ajax_referer( 'prautoblogger_idea_gen', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
wp_send_json_error( array( 'message' => 'Insufficient permissions.' ), 403 );
return;
}
$idea_id = absint( $_POST['idea_id'] ?? 0 );
if ( $idea_id < 1 ) {
wp_send_json_error( [ 'message' => 'Invalid idea ID.' ] );
wp_send_json_error( array( 'message' => 'Invalid idea ID.' ) );
return;
}
// Build Article_Idea from the analysis row.
$idea_data = self::load_idea_data( $idea_id );
if ( null === $idea_data ) {
wp_send_json_error( [ 'message' => 'Idea not found.' ] );
wp_send_json_error( array( 'message' => 'Idea not found.' ) );
return;
}
// Store idea for the cron handler and set initial "running" status.
set_transient( self::STATUS_PREFIX . 'data_' . $idea_id, $idea_data, self::STATUS_TTL );
set_transient( self::STATUS_PREFIX . $idea_id, [
'status' => 'running',
'stage' => __( 'Starting generation…', 'prautoblogger' ),
'started' => time(),
], self::STATUS_TTL );
set_transient(
self::STATUS_PREFIX . $idea_id,
array(
'status' => 'running',
'stage' => __( 'Starting generation…', 'prautoblogger' ),
'started' => time(),
),
self::STATUS_TTL
);
// Schedule background cron — passes idea_id as argument.
$hook = 'prautoblogger_generate_from_idea';
if ( ! wp_next_scheduled( $hook, [ $idea_id ] ) ) {
wp_schedule_single_event( time(), $hook, [ $idea_id ] );
if ( ! wp_next_scheduled( $hook, array( $idea_id ) ) ) {
wp_schedule_single_event( time(), $hook, array( $idea_id ) );
}
spawn_cron();
wp_remote_post(
site_url( 'wp-cron.php?doing_wp_cron=' . sprintf( '%.22F', microtime( true ) ) ),
[ 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false ]
array(
'timeout' => 0.01,
'blocking' => false,
'sslverify' => false,
)
);
wp_send_json_success( [ 'message' => 'Generation started.' ] );
wp_send_json_success( array( 'message' => 'Generation started.' ) );
}
/**
@@ -109,14 +117,14 @@ class PRAutoBlogger_Ideas_Browser {
public function on_ajax_idea_gen_status(): void {
check_ajax_referer( 'prautoblogger_idea_gen', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => 'Insufficient permissions.' ], 403 );
wp_send_json_error( array( 'message' => 'Insufficient permissions.' ), 403 );
return;
}
$idea_id = absint( $_POST['idea_id'] ?? 0 );
$status = get_transient( self::STATUS_PREFIX . $idea_id );
if ( ! is_array( $status ) ) {
wp_send_json_success( [ 'status' => 'idle' ] );
wp_send_json_success( array( 'status' => 'idle' ) );
return;
}
@@ -142,12 +150,26 @@ class PRAutoBlogger_Ideas_Browser {
$key = self::STATUS_PREFIX . $idea_id;
$idea_data = get_transient( self::STATUS_PREFIX . 'data_' . $idea_id );
if ( ! is_array( $idea_data ) ) {
set_transient( $key, [ 'status' => 'error', 'message' => 'Idea data expired.' ], self::STATUS_TTL );
set_transient(
$key,
array(
'status' => 'error',
'message' => 'Idea data expired.',
),
self::STATUS_TTL
);
return;
}
if ( ! PRAutoBlogger_Generation_Lock::acquire() ) {
set_transient( $key, [ 'status' => 'error', 'message' => 'Another generation is running.' ], self::STATUS_TTL );
set_transient(
$key,
array(
'status' => 'error',
'message' => 'Another generation is running.',
),
self::STATUS_TTL
);
return;
}
@@ -166,19 +188,30 @@ class PRAutoBlogger_Ideas_Browser {
// Find the generated post ID for the "View" link.
$post_id = self::find_post_by_run_id( $cost_tracker->get_run_id() );
set_transient( $key, [
'status' => 'complete',
'generated' => $result['generated'],
'published' => $result['published'],
'cost' => $result['cost'],
'post_id' => $post_id,
], self::STATUS_TTL );
set_transient(
$key,
array(
'status' => 'complete',
'generated' => $result['generated'],
'published' => $result['published'],
'cost' => $result['cost'],
'post_id' => $post_id,
),
self::STATUS_TTL
);
} catch ( \Throwable $e ) {
PRAutoBlogger_Logger::instance()->error(
sprintf( 'Idea generation %s for #%d: %s', get_class( $e ), $idea_id, $e->getMessage() ),
'pipeline'
);
set_transient( $key, [ 'status' => 'error', 'message' => $e->getMessage() ], self::STATUS_TTL );
set_transient(
$key,
array(
'status' => 'error',
'message' => $e->getMessage(),
),
self::STATUS_TTL
);
}
PRAutoBlogger_Generation_Lock::release();
@@ -231,26 +264,26 @@ class PRAutoBlogger_Ideas_Browser {
return null;
}
$meta = json_decode( $row['metadata_json'] ?? '{}', true ) ?: [];
return [
$meta = json_decode( $row['metadata_json'] ?? '{}', true ) ?: array();
return array(
'topic' => $row['topic'],
'article_type' => $row['analysis_type'],
'suggested_title' => $meta['suggested_title'] ?? $row['topic'],
'summary' => $row['summary'] ?? '',
'score' => (float) $row['relevance_score'],
'analysis_id' => (int) $row['id'],
'source_ids' => json_decode( $row['source_ids_json'] ?? '[]', true ) ?: [],
'key_points' => $meta['key_points'] ?? [],
'target_keywords' => $meta['target_keywords'] ?? [],
];
'source_ids' => json_decode( $row['source_ids_json'] ?? '[]', true ) ?: array(),
'key_points' => $meta['key_points'] ?? array(),
'target_keywords' => $meta['target_keywords'] ?? array(),
);
}
/** Query analysis results with optional filtering and pagination. */
private function query_ideas( int $paged, string $type ): array {
global $wpdb;
$table = $wpdb->prefix . 'prautoblogger_analysis_results';
$where = [];
$params = [];
$where = array();
$params = array();
if ( '' !== $type ) {
$where[] = 'analysis_type = %s';
@@ -276,7 +309,10 @@ class PRAutoBlogger_Ideas_Browser {
? $wpdb->get_results( $full_sql, ARRAY_A )
: $wpdb->get_results( $wpdb->prepare( $full_sql, ...$params ), ARRAY_A );
return [ 'rows' => $rows ?: [], 'total' => $total ];
return array(
'rows' => $rows ?: array(),
'total' => $total,
);
}
/** Get counts per analysis_type for the filter badges. */
@@ -290,8 +326,8 @@ class PRAutoBlogger_Ideas_Browser {
ARRAY_A
);
$counts = [];
foreach ( $rows ?: [] as $row ) {
$counts = array();
foreach ( $rows ?: array() as $row ) {
$counts[ $row['analysis_type'] ] = (int) $row['cnt'];
}
return $counts;

View File

@@ -33,64 +33,64 @@ class PRAutoBlogger_Image_Model_Registry {
* @return array<int, array<string, mixed>>
*/
public static function get_models(): array {
return [
[
return array(
array(
'id' => 'runware:100@1',
'name' => 'FLUX.1 schnell (Runware)',
'provider' => 'runware',
'cost_per_image' => 0.0006,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'Default. Fast, very cheap, comic-friendly fidelity.', 'prautoblogger' ),
],
[
),
array(
'id' => 'runware:101@1',
'name' => 'FLUX.1 dev (Runware)',
'provider' => 'runware',
'cost_per_image' => 0.02,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'Higher fidelity FLUX. ~30x schnell cost.', 'prautoblogger' ),
],
[
),
array(
'id' => 'google/gemini-2.5-flash-image',
'name' => 'Gemini 2.5 Flash Image (Nano Banana)',
'provider' => 'openrouter',
'cost_per_image' => 0.005,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'Mid-tier quality, proven.', 'prautoblogger' ),
],
[
),
array(
'id' => 'google/gemini-3.1-flash-image-preview',
'name' => 'Gemini 3.1 Flash Image (Nano Banana 2)',
'provider' => 'openrouter',
'cost_per_image' => 0.008,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'Latest Google. Better quality.', 'prautoblogger' ),
],
[
),
array(
'id' => 'google/gemini-3-pro-image-preview',
'name' => 'Gemini 3 Pro Image (Nano Banana Pro)',
'provider' => 'openrouter',
'cost_per_image' => 0.03,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'Highest quality Google. Premium.', 'prautoblogger' ),
],
[
),
array(
'id' => 'openai/gpt-5-image-mini',
'name' => 'GPT-5 Image Mini',
'provider' => 'openrouter',
'cost_per_image' => 0.02,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'OpenAI budget image model.', 'prautoblogger' ),
],
[
),
array(
'id' => 'openai/gpt-5-image',
'name' => 'GPT-5 Image',
'provider' => 'openrouter',
'cost_per_image' => 0.08,
'capabilities' => [ 'image_generation' ],
'capabilities' => array( 'image_generation' ),
'description' => __( 'OpenAI premium. Photorealistic.', 'prautoblogger' ),
],
];
),
);
}
/**

View File

@@ -29,7 +29,7 @@ class PRAutoBlogger_Log_Viewer {
__( 'Activity Log', 'prautoblogger' ),
'manage_options',
'prautoblogger-logs',
[ $this, 'render_page' ]
array( $this, 'render_page' )
);
}
@@ -66,19 +66,21 @@ class PRAutoBlogger_Log_Viewer {
check_ajax_referer( 'prautoblogger_clear_logs', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ), 403 );
return;
}
$days = isset( $_POST['days'] ) ? absint( $_POST['days'] ) : 30;
$deleted = PRAutoBlogger_Logger::prune( $days );
wp_send_json_success( [
'message' => sprintf(
wp_send_json_success(
array(
'message' => sprintf(
/* translators: %d: number of entries deleted */
__( '%d log entries cleared.', 'prautoblogger' ),
$deleted
),
] );
__( '%d log entries cleared.', 'prautoblogger' ),
$deleted
),
)
);
}
}

View File

@@ -27,7 +27,7 @@ class PRAutoBlogger_Metrics_Page {
__( 'Metrics & Costs', 'prautoblogger' ),
'manage_options',
'prautoblogger-metrics',
[ $this, 'render_page' ]
array( $this, 'render_page' )
);
}

View File

@@ -23,11 +23,11 @@ class PRAutoBlogger_Post_List_Columns {
* Side effects: adds WordPress filters/actions for post list customization.
*/
public function register(): void {
add_filter( 'manage_posts_columns', [ $this, 'filter_add_columns' ] );
add_action( 'manage_posts_custom_column', [ $this, 'on_render_column' ], 10, 2 );
add_filter( 'manage_edit-post_sortable_columns', [ $this, 'filter_sortable_columns' ] );
add_action( 'pre_get_posts', [ $this, 'on_sort_by_model' ] );
add_action( 'admin_head', [ $this, 'on_column_styles' ] );
add_filter( 'manage_posts_columns', array( $this, 'filter_add_columns' ) );
add_action( 'manage_posts_custom_column', array( $this, 'on_render_column' ), 10, 2 );
add_filter( 'manage_edit-post_sortable_columns', array( $this, 'filter_sortable_columns' ) );
add_action( 'pre_get_posts', array( $this, 'on_sort_by_model' ) );
add_action( 'admin_head', array( $this, 'on_column_styles' ) );
}
/**
@@ -37,7 +37,7 @@ class PRAutoBlogger_Post_List_Columns {
* @return array<string, string> Modified columns.
*/
public function filter_add_columns( array $columns ): array {
$new = [];
$new = array();
foreach ( $columns as $key => $label ) {
$new[ $key ] = $label;
if ( 'title' === $key ) {
@@ -192,7 +192,7 @@ class PRAutoBlogger_Post_List_Columns {
return;
}
$total = array_sum( array_column( $rows, 'estimated_cost' ) );
$total = array_sum( array_column( $rows, 'estimated_cost' ) );
$formatted = $total < 0.01
? sprintf( '$%.4f', $total )
: sprintf( '$%.2f', $total );
@@ -243,7 +243,7 @@ class PRAutoBlogger_Post_List_Columns {
. '<td><strong>%s</strong></td></tr>',
esc_html( $total_f )
);
$html .= '</table>';
$html .= '</table>';
return $html;
}

View File

@@ -34,7 +34,7 @@ class PRAutoBlogger_Post_Metabox {
add_meta_box(
'prautoblogger_generation_info',
__( 'PRAutoBlogger Generation Info', 'prautoblogger' ),
[ $this, 'render_metabox' ],
array( $this, 'render_metabox' ),
'post',
'side',
'default'

View File

@@ -28,11 +28,11 @@ class PRAutoBlogger_Review_Queue {
__( 'Review Queue', 'prautoblogger' ),
'manage_options',
'prautoblogger-review-queue',
[ $this, 'render_page' ]
array( $this, 'render_page' )
);
if ( false !== $hook ) {
add_action( "load-{$hook}", [ $this, 'on_handle_bulk_actions' ] );
add_action( "load-{$hook}", array( $this, 'on_handle_bulk_actions' ) );
}
}
@@ -60,20 +60,22 @@ class PRAutoBlogger_Review_Queue {
* @return \WP_Query
*/
public function get_pending_posts( int $paged = 1 ): \WP_Query {
return new \WP_Query( [
'post_type' => 'post',
'post_status' => 'draft',
'posts_per_page' => 20,
'paged' => $paged,
'meta_query' => [
[
'key' => '_prautoblogger_generated',
'value' => '1',
],
],
'orderby' => 'date',
'order' => 'DESC',
] );
return new \WP_Query(
array(
'post_type' => 'post',
'post_status' => 'draft',
'posts_per_page' => 20,
'paged' => $paged,
'meta_query' => array(
array(
'key' => '_prautoblogger_generated',
'value' => '1',
),
),
'orderby' => 'date',
'order' => 'DESC',
)
);
}
/**
@@ -100,7 +102,7 @@ class PRAutoBlogger_Review_Queue {
$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'] )
: [];
: array();
if ( empty( $post_ids ) ) {
return;
@@ -113,25 +115,31 @@ class PRAutoBlogger_Review_Queue {
}
if ( 'approve' === $action ) {
$result = wp_update_post( [ 'ID' => $post_id, 'post_status' => 'publish' ], true );
$result = wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
),
true
);
if ( ! is_wp_error( $result ) ) {
update_post_meta( $post_id, '_prautoblogger_approved_at', gmdate( 'c' ) );
$count++;
++$count;
}
} elseif ( 'reject' === $action ) {
$result = wp_trash_post( $post_id );
if ( false !== $result ) {
$count++;
++$count;
}
}
}
$redirect = add_query_arg(
[
'page' => 'prautoblogger-review-queue',
array(
'page' => 'prautoblogger-review-queue',
'prautoblogger_bulk_done' => $count,
'prautoblogger_bulk_type' => $action,
],
),
admin_url( 'admin.php' )
);
@@ -150,25 +158,36 @@ class PRAutoBlogger_Review_Queue {
check_ajax_referer( 'prautoblogger_review_queue', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
wp_send_json_error( array( '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.', 'prautoblogger' ) ] );
wp_send_json_error( array( 'message' => __( 'Invalid post ID.', 'prautoblogger' ) ) );
return;
}
$result = wp_update_post( [ 'ID' => $post_id, 'post_status' => 'publish' ], true );
$result = wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'publish',
),
true
);
if ( is_wp_error( $result ) ) {
wp_send_json_error( [ 'message' => $result->get_error_message() ] );
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
return;
}
update_post_meta( $post_id, '_prautoblogger_approved_at', gmdate( 'c' ) );
wp_send_json_success( [ 'message' => __( 'Post published.', 'prautoblogger' ), 'post_id' => $post_id ] );
wp_send_json_success(
array(
'message' => __( 'Post published.', 'prautoblogger' ),
'post_id' => $post_id,
)
);
}
/**
@@ -182,22 +201,27 @@ class PRAutoBlogger_Review_Queue {
check_ajax_referer( 'prautoblogger_review_queue', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'prautoblogger' ) ], 403 );
wp_send_json_error( array( '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.', 'prautoblogger' ) ] );
wp_send_json_error( array( '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.', 'prautoblogger' ) ] );
wp_send_json_error( array( 'message' => __( 'Failed to reject post.', 'prautoblogger' ) ) );
return;
}
wp_send_json_success( [ 'message' => __( 'Post rejected.', 'prautoblogger' ), 'post_id' => $post_id ] );
wp_send_json_success(
array(
'message' => __( 'Post rejected.', 'prautoblogger' ),
'post_id' => $post_id,
)
);
}
}

View File

@@ -33,9 +33,9 @@ class PRAutoBlogger_Settings_Fields_Extended {
* @return array<int, array<string, mixed>>
*/
public static function get_fields(): array {
return [
return array(
// ── Sources (extended) ──────────────────────────────────────
[
array(
'id' => 'prautoblogger_research_model',
'label' => __( 'Research Model', 'prautoblogger' ),
'type' => 'model_select',
@@ -44,43 +44,43 @@ class PRAutoBlogger_Settings_Fields_Extended {
'capability' => 'text→text',
'description' => __( 'Model for LLM Deep Research. Pick a reasoning-capable model (e.g. Grok 4.1 Fast, DeepSeek-R1) for best results. Only used when LLM Deep Research is enabled above.', 'prautoblogger' ),
'badge' => __( 'Reasoning', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_research_prompt',
'label' => __( 'Research Prompt', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_sources',
'default' => 'Conduct a deep research sweep of the {niche} space and identify the most substantive, actionable topics that practitioners and curious newcomers are searching for or actively discussing.' . "\n\n" . 'Focus on:' . "\n" . '- Practical questions real readers would search for (not theoretical debates)' . "\n" . '- Emerging trends gaining momentum in the community' . "\n" . '- Common misconceptions that deserve clarification' . "\n" . '- Comparative analysis (product vs. product, protocol vs. protocol)' . "\n" . '- Safety and efficacy topics that drive concern or curiosity' . "\n" . '- Beginner-to-advanced mix — cover entry-level and depth-focused topics' . "\n\n" . 'Aim for topics that feel timely, underserved by existing content, and likely to drive reader engagement. Return 8-12 findings with detailed analysis in each.',
'description' => __( 'The research brief sent to the LLM. Use {niche} as a placeholder for your niche description. The system prompt handles output format — this controls WHAT to research.', 'prautoblogger' ),
],
),
// ── AI Models (extended) ────────────────────────────────────
[
array(
'id' => 'prautoblogger_reasoning_enabled',
'label' => __( 'Enable Reasoning', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_models',
'default' => '0',
'description' => __( 'Send reasoning instructions to models that support it (e.g. Grok, DeepSeek-R1). Reasoning tokens are billed as output tokens — expect higher costs per call. Models that don\'t support reasoning will ignore this.', 'prautoblogger' ),
],
[
'id' => 'prautoblogger_reasoning_effort',
'label' => __( 'Reasoning Effort', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_models',
'default' => 'medium',
'options' => [
),
array(
'id' => 'prautoblogger_reasoning_effort',
'label' => __( 'Reasoning Effort', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_models',
'default' => 'medium',
'options' => array(
'xhigh' => __( 'Extra High — maximum depth, highest cost', 'prautoblogger' ),
'high' => __( 'High — thorough reasoning', 'prautoblogger' ),
'medium' => __( 'Medium — balanced (recommended)', 'prautoblogger' ),
'low' => __( 'Low — light reasoning, lower cost', 'prautoblogger' ),
'minimal' => __( 'Minimal — barely any reasoning', 'prautoblogger' ),
],
),
'description' => __( 'How much effort the model spends reasoning before answering. Higher effort = more reasoning tokens = higher cost. Only applies when reasoning is enabled above.', 'prautoblogger' ),
],
),
// ── Schedule & Budget ───────────────────────────────────────
[
array(
'id' => 'prautoblogger_daily_article_target',
'label' => __( 'Articles per Day', 'prautoblogger' ),
'type' => 'number',
@@ -88,8 +88,8 @@ class PRAutoBlogger_Settings_Fields_Extended {
'default' => 1,
'min' => 1,
'max' => 10,
],
[
),
array(
'id' => 'prautoblogger_schedule_time',
'label' => __( 'Generation Time', 'prautoblogger' ),
'type' => 'time',
@@ -100,8 +100,8 @@ class PRAutoBlogger_Settings_Fields_Extended {
esc_html__( "Daily generation runs at this time in your site's configured timezone (Settings → General → Timezone). Current site timezone: %s.", 'prautoblogger' ),
esc_html( function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC' )
),
],
[
),
array(
'id' => 'prautoblogger_monthly_budget_usd',
'label' => __( 'Monthly Budget (USD)', 'prautoblogger' ),
'type' => 'number',
@@ -110,66 +110,66 @@ class PRAutoBlogger_Settings_Fields_Extended {
'min' => 0,
'step' => '0.01',
'description' => __( 'Hard stop when reached. 0 = unlimited.', 'prautoblogger' ),
],
),
// ── Publishing ──────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_auto_publish',
'label' => __( 'Auto-Publish', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_publishing',
'default' => '0',
'description' => __( 'Automatically publish editor-approved posts. When off, all posts go to the Review Queue as drafts.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_default_author',
'label' => __( 'Default Author', 'prautoblogger' ),
'type' => 'author_select',
'section' => 'prautoblogger_publishing',
'default' => 0,
'description' => __( 'WordPress user assigned as author of generated posts.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_default_category',
'label' => __( 'Default Category', 'prautoblogger' ),
'type' => 'category_select',
'section' => 'prautoblogger_publishing',
'default' => 0,
'description' => __( 'Fallback category for generated posts.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_log_level',
'label' => __( 'Log Level', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_publishing',
'default' => 'info',
'options' => [
'options' => array(
'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.', 'prautoblogger' ),
],
),
// ── Analytics ───────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_ga4_property_id',
'label' => __( 'GA4 Property ID', 'prautoblogger' ),
'type' => 'text',
'section' => 'prautoblogger_analytics',
'description' => __( 'Your GA4 Property ID — digits only, e.g. 123456789 (the code adds the "properties/" prefix internally). Find it in GA4 → Admin → Property details. Leave blank to skip GA4.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_ga4_credentials_json',
'label' => __( 'Service Account JSON', 'prautoblogger' ),
'type' => 'password',
'section' => 'prautoblogger_analytics',
'description' => __( 'Paste the full JSON key file for a service account with Analytics read access.', 'prautoblogger' ),
],
),
// ── Display ────────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_article_font_family',
'label' => __( 'Article Font', 'prautoblogger' ),
'type' => 'select',
@@ -177,8 +177,8 @@ class PRAutoBlogger_Settings_Fields_Extended {
'default' => 'default',
'options' => PRAutoBlogger_Article_Typography::get_font_choices(),
'description' => __( 'Font family used for generated article body text. Serif fonts improve long-form readability.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_article_font_size',
'label' => __( 'Article Font Size (px)', 'prautoblogger' ),
'type' => 'number',
@@ -187,34 +187,34 @@ class PRAutoBlogger_Settings_Fields_Extended {
'min' => 0,
'max' => 32,
'description' => __( 'Base font size for article body text. 0 uses the theme default (13px). Recommended: 16–18px for comfortable reading.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_table_borders',
'label' => __( 'Table Borders', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_display',
'default' => '1',
'description' => __( 'Add visible borders, padding, and alternating row colors to tables in generated articles.', 'prautoblogger' ),
],
),
// ── Images ─────────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_image_enabled',
'label' => __( 'Enable Image Generation', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_images',
'default' => '0',
'description' => __( 'Generate images for each published article.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_image_b_enabled',
'label' => __( 'Generate Second Image (B)', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_images',
'default' => '1',
'description' => __( 'Generate a second image from source data for A/B testing. Disabling saves one image generation + one LLM prompt rewrite per article.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_image_model',
'label' => __( 'Image Model', 'prautoblogger' ),
'type' => 'model_select',
@@ -223,39 +223,39 @@ class PRAutoBlogger_Settings_Fields_Extended {
'capability' => 'image_generation',
'description' => __( 'Pick an image model. The provider (Runware or OpenRouter) is derived from the model registry on save, so mismatched pairs are no longer possible.', 'prautoblogger' ),
'badge' => __( 'Quality', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_runware_api_key',
'label' => __( 'Runware API Key', 'prautoblogger' ),
'type' => 'password',
'section' => 'prautoblogger_images',
'description' => __( 'Required when using a Runware FLUX model. Get your key at runware.ai/signup.', 'prautoblogger' ),
'icon' => '🔑',
],
[
),
array(
'id' => 'prautoblogger_image_style_suffix',
'label' => __( 'Style Suffix', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_images',
'default' => PRAUTOBLOGGER_DEFAULT_IMAGE_STYLE_SUFFIX,
'description' => __( 'Appended to every image prompt. Controls visual look. Changing mid-run causes visible style drift.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_image_prompt_instructions',
'label' => __( 'Image Prompt Instructions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_images',
'default' => PRAutoBlogger_Image_Prompt_Builder::REWRITER_SYSTEM_PROMPT,
'description' => __( 'System prompt given to the rewriter LLM that turns each article into a SCENE + CAPTION for the image generator. Changing this reshapes the look of all future images. Leave blank to use the default.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_image_nsfw_retry',
'label' => __( 'Retry NSFW-Blocked Images', 'prautoblogger' ),
'type' => 'toggle',
'section' => 'prautoblogger_images',
'default' => '1',
'description' => __( 'When the provider rejects an image prompt as NSFW, retry once with a generic fallback scene built from the article title. Disable to fail fast if the filter gets trigger-happy.', 'prautoblogger' ),
],
];
),
);
}
}

View File

@@ -24,53 +24,53 @@ class PRAutoBlogger_Settings_Fields {
* @return array<string, array{title: string, icon: string, description: string}>
*/
public static function get_sections(): array {
return [
'prautoblogger_api' => [
return array(
'prautoblogger_api' => array(
'title' => __( 'API Keys', 'prautoblogger' ),
'icon' => 'dashicons-admin-network',
'description' => __( 'Connect your external services. Keys are encrypted at rest.', 'prautoblogger' ),
],
'prautoblogger_models' => [
),
'prautoblogger_models' => array(
'title' => __( 'AI Models', 'prautoblogger' ),
'icon' => 'dashicons-superhero-alt',
'description' => __( 'Choose which models power each stage of the pipeline.', 'prautoblogger' ),
],
'prautoblogger_content' => [
),
'prautoblogger_content' => array(
'title' => __( 'Content', 'prautoblogger' ),
'icon' => 'dashicons-edit-large',
'description' => __( 'Control tone, length, pipeline mode, and topic guardrails.', 'prautoblogger' ),
],
'prautoblogger_sources' => [
),
'prautoblogger_sources' => array(
'title' => __( 'Sources', 'prautoblogger' ),
'icon' => 'dashicons-rss',
'description' => __( 'Configure where PRAutoBlogger finds trending topics.', 'prautoblogger' ),
],
'prautoblogger_schedule' => [
),
'prautoblogger_schedule' => array(
'title' => __( 'Schedule & Budget', 'prautoblogger' ),
'icon' => 'dashicons-calendar-alt',
'description' => __( 'Set daily generation schedule, volume, and spending limits.', 'prautoblogger' ),
],
'prautoblogger_publishing' => [
),
'prautoblogger_publishing' => array(
'title' => __( 'Publishing', 'prautoblogger' ),
'icon' => 'dashicons-megaphone',
'description' => __( 'Control how generated content is published.', 'prautoblogger' ),
],
'prautoblogger_analytics' => [
),
'prautoblogger_analytics' => array(
'title' => __( 'Analytics', 'prautoblogger' ),
'icon' => 'dashicons-chart-area',
'description' => __( 'Connect Google Analytics 4 for post performance scoring.', 'prautoblogger' ),
],
'prautoblogger_display' => [
),
'prautoblogger_display' => array(
'title' => __( 'Display', 'prautoblogger' ),
'icon' => 'dashicons-editor-textcolor',
'description' => __( 'Control how generated articles look on the frontend — fonts, sizes, and table styling.', 'prautoblogger' ),
],
'prautoblogger_images' => [
),
'prautoblogger_images' => array(
'title' => __( 'Images', 'prautoblogger' ),
'icon' => 'dashicons-format-image',
'description' => __( 'Image provider, model, and style controls. OpenRouter reuses your existing API key.', 'prautoblogger' ),
],
];
),
);
}
/**
@@ -88,25 +88,25 @@ class PRAutoBlogger_Settings_Fields {
* @return array<int, array<string, mixed>>
*/
private static function get_core_fields(): array {
return [
return array(
// ── API Keys ────────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_openrouter_api_key',
'label' => __( 'OpenRouter API Key', 'prautoblogger' ),
'type' => 'password',
'section' => 'prautoblogger_api',
'description' => __( 'Get your key at openrouter.ai/keys', 'prautoblogger' ),
'icon' => '🔑',
],
[
),
array(
'id' => 'prautoblogger_reddit_source_status',
'label' => __( 'Reddit Data Source', 'prautoblogger' ),
'type' => 'source_status',
'section' => 'prautoblogger_api',
'description' => __( 'Uses Reddit RSS (primary) and .json (fallback). No API key required.', 'prautoblogger' ),
'icon' => '📡',
],
[
),
array(
'id' => 'prautoblogger_ai_gateway_base_url',
'label' => __( 'Cloudflare AI Gateway URL', 'prautoblogger' ),
'type' => 'text',
@@ -114,8 +114,8 @@ class PRAutoBlogger_Settings_Fields {
'default' => '',
'description' => __( 'Optional. Route OpenRouter calls through a Cloudflare AI Gateway for caching, cost logging, and rate limiting. Format: https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openrouter — leave blank to call OpenRouter directly.', 'prautoblogger' ),
'icon' => '☁️',
],
[
),
array(
'id' => 'prautoblogger_ai_gateway_cache_ttl',
'label' => __( 'AI Gateway Cache TTL (seconds)', 'prautoblogger' ),
'type' => 'number',
@@ -124,10 +124,10 @@ class PRAutoBlogger_Settings_Fields {
'min' => 0,
'max' => 2592000,
'description' => __( 'How long Cloudflare may serve cached responses for identical LLM calls. 0 disables caching. Only used when a gateway URL is set above. Safe values: 0 for article generation (always fresh), 3600+ for repeated classification/scoring calls.', 'prautoblogger' ),
],
),
// ── AI Models ───────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_analysis_model',
'label' => __( 'Analysis Model', 'prautoblogger' ),
'type' => 'model_select',
@@ -136,8 +136,8 @@ class PRAutoBlogger_Settings_Fields {
'capability' => 'text→text',
'description' => __( 'Used for source analysis. Pick a cheap, fast model.', 'prautoblogger' ),
'badge' => __( 'Low cost', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_writing_model',
'label' => __( 'Writing Model', 'prautoblogger' ),
'type' => 'model_select',
@@ -146,8 +146,8 @@ class PRAutoBlogger_Settings_Fields {
'capability' => 'text→text',
'description' => __( 'Used for article generation. Quality matters here.', 'prautoblogger' ),
'badge' => __( 'Quality', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_editor_model',
'label' => __( 'Editor Model', 'prautoblogger' ),
'type' => 'model_select',
@@ -156,110 +156,110 @@ class PRAutoBlogger_Settings_Fields {
'capability' => 'text→text',
'description' => __( 'Used for the chief editor review pass.', 'prautoblogger' ),
'badge' => __( 'Quality', 'prautoblogger' ),
],
),
// ── Content ─────────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_niche_description',
'label' => __( 'Niche Description', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'description' => __( 'Describe your site\'s niche. Guides content analysis and generation.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_analysis_instructions',
'label' => __( 'Analysis Instructions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'default' => '',
'description' => __( 'Custom instructions for the topic analysis LLM. Steers how source data is evaluated and which ideas get surfaced — e.g. "Prioritize questions over news. Ignore price-related discussions."', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_tone',
'label' => __( 'Content Tone', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_content',
'default' => 'informational',
'options' => [
'options' => array(
'informational' => __( 'Informational', 'prautoblogger' ),
'conversational' => __( 'Conversational', 'prautoblogger' ),
'professional' => __( 'Professional', 'prautoblogger' ),
'casual' => __( 'Casual', 'prautoblogger' ),
'authoritative' => __( 'Authoritative', 'prautoblogger' ),
],
],
[
),
),
array(
'id' => 'prautoblogger_writing_pipeline',
'label' => __( 'Writing Pipeline', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_content',
'default' => 'multi_step',
'options' => [
'options' => array(
'multi_step' => __( 'Multi-step (outline → draft → polish)', 'prautoblogger' ),
'single_pass' => __( 'Single-pass (one LLM call)', 'prautoblogger' ),
],
],
[
),
),
array(
'id' => 'prautoblogger_min_word_count',
'label' => __( 'Min Word Count', 'prautoblogger' ),
'type' => 'number',
'section' => 'prautoblogger_content',
'default' => 800,
'min' => 200,
],
[
),
array(
'id' => 'prautoblogger_max_word_count',
'label' => __( 'Max Word Count', 'prautoblogger' ),
'type' => 'number',
'section' => 'prautoblogger_content',
'default' => 2000,
'min' => 500,
],
[
),
array(
'id' => 'prautoblogger_writing_instructions',
'label' => __( 'Writing Instructions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'default' => '',
'description' => __( 'Custom instructions appended to the LLM\'s system prompt when writing articles. Use this to steer style, structure, voice, and formatting — e.g. "Always open with a hook question. Use short paragraphs. Cite at least two studies."', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_editor_instructions',
'label' => __( 'Editor Instructions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'default' => '',
'description' => __( 'Custom instructions for the chief editor LLM review pass. Steers what the editor looks for and how it revises — e.g. "Be strict about medical disclaimers. Reject any article under 800 words."', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_topic_exclusions',
'label' => __( 'Topic Exclusions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'description' => __( 'Comma-separated topics to never write about.', 'prautoblogger' ),
],
),
// ── Sources ─────────────────────────────────────────────────
[
array(
'id' => 'prautoblogger_target_subreddits',
'label' => __( 'Target Subreddits', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_sources',
'description' => __( 'Comma-separated, without r/. E.g.: peptides, Nootropics, biohackers', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_enabled_sources',
'label' => __( 'Enabled Sources', 'prautoblogger' ),
'type' => 'checkboxes',
'section' => 'prautoblogger_sources',
'options' => [
'options' => array(
'reddit' => __( 'Reddit (RSS + .json)', 'prautoblogger' ),
'llm_research' => __( 'LLM Deep Research (reasoning model)', 'prautoblogger' ),
],
),
'default' => '["reddit"]',
'description' => __( 'Select which platforms to monitor for topics.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_pullpush_cache_ttl',
'label' => __( 'Research Cache (hours)', 'prautoblogger' ),
'type' => 'number',
@@ -268,21 +268,21 @@ class PRAutoBlogger_Settings_Fields {
'min' => 1,
'max' => 72,
'description' => __( 'How long to cache Reddit research results before re-fetching. Saves bandwidth on low-traffic sites.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_reddit_time_filter',
'label' => __( 'Reddit Time Window', 'prautoblogger' ),
'type' => 'select',
'section' => 'prautoblogger_sources',
'default' => 'day',
'options' => [
'options' => array(
'day' => __( 'Past 24 hours', 'prautoblogger' ),
'week' => __( 'Past week', 'prautoblogger' ),
'month' => __( 'Past month', 'prautoblogger' ),
],
),
'description' => __( 'How far back to search for trending posts and comments.', 'prautoblogger' ),
],
[
),
array(
'id' => 'prautoblogger_reddit_posts_per_subreddit',
'label' => __( 'Posts per Subreddit', 'prautoblogger' ),
'type' => 'number',
@@ -291,7 +291,7 @@ class PRAutoBlogger_Settings_Fields {
'min' => 5,
'max' => 100,
'description' => __( 'Maximum posts to fetch per subreddit per collection run.', 'prautoblogger' ),
],
];
),
);
}
}